0

I have defined a Drawing class that derives from the FrameworkElement. I'm using this to draw on the DrawingVisual. In my XAML I have included this local class.

Now my question is how can I bind the properties of this class to a variable or how can I even change them?

For example, let's say I want to change the width property. I included a Button on my XAML and tried to change the width but nothing happens.

All the required code is below:

<Window x:Class="Geom.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:geom="clr-namespace:Geom"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="100"/>
        </Grid.RowDefinitions>
        <Canvas Width="0" Height="0">
            <geom:Drawing x:Name="Drawing" />
        </Canvas>
        <Button Grid.Row="1" Content="Change" Click="ButtonBase_OnClick"/>
    </Grid>
</Window>


public class Drawing : FrameworkElement
{
    private readonly VisualCollection _visuals;
    public int width { get; set; }

    public Drawing()
    {
        RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
        _visuals = new VisualCollection(this);

        var geometryGroup = new GeometryGroup();
        width = 50;
        var depth = 50;
        var x = 50;
        var y = 50;
        var rect = new Rect(x, y , width, depth);
        var rectGeom = new RectangleGeometry(rect);
        geometryGroup.Children.Add(rectGeom);
        geometryGroup.Freeze();

        var drawingVisual = new DrawingVisual();
        using (var dc = drawingVisual.RenderOpen())
        {
            dc.DrawGeometry(Brushes.Blue, null, geometryGroup);
        }
        _visuals.Add(drawingVisual);
    }

    protected override Visual GetVisualChild(int index)
    {
        return _visuals[0];
    }

    protected override int VisualChildrenCount
    {
        get
        {
            return 1;
        }
    }
}


public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        Drawing.width = 10;
    }
}
Vahid
  • 5,144
  • 13
  • 70
  • 146
  • You have to re-draw the visual again on `DrawingContext` to see the changes. – Rohit Vats Aug 03 '14 at 09:16
  • Can you please explain it more? I also defined a `Dependency Property` for the width. And tried to change the width like this ``, it still didn't work. Can you show me how to redraw in an answer. Thanks. – Vahid Aug 03 '14 at 09:19

1 Answers1

1

For DrawingVisual to refresh you have to re-draw visual on it's drawingContext with new values.

So, in case you set width you have to re-draw visual on it's drawing context. Change your code to this:

public class Drawing : FrameworkElement
{
    private readonly VisualCollection _visuals;

    private int width;
    public int VisualWidth
    {
        get { return width; }
        set
        {
            width = value;
            RefreshDrawing();
        }
    }

    public Drawing()
    {
        RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
        _visuals = new VisualCollection(this);

        var geometryGroup = new GeometryGroup();
        width = 50;
        DrawingVisual drawingVisual = new DrawingVisual();
        RefreshDrawing(drawingVisual);
        _visuals.Add(drawingVisual);
    }

    private void RefreshDrawing(DrawingVisual drawingVisual = null)
    {
        var geometryGroup = new GeometryGroup();
        var rect = new Rect(50, 50, VisualWidth, 50);
        var rectGeom = new RectangleGeometry(rect);
        geometryGroup.Children.Add(rectGeom);
        geometryGroup.Freeze();

        drawingVisual = drawingVisual ?? (DrawingVisual)GetVisualChild(0);
        using (var dc = drawingVisual.RenderOpen())
        {
            dc.DrawGeometry(Brushes.Blue, null, geometryGroup);
        }
    }

    protected override Visual GetVisualChild(int index)
    {
        return _visuals[0];
    }

    protected override int VisualChildrenCount
    {
        get
        {
            return 1;
        }
    }
}

Set VisualWidth from button click handler:

Drawing.VisualWidth = 10;
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • 1
    You're welcome Vahid. In case you want to learn more about `DrawingVisuals` refer to the great article [here](http://www.codeproject.com/Articles/22776/WPF-DrawTools). Also, sample is attached with it. – Rohit Vats Aug 03 '14 at 09:49
  • By the way, Am I on the correct path? Is using `GeometryGroup` and drawing on the `DrawingVisual` is the most efficient way I can draw hundreds of geometries in WPF? I'm asking here because the question is so general that cannot be posted on SO and I find you very knowledgeable regarding this. Thanks. – Vahid Aug 03 '14 at 09:53
  • 1
    If it's just Rectangle you are interested in, you can directly draw it using method `drawingContext.DrawRectangle`. There are also direct methods for simple shapes which you can use like `DrawEllipse`, `DrawLine` etc. But when shapes gets complex `DrawGeometry` is a way to go. – Rohit Vats Aug 03 '14 at 09:59
  • And indeed `DrawingVisual` is fastest you can get in WPF to draw thousand of shapes since they are very lightweight compared to UIElements. – Rohit Vats Aug 03 '14 at 10:01
  • What is most efficient? 1) Define the geometry of 1000 rectangles in a foreach loop and add them to `GeometryGroup` and finally use the `drawingcontext.DrawGeometry` to draw. 2) Inside a foreach loop use `drawingcontext.DrawRectangle` to draw 1000 rectangles. This question has been bugging me for ages, Thanks. – Vahid Aug 03 '14 at 10:08
  • I'm asking this because, GeometryGroup itself is freezable and it comes to mind that maybe it is faster to do this the first way. – Vahid Aug 03 '14 at 10:15
  • 2
    I would personally prefer drawing 1000 separate rectangles because for any change in visual you have to redraw them. Say you want to change width of one rectangle but if they are part of GeometryGroup then for 1 rectangle to refresh, you have to re-draw all of them but not with second approach. Also, if you try both approaches you will see second one renders very fast compared to first one. – Rohit Vats Aug 03 '14 at 10:23
  • Thanks Rohit for your clarification, do you have any idea why the second one renders faster? Maybe it is an internal thing? – Vahid Aug 03 '14 at 11:19