原来使用控件,但是鼠标事件会受到子控件影响,现在改成装饰器
xaml
<ctrlHelpers:GridLineDecorator>
<Canvas />
</ctrlHelpers:GridLineDecorator>
cs
public class GridLineDecorator : Decorator
{
public static readonly DependencyProperty ForegroundProperty = TextElement.ForegroundProperty.AddOwner(
typeof(GridLineDecorator));
public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(
typeof(GridLineDecorator));
public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(
typeof(GridLineDecorator));
public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(
typeof(GridLineDecorator));
public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(
typeof(GridLineDecorator));
public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
typeof(GridLineDecorator));
private readonly double pixelsPerDip;
private readonly IDictionary<FontWeight, Pen> penDict = new Dictionary<FontWeight, Pen>();
private readonly Pen defaultPen;
private Typeface? defaultTypeface;
public GridLineDecorator()
{
this.Opacity = 0.75;
this.Loaded += this.OnLoaded;
this.Foreground = Brushes.CadetBlue;
this.defaultPen = new Pen(this.Foreground, 1);
this.pixelsPerDip = DpiHelper.GetScale(this);
}
public Brush Foreground
{
get => (Brush)this.GetValue(ForegroundProperty);
set => this.SetValue(ForegroundProperty, value);
}
public FontFamily FontFamily
{
get => (FontFamily)this.GetValue(FontFamilyProperty);
set => this.SetValue(FontFamilyProperty, value);
}
public FontStyle FontStyle
{
get => (FontStyle)this.GetValue(FontStyleProperty);
set => this.SetValue(FontStyleProperty, value);
}
public FontWeight FontWeight
{
get => (FontWeight)this.GetValue(FontWeightProperty);
set => this.SetValue(FontWeightProperty, value);
}
public FontStretch FontStretch
{
get => (FontStretch)this.GetValue(FontStretchProperty);
set => this.SetValue(FontStretchProperty, value);
}
public double FontSize
{
get => (double)this.GetValue(FontSizeProperty);
set => this.SetValue(FontSizeProperty, value);
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
this.InvalidateVisual();
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
this.InvalidateVisual();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
this.InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var width = this.ActualWidth;
var height = this.ActualHeight;
if (width <= 0 || height <= 0)
{
return;
}
// 绘制横向线
this.DrawHorizontalLines(drawingContext, width, height);
// 绘制纵向线
this.DrawVerticalLines(drawingContext, width, height);
// 绘制鼠标交叉线
this.DrawMouseCrossLines(drawingContext, width, height);
}
private FormattedText? CreateFormattedText(string text)
{
var typeface = this.defaultTypeface;
if (typeface == null)
{
return null;
}
return new FormattedText(
text,
CultureInfo.CurrentCulture,
this.FlowDirection,
typeface,
this.FontSize,
this.Foreground, this.pixelsPerDip);
}
private void DrawHorizontalLines(DrawingContext drawingContext, double width, double height)
{
// 横线
for (double y = 0; y <= height; y += 10)
{
if (y == 0)
{
continue;
}
var currentPath = this.GetPenWithLine(y);
drawingContext.DrawLine(currentPath, new Point(0, y), new Point(width, y));
if (y % 50 != 0 || y == 0)
{
continue;
}
var y1 = y;
this.DrawText(drawingContext, $"{y:0}", text => new Point(1, y1 - text.Height / 2));
}
}
private void DrawMouseCrossLines(DrawingContext drawingContext, double width, double height)
{
// 画鼠标交叉线
if (!this.IsMouseOver)
{
return;
}
{
var mousePen = this.GetPen(FontWeights.DemiBold);
var position = Mouse.GetPosition(this);
drawingContext.DrawLine(mousePen, position with { Y = 0, }, position with { Y = height, });
drawingContext.DrawLine(mousePen, position with { X = 0, }, position with { X = width, });
this.DrawText(drawingContext, $"{position.X:0}", text => new Point(position.X - text.Width / 2, height - text.Height - 1));
this.DrawText(drawingContext, $"{position.Y:0}", text => new Point(width - text.Width - 1, position.Y - text.Height / 2));
}
}
private void DrawText(DrawingContext drawingContext, string text, Func<FormattedText, Point> getTextLocation)
{
var formattedText = this.CreateFormattedText(text);
if (formattedText == null)
{
return;
}
var location = getTextLocation(formattedText);
// 虚化一下背景让文字清晰一些
var textBounds = new Rect(location, new Size(formattedText.Width, formattedText.Height));
drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(200, 255, 255, 255)), null, textBounds);
// 绘制文字
drawingContext.DrawText(formattedText, location);
}
private void DrawVerticalLines(DrawingContext drawingContext, double width, double height)
{
// 纵线
for (double x = 0; x <= width; x += 10)
{
var currentPath = this.GetPenWithLine(x);
drawingContext.DrawLine(currentPath, new Point(x, 0), new Point(x, height));
if (x % 50 != 0 || x == 0)
{
continue;
}
var x1 = x;
this.DrawText(drawingContext, $"{x:0}", text => new Point(x1 - text.Width / 2, 1));
}
}
private Pen GetPen(FontWeight key)
{
return this.penDict.TryGetValue(key, out var pen) ? pen : this.defaultPen;
}
private Pen GetPenWithLine(double len)
{
if (len % 100 == 0)
{
return this.GetPen(FontWeights.ExtraBold);
}
return len % 50 == 0 ? this.GetPen(FontWeights.Bold) : this.GetPen(FontWeights.Normal);
}
private void InheritPropertyFromParent(DependencyProperty sourceProperty, DependencyProperty targetProperty)
{
if (this.ReadLocalValue(targetProperty) == DependencyProperty.UnsetValue &&
this.Parent?.GetValue(sourceProperty) is { } value)
{
this.SetValue(targetProperty, value);
}
}
private void InitPenDict()
{
// 定义线条画笔
var penBrush = this.Foreground.Clone();
penBrush.Opacity = 0.3;
var pen = new Pen(penBrush, 1);
pen.Freeze();
var boldPen = pen.Clone();
boldPen.Thickness = 1.5;
boldPen.Freeze();
var extraBoldPen = pen.Clone();
extraBoldPen.Thickness = 2;
extraBoldPen.Freeze();
var mousePen = new Pen(this.Foreground, 1.5);
mousePen.Freeze();
this.penDict[FontWeights.Normal] = pen;
this.penDict[FontWeights.Bold] = boldPen;
this.penDict[FontWeights.ExtraBold] = extraBoldPen;
this.penDict[FontWeights.DemiBold] = mousePen;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// 如果一些属性没有设置,则使用父类的属性值
this.InheritPropertyFromParent(TextElement.ForegroundProperty, ForegroundProperty);
this.InheritPropertyFromParent(TextElement.FontFamilyProperty, FontFamilyProperty);
this.InheritPropertyFromParent(TextElement.FontStyleProperty, FontStyleProperty);
this.InheritPropertyFromParent(TextElement.FontWeightProperty, FontWeightProperty);
this.InheritPropertyFromParent(TextElement.FontStretchProperty, FontStretchProperty);
this.InheritPropertyFromParent(TextElement.FontSizeProperty, FontSizeProperty);
this.InheritPropertyFromParent(FlowDirectionProperty, FlowDirectionProperty);
this.InheritPropertyFromParent(OpacityProperty, OpacityProperty);
// 输出文字字形
this.defaultTypeface = new Typeface(
this.FontFamily,
this.FontStyle,
this.FontWeight,
this.FontStretch);
this.InitPenDict();
}
}
DpiHelper.cs
public static class DpiHelper
{
public static double GetScale(FrameworkElement element)
{
var source = PresentationSource.FromVisual(element);
return source?.CompositionTarget == null ? 1.0 : source.CompositionTarget.TransformFromDevice.M11;
}
public static double GetScale(Window window)
{
return GetScale(window as FrameworkElement);
}
public static double GetScale(Control control)
{
return GetScale(control as FrameworkElement);
}
}