WPF 中创建图形元素的概念






4.95/5 (8投票s)
我参与过的两个项目都需要大量的图形。一般来说,用户与图形之间的交互很少,因此实现非常简单。
引言
我最初是在研究用于白内障手术的设备时想到这个概念的。上面有眼睛的图像,并且在图像上有很多图形元素,这些元素显示了关于眼睛测量结果(球镜、柱镜、轴)的信息,并帮助进行对准,并显示质量读数。前端是 WPF,这就是我被雇来做这个项目的原因。我决定最好的方法是在每张图形元素的画布上叠加一个 Canvas,并保持每个 Canvas 简单。
Canvas 的示例
示例中有很多这样的图形 Canvas
示例。它们都非常相似。
这是满足在图像上显示矩形的需求的实现:
public class RectangleObserableCollectionCanvas : Canvas { static RectangleObserableCollectionCanvas() { IsHitTestVisibleProperty.OverrideMetadata(typeof(RectangleObserableCollectionCanvas), new FrameworkPropertyMetadata(false)); BackgroundProperty.OverrideMetadata(typeof(RectangleObserableCollectionCanvas), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Transparent))); } public static readonly DependencyProperty RectanglesProperty = DependencyProperty.Register("Rectangles", typeof(ObservableCollection<Tuple<Rect, Color>>), typeof(RectangleObserableCollectionCanvas), new PropertyMetadata(null, ObservableCollectionChangedCallback)); public ObservableCollection<Tuple<Rect, Color>> Rectangles { get { return (ObservableCollection<Tuple<Rect, Color>>)GetValue(RectanglesProperty); } set { SetValue(RectanglesProperty, value); } } public static readonly DependencyProperty ShowProperty = DependencyProperty.Register("Show", typeof(bool), typeof(RectangleObserableCollectionCanvas), new PropertyMetadata(true, PropertyChangedCallback)); public bool Show { get { return (bool)GetValue(ShowProperty); } set { SetValue(ShowProperty, value); } } public static readonly DependencyProperty ScaleProperty = DependencyProperty.Register("Scale", typeof(double), typeof(RectangleObserableCollectionCanvas), new PropertyMetadata(1.0, PropertyChangedCallback)); public double Scale { get { return (double)GetValue(ScaleProperty); } set { SetValue(ScaleProperty, value); } } public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(double), typeof(RectangleObserableCollectionCanvas), new PropertyMetadata(1.0, PropertyChangedCallback)); public double StrokeThickness { get { return (double)GetValue(StrokeThicknessProperty); } set { SetValue(StrokeThicknessProperty, value); } } public static readonly DependencyProperty StrokeDashStyleProperty = DependencyProperty.Register("StrokeDashStyle", typeof(DoubleCollection), typeof(RectangleObserableCollectionCanvas), new PropertyMetadata(new DoubleCollection { }, PropertyChangedCallback)); public DoubleCollection StrokeDashStyle { get { return (DoubleCollection)GetValue(StrokeDashStyleProperty); } set { SetValue(StrokeDashStyleProperty, value); } } protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { base.OnRenderSizeChanged(sizeInfo); Redraw(); } private static void ObservableCollectionChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { var rectangleObserableCollectionCanvas = (RectangleObserableCollectionCanvas)dependencyObject; rectangleObserableCollectionCanvas.Rectangles.CollectionChanged += (s, e) => rectangleObserableCollectionCanvas?.Redraw(); rectangleObserableCollectionCanvas?.Redraw(); } private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { (dependencyObject as RectangleObserableCollectionCanvas)?.Redraw(); } private void Redraw() { Children.Clear(); if (!Show) return; try { if (Math.Abs(ActualHeight) < 1 || Math.Abs(ActualWidth) < 1) return; foreach (var rectangleProperties in Rectangles) { var rectangle = new System.Windows.Shapes.Rectangle { Stroke = new SolidColorBrush(rectangleProperties.Item2), HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, StrokeDashArray = StrokeDashStyle, Width = rectangleProperties.Item1.Width, Height = rectangleProperties.Item1.Height, StrokeThickness = StrokeThickness / Scale, }; Canvas.SetLeft(rectangle, rectangleProperties.Item1.Left); Canvas.SetTop(rectangle, rectangleProperties.Item1.Top); Children.Add(rectangle); } } catch { } } }
对大多数 DependencyProperties
的更改都会导致 ReDraw
方法被调用。这个 Control
具有以下 DependencyProperties
,可以轻松更改外观
StrokeThickness
:指定矩形边框的粗细StrokeDashStyle
:指定矩形边框的虚线样式Scale
:指定矩形边框的缩放比例。这是必需的,因为此特定控件用于缩放和平移控件,这意味着无论缩放级别如何,边框的粗细在视觉上都是相同的。Show
:这确定是否显示矩形。可以使用 Visibility,但很多时候希望绑定到布尔值,因此需要一个转换器。
在此控件中,主要属性是 Rectangles
DependencyProperty
。我为 DependencyProperty
使用了 ObservableCollection
,因为如果集合发生变化,则需要刷新显示(这意味着调用 ReDraw
方法)。我还在这个集合中的项目使用了 Tuple
,因为项目的设计使在 ViewModel
和 View
之间共享一个 class
非常困难。Tuple
的两个属性是 Rect
类,它指定矩形的位置及其 Height
和 Width
,第二个属性是边框的颜色。
使用代码
这只是该项目中使用的控件之一的 XAML 示例
<graphicsSample:RectangleObserableCollectionCanvas Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" StrokeThickness="2" Opacity=".5" Rectangles="{Binding Rectangles}" Show="{Binding ElementName=RectangleObserableCollectionCanvasCheckBox, Path=IsChecked}" />
此示例也没有显示 Control 可用的所有属性的使用。
样本中包含的其他控件
以下是在示例项目中用于显示图形的 Canvas 派生类的列表。并非所有这些都实际在 MainWindow
中使用
CenteredCircleCanvas
:显示图像中心的圆。CenteredCrossHairCanvas
:显示一个以图像为中心的十字线,不必延伸到边缘EndMarkerCanvas
:用于在中心线的末端显示加号或减号。GridlineesCanvas
:在图像上显示网格。PolarPlotCanvas
:显示一个具有两种大小刻度线的罗盘PolarPlotCanvas2
:显示一个具有三种大小刻度线的罗盘RadialMouseMoveCanvas
:与极坐标图一起使用以移动罗盘RectangleObserableCollectionCanvas
:在图像上绘制矩形。TextPath
:在图像上显示文本TickMarkCanvas
:放置一个小标记,该标记应该在罗盘外,以指示感兴趣的点。
它们往往具有非常不同的属性,因为它们具有不同的复杂性和不同的需求。最复杂的是 PolarPlotCanvas
,它具有指定刻度线和数字字体的属性。
历史
- 2016-09-21:初始版本
确定