65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 中创建图形元素的概念

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (8投票s)

2016年9月21日

CPOL

3分钟阅读

viewsIcon

11674

downloadIcon

298

我参与过的两个项目都需要大量的图形。一般来说,用户与图形之间的交互很少,因此实现非常简单。

引言

我最初是在研究用于白内障手术的设备时想到这个概念的。上面有眼睛的图像,并且在图像上有很多图形元素,这些元素显示了关于眼睛测量结果(球镜、柱镜、轴)的信息,并帮助进行对准,并显示质量读数。前端是 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,因为项目的设计使在 ViewModelView 之间共享一个 class 非常困难。Tuple 的两个属性是 Rect 类,它指定矩形的位置及其 HeightWidth,第二个属性是边框的颜色。

使用代码

这只是该项目中使用的控件之一的 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:初始版本

确定

© . All rights reserved.