Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Concept on Creating Graphics Elements in WPF

0.00/5 (No votes)
21 Sep 2016 1  
I have had two projects that I have worked on that required a lot of graphics. In general there has been minimal interaction between the user and the graphics, so the implementation has been very straight forward.

Introduction

I initially came up with this concept when working on a device that is used for cataract surgery. There was the image of the eye, and over the image there were a lot of graphic elements that displayed information about the measurements of the eye (sphere, cylinder, axis), and helped in alignment, and showed that there was a quality reading. The front end was WPF, which is why I was hired to work on the project. I decided that the best way to do this was to overlay the eye image with a Canvas for each graphics element, and to keep each Canvas simple.

An Example of a Canvas

There are quite a few of these Graphics Canvas examples in the sample. They are all pretty similar.

This is the implementation for the requirement to displayed rectangles over an image:

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 { }
    }
}

The changes to most of the DependencyProperties results in the ReDraw method being called. This Control has the following DependencyProperties that allow easy change to the look:

  • StrokeThickness: specifies the thickness of the border of the rectangles
  • StrokeDashStyle: specifies the dash style for the border of the rectangles
  • Scale: speicied the scaling of the rectangle borders. This is required because this particular control is used in a Zoom and Pan control, and meant that the thickness of the borders was visually the same no matter what the zoom level was.
  • Show: this determines if the rectangle is shown or not. Could use Visibility, but many times want to bind to a boolean value, and so would require a converter.

In this control the major property is the Rectangles DependencyProperty. I used the ObservableCollection for the DependencyProperty because if there was a change in the collection, the display needed to be refreshed (meaning the ReDraw method is called).I also used a Tuple for the items in this collection because of the design of the project, which made it very difficult to have a class that was shared between the ViewModel and the View. The two properties of the Tuple is  Rect class that specifies the postiion of the rectangle and its Height and Width, and the second property is the color for the border.

Using the code

This is just an example of the XAML for one of the controls that is used in this project:

<graphicsSample:RectangleObserableCollectionCanvas Grid.Column="0"
                         HorizontalAlignment="Stretch"
                         VerticalAlignment="Stretch"
                         StrokeThickness="2"
                         Opacity=".5"
                         Rectangles="{Binding Rectangles}"
                         Show="{Binding ElementName=RectangleObserableCollectionCanvasCheckBox,
                                                     Path=IsChecked}" />

This sample also does not show the use of all the properties available for the Control..

Other Controls Included in the Sample

Here is a list of the Canvas derived classes for displaying graphic included in the sample project. Not all are actually used in the MainWindow:

  • CenteredCircleCanvas: Displays a circle center on the image.
  • CenteredCrossHairCanvas: Displays a cross hair centered on the image that does not have to extend to the edges
  • EndMarkerCanvas: Used to display a plus or minus at the end of a centered line.
  • GridlineesCanvas: Displays a grid over the image.
  • PolarPlotCanvas: Displays a compass that has two sizes of tick marks
  • PolarPlotCanvas2: Displays a compass that has three sizes of tick marks
  • RadialMouseMoveCanvas: Used with the polar plots to move the compass
  • RectangleObserableCollectionCanvas: draws rectangles over the image.
  • TextPath: Displays text over the image
  • TickMarkCanvas: Puts a little marker that is supposed to be outside the compass to indicate points of interest.

They tend to have very different properties because they are different complexities, and different needs. The most complex one is the PolarPlotCanvas which has properties to specify the tick marks and font for the numbers.

History

  • 2016-09-21: Initial Version

determines

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here