Morph
Morph

Reputation: 65

Structure and Performance with WPF and MVVM

I have a requirement to create a simple menu/selection design tool - the idea is to have a selection of categories and a larger selection of choices which are filtered by a category selected. Ultimately this will run in a Kiosk-style environment.

I'm using MVVM and my design consists of a view containing 2 grids inside two ItemsControls - one for the categories (2 rows by 10 columns) and one for the choices (10 x 10). In my ViewModel these ItemsControls are both bound to ObservableCollection objects with some details (caption, colour etc.) bound to properties of the part. The DataTemplate is bound to an item class in a seperate assembly as I want to reuse them in the kiosk app too.

I've used a "ModifiedBehaviours" class to map right and left clicks on my grid objects to commands picked up by the ViewModel similar to this

How can I attach two attached behaviors to one XAML element?

The design seems "clean" from what I have read (relatively new to MVVM here) in that the code-behind of the view has nothing in it except assigning the ViewModel to the window's DataContext, no x:name= tags in the view and the ViewModel doesn't interract with the view directly.

However I do have a problem with performance.

When the user clicks on a category I create a new ObservableCollection containing the detail items for it - I also fill in the blanks in design mode so I end up with 100 choices, the empty ones having "Right click to edit" in them.

The time to create this collection is tiny - < 0.01 secs on a 1.6 Ghz netbook (the Kiosk PC will be slow so I'm testing on slowish hardware). However once the collection the control is bound to is refreshed the UI takes about 2 seconds to update which is far slower than the older app I'm recreating - that is written in very old Delphi.

I've tried various things. Firstly I simplified my XAML by removing some gradients and other stuff to make it as simple as possible - eventually coming down to a single textblock. Then when refreshing the collection I create it seperately and then assign it to the bound one so the update happens "all at once". Thirdly I broke my design slightly and added a BeginInit() and EndInit() around the update code for the window and the grid.

None of these made the slightest improvement.

The following did - which leads to my question(s).

  1. I removed one of the command behaviours from the item control - I need both though for right and left click. Could the fact this has to bind the event for each item cell (100 of them) to the command cause the problem ?

Is there an alternative approach to right and left click for each grid cell ?

  1. <- this is 2 in my markup! I copied the XAML for the item (a border, a textblock and the commands) from the seperate assembly into the main window XAML. This gave the largest, simple improvement but I don't understand why.

Both seperately made a difference, both together make the performance "acceptable" - still very sluggish though. Is there anything else I can be looking at ?

My control looks like this (in a seperate assembly). Before anyone points out as stated above I've tried removing a lot of this and even cutting this right down to just be a textblock.

<Control.Resources>
    <Style x:Key="MyBorderStyle" TargetType="Border">
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsSelected}" Value="True">
                <Setter Property="BorderBrush" Value="Red"/>
                <Setter Property="BorderThickness" Value="3"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding IsSelected}" Value="False">
                <Setter Property="BorderBrush" Value="{Binding BackColour}"/>
                <Setter Property="BorderThickness" Value="0"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Control.Resources>
<Grid>
    <Border Margin="1" Style="{StaticResource MyBorderStyle}" CornerRadius="8">
        <CommandBehaviour:CommandBehaviorCollection.Behaviors>
            <CommandBehaviour:BehaviorBinding Event="MouseLeftButtonDown" Command="{Binding DataContext.SelectLeftCommand, 
                RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" CommandParameter="{Binding ItemKey}"/>
            <CommandBehaviour:BehaviorBinding Event="MouseRightButtonDown" Command="{Binding DataContext.SelectRightCommand, 
                RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" CommandParameter="{Binding ItemKey}"/>
        </CommandBehaviour:CommandBehaviorCollection.Behaviors>
        <Border.Background>
            <LinearGradientBrush StartPoint="0.7,0" EndPoint="0.7,1">
                <GradientStop Color="{Binding BackColour}" Offset="0"/>
                <GradientStop Color="#33333333" Offset="1.5"/>
            </LinearGradientBrush>
        </Border.Background>
            <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{Binding ForeColour}" Text="{Binding Description}"/>
    </Border>
</Grid>

I can leave a lot of this in place when I copy it to the ItemsTemplate in the main window and the performance improvement sticks.

Statement-Rant One of the biggest problems with WPF seems to be that a lot of the performance (and other) issues are "down the rabbit hole" somewhere - in code behind the scenes - which makes it very difficult to work out what is happening without external and sometimes quite complex perf and profiling tools and the time to download, install and learn them.

Which is pants really.

And breathe... End of Statement-Rant

Upvotes: 4

Views: 1629

Answers (2)

akjoshi
akjoshi

Reputation: 15802

when refreshing the collection I create it seperately and then assign it to the bound one so the update happens "all at once".

If it's just a refresh then you should not be creating a new ObservableCollection, instead just add/remove the items; assigning a new ObservableCollection would cause the WPF engine to clear old UI and render the whole UI again, which will be time consuming.

Also check for binding errors as they too impact the performance, look at the output window in Visual Studio while in Debug mode, you'll get details of the binding error. This happens a lot with RelativeSource bindings and can be a bottleneck in your case too.

Upvotes: 1

G.Y
G.Y

Reputation: 6159

that's your problem right there:

"When the user clicks on a category I create a new ObservableCollection containing the detail items"

Creating a new collection is costly.. creating a new collection which being bounded for each click of the user - is very costly.

Can you try to create the collection just once, and resuse it?
(It's just an educated bet - but that what I would aim for if I were you)

Upvotes: 1

Related Questions