0

My Application displays a lot of lines and polygons/paths on a canvas. My ViewModel holds a series of ObservableCollections that represent different items to be drawn.

The issue I have is the application is very slow to zoom and pan. Zoom and pan is all taken care of using an IvalueConverter and converts from world coordinate system to the canvas coordinate system.

For this to work, I have to NotifyPropertyChange all objects visible on the screen to force them to be redrawn with the latest pan and zoom values. It works very well for a few hundred lines, but for thousands it’s very slow. And if you zoom out so all objects are visible therefore subject to NotifyPropertyChange it’s almost unusable with over 10,000 lines.

I’m not using the polygons built-in features in any way as all handling, selection moving etc is taken care of in the viewmodel. I therefore want to try and use DrawingVisual instead of Shapes as I understand they have much lower overheads but I can’t find any good MVVM examples of how to use them. Examples I have seen show them being built in codebehind which isn’t how I think I should be using them.

Examples as follows:

//new DrawingVisual
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80));
drawingContext.DrawRectangle(System.Windows.Media.Brushes.LightBlue, (System.Windows.Media.Pen)null, rect);
drawingContext.Close();

//creating a Host as follows:
public MyVisualHost()
    {
    _children = new VisualCollection(this);
    _children.Add(CreateDrawingVisualRectangle());
    _children.Add(CreateDrawingVisualText());
    _children.Add(CreateDrawingVisualEllipses());

   this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(MyVisualHost_MouseLeftButtonUp);
    }

Unless there is a WPF way to bind to my observable collection using DrawingVisual I would need to create and delete drawings objects in my models. Every-time a model is updated it would then have to update my drawingVisual. But then I’m creating View items in model which can’t be the correct way. Can anybody advise how I should go about implementing DrawingVisual instead of Shapes in my MVVM application?

Here is an extract of the code I am currently using that uses Shapes

XAML

    <ItemsControl x:Name="Catchments">
        <ItemsControl.Resources>
            <CollectionViewSource x:Key="CatchmentPolygons" Source="{Binding Path=NetworkMain.Catchments}"></CollectionViewSource>
            <DataTemplate DataType="{x:Type cad:Catchment}">
                <Polygon 
                    Stroke="{Binding IsSelected, Mode=OneWay, Converter={StaticResource ObjectColour}, ConverterParameter=Catchment}"
                    StrokeThickness="1" 
                    Visibility="{Binding Visible, Mode=OneWay, TargetNullValue='Hidden'}"
                    Points="{Binding Points, Mode=OneWay, Converter={StaticResource CollectionPointConverter}}">
                    <Polygon.Fill>
                        <SolidColorBrush 
                            Color="{Binding IsSelected, Mode=OneWay, Converter={StaticResource ObjectColour}, ConverterParameter=Catchment}" 
                            Opacity=".25"
                            >
                            
                        </SolidColorBrush>
                    </Polygon.Fill>
                </Polygon>
            </DataTemplate>
        </ItemsControl.Resources>
        <ItemsControl.ItemsSource>
            <CompositeCollection>
                <CollectionContainer Collection="{Binding Source={StaticResource CatchmentPolygons}}"></CollectionContainer>
            </CompositeCollection>
        </ItemsControl.ItemsSource>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas 
                    ClipToBounds="true">
                </Canvas>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>

My Observable Collections in my ViewModel:

    public ObservableCollection<Conduit> Conduits { get; set; } = new();
    public ObservableCollection<Node> Nodes { get; set; } = new();
    public ObservableCollection<Catchment> Catchments { get; set; } = new();

EDIT

Screenshots of the Canvas with a zoomed in and zoomed out view:

Zoomed out:

enter image description here

The images have scaled a little bit but in reality, line thicknesses, node sizes and arrows remain constant as you zoom in and out. Only the projected coordinates of nodes change.

Zoomed in:

enter image description here

Richard
  • 439
  • 3
  • 25
  • You could add a dependency property to your visual host and bind this one to the source property of the view model. Then the visual host can create a `DrawingVisual` per item in the source collection. – mm8 May 10 '21 at 13:28
  • If you need strong acceleration, then asynchrony is indispensable. You need to build a Shape or DrawingImage on Sharp in an asynchronous task, freeze and return a frozen instance from any thread. BUT this will require a very strong re-architecture of the application. – EldHasp May 10 '21 at 16:14
  • Do your shapes change? If they don't then i would use one observablecollection as itemssource of an itemscontrol and render them in it's itemspanrl - a canvas. Then rendertransform the canvas to zoom and pan. – Andy May 10 '21 at 17:35
  • Thanks for all the suggestions. @Clemens: I will get my head round `DrawingVisual` soon so I can do some comparisons in performance. @mm8: This sound like it's the ticket to honest. May take a fair bit of trial and error to get something that works. – Richard May 10 '21 at 19:17
  • @EldHasp: I haven't experimented with Threading at all yet. At the moment I'm working on items that the user will interact with, but later plan on adding the ability to load drawing files as a background which the user wont interact with. I like the idea of the user objects working on the main-thread and the background layers of separate threads. Or something like that. – Richard May 10 '21 at 19:17
  • @Andy: The user can add circles, lines and polygons to the canvas. Move them around, snap them to each-other and highlight them. But later I plan on adding background layers as mentioned above. I don't know if it's a valid reason, but I opted to not use RenderTransform as I wanted to keep the circle sizes, line weights, arrow sizes etc the same regardless of zoom so I would need a bunch of bindings anyway to update the sizes. – Richard May 10 '21 at 19:17
  • I don't follow how you can zoom in or out yet maintain the same size of things. – Andy May 10 '21 at 19:37
  • I've added a couple of screen grabs to explain what I mean – Richard May 10 '21 at 20:44
  • @Richard: Did adding a dependency property to your visual host solve your original issue? – mm8 May 17 '21 at 15:46
  • @mm8 apologies, just seen this. Yes it did thanks. It works really well. – Richard May 31 '21 at 17:59

1 Answers1

1

You could add a dependency property to your visual host and bind this one to the source property of the view model.

Then the visual host can create a DrawingVisual per item in the source collection

mm8
  • 163,881
  • 10
  • 57
  • 88