Improve performance in WPF with many UIElements - c#

I'm very new in WPF and I need some advise on how to create a view with many UIElements at the same time. What i want to do, is to view some kind of table with fixed width and height for each cell. In each cell could be a random number of textblocks with a different back and forecolor (see image).
Image for table-like view with many UiElements
That is what I have done so far... I created an usercontrol for the content of one cell which binds the items in an itemcontrol.
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{Binding Backcolor, Converter={StaticResource IntegerToBrushConverter}}" >
<Viewbox MaxHeight="20" >
<TextBlock TextWrapping="Wrap" Text="{Binding Caption}"
Foreground="{Binding Forecolor, Converter={StaticResource IntegerToBrushConverter}}"/>
</Viewbox>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This usercontrol is also binded to an itemcontrol for representing the x-axis of my table.
<ItemsControl ItemsSource="{Binding}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:DayView DataContext="{Binding Days}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the same is done for the y-axis with an nice scrollviewer around. So, the problem is, that there are many many elements in the virtual tree. In my sample case there are over 60.000 elements listed in the tree. This results in a bad performance, when opening the view. The scrolling performance is quite okay, but he takes several seconds to open the view.
I've tried some things like the CacheMode and so on, but all of that doesnt effect the opening performance. Using a VirtualizingStackPanel results in bad scrolling performance. I even cant figure out what takes so long to build up the Ui. Seems that he need a lot of time to measure all UiElemtents, but Im not sure...
Are there tips to make such an UiElement rich view any faster? As I said, Im very new in WPF and that is only a performance test. We have such an Ui in Winforms, but there, the whole table was user painted. In WPF it is so easy to rebuild the Winforms design with stock content-controls that I only want to draw all for myself as a last resort.

Take a look here for a possible solution to your problem by using virtualization:
WPF Data Virtualization
Also if you want to go another way, there are commercial 3rd party WPF control libraries that handle virtualization with various degrees of success. To name a few: Xceed, Syncfusion, DevExpress and Telerik.
I even cant figure out what takes so long to build up the Ui.
Hoping you are using VS2015 (or something better), there are a few ways to get an answer to what is taking so long. Visual Studio has a live visual tree tool (Debug->Windows) where you can see the visual tree in real time and it might help you find and eliminate some of the UI elements you don't really need (like borders inside more borders, etc)
Second, you can run the Diagnostic tools (Alt+F2), choose CPU usage and that should generate a report where you can see where your program is spending the most time. Hopefully this will isolate the issue to certain methods that can be optimized afterwards.
Also, more detail about your problem would be welcome. What is the code that populates the ItemsSource do exactly?

Related

Reordering ListBox elements with gong-wpf-dragdrop causes visual bugs

Task
My task was to be able to change the order of the service cards in the ListBox by dragging and dropping. I found a good solution on StackOverflow that has a lot of useful features (gong-wpf-drag drop, https://stackoverflow.com/a/33367826/19631476).
Problem
However, I had to face an unpleasant visual bug: when I drag the service card, there is some label that indicates where the dragged object will be moved. So, for some reason, this label is clipped if a number of objects are also clipped and not visible in the ListBox. However, when scrolling, the cropping does not disappear. Moreover, then the label will always point to the line above, wherever it is.
Problem demo
below I will present some screenshots demonstrating the problem
the normal state of the separator
the separator is cut off due to the fact that the card does not fit into the container
the separator continues to be cut off after scrolling
the separator is shifted up a line, and is still clipped, despite the fact that I move the elements already in another line
My code
This is how my ListBox element looks like:
<ListBox
Margin="10 0 10 10"
Background="Cornsilk"
MinWidth="300"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.SelectDroppedItems="True"
ItemsSource="{Binding ServicesListCollectionView.View}"
SelectedIndex="{Binding SelectedServiceIndex}"
SelectedItem="{Binding SelectedService}"
d:ItemsSource="{d:SampleData ItemCount=5}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border AllowDrop="True" Style="{StaticResource BorderStyle}">
<DockPanel Background="GhostWhite">
<!-- Service Card Template-->
</DockPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
it contains a custom container so that the service cards can be arranged as a grid from top to bottom
What have I tried to do?
The idea came to my mind to prohibit the use of ScrollBar for the ListBox element. It worked, and the visual bug sort of disappeared. But you can't leave it like that. I wrapped the ListBox in ScrollViewer and allowed it to scroll vertically. This visual bug is no longer there:
code:
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="ServiceScroll">
<!-- Here is ListBox -->
</ScrollViewer>
However, this solution is not suitable:
I lose control of scrolling in the ListBox and can only use scrolling on the ScrollViewer itself
Scrolling also becomes unavailable when dragging items.
the second problem can be solved by specifying the dd:DragDrop.DropTargetScrollViewer ScrollViewer with binding to the ScrollViewer element:
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="ServiceScroll">
<ListBox
...
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.SelectDroppedItems="True"
dd:DragDrop.DropTargetScrollViewer="{Binding ElementName=ServiceScroll}"
...
></ListBox>
</ScrollViewer>
but in this case, the visual bug described above appears again, and also, this solution still does not allow you to scroll through the elements inside the ListBox using the mouse wheel.
Any ideas?
To be honest, I do not know what to do. Has anyone encountered anything described above?
Maybe I'm doing something wrong, which is why I get such strange behavior? What's wrong with scrolling?
Maybe you have good solutions to solve the problem? How else would it be possible to solve the issue of dragging items to the ListBox in the most flexible way?

How to further optimize Windows Store apps?

I have 2 levels deep collection that I render into something like a Header, Child Item.
Roughly, my code looks something like this:
<ScrollViewer>
<ItemsControl IsEnabled="{Binding Path=IsEnabled}"
ItemsSource="{Binding Path=HeaderViewModels, Mode=OneTime}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsTemplate>
<DataTemplate>
<StackPanel Visibility="{Binding Path=ShouldShow, Converter={StaticResource SomeConverter}}">
<TextBlock Text="{Binding Path=Description, Mode=OneTime}" />
<ItemsControl ItemsPanel="{StaticResource WinRTXAMLWrapPanel}"
ItemsSource="{Binding Path=ChildViewModels, Mode=OneTime}">
<i:Interaction.Behaviors>
<SomeClass:AdjustSizeBehavior SizeFromParentMode="Height" />
</i:Interaction.Behaviors>
<ItemsControl.ItemsTemplate>
<DataTemplate>
<ContentControl>
<i:Interaction.Behaviors>
<SomeClass:IsEnabledBehavior />
<SomeClass:PointerPressBehavior />
<SomeClass:PointerLeaveBehavior />
</i:Interaction.Behaviors>
<StackPanel>
<TextBlock Text="X">
<i:Interaction.Behaviors>
<SomeClass:SetXMarkColor />
</i:Interaction.Behaviors>
</TextBlock>
<TextBlock Text="{Binding Path=Description, Mode=OneTime}" />
</StackPanel>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
</ScrollViewer>
Left out the styling, but its pretty much background, margins, font-size, etc. Also, you can see I'm using lots of Bindings and Behaviors.
I already did most of the optimization I can think of. Removed unnecessary UI elements (ex. rectangles used for 'backgrounds'), used StackPanel instead of Grid, set column / height to static instead of auto. Basically done what I can on this links: Twelve ways to improve wpf performance and Best practices for Windows Store Apps
With the optimizations I have done, I have actually reduced loading time of 10 headers + 8 child each (total of 80), from 1.8 sec to around 0.5 ms - 0.6 ms.
However, I would like it to reach less than 0.5 ms so it is like 'instant'.
Is there anything else I can do to improve performance?
Thank you!
It looks like your XAML is quite clean. What about your C# code?
If you use async/await, and push tasks into a background thread, it can really boost performance. Async/await is the secret to creating fast, ressponsive apps.
Visual Studio 2015 has some great improvements when it comes to identifying and fixing WPF performance bottlenecks.
You can identify which lines of code are taking the longest, and are thus a candidate for pushing onto a background thread using async/await, using the profiler. The profiler is awesome: it displays how long each block or line of code has taken, in milliseconds, right in the editor.
More:
https://channel9.msdn.com/Events/Build/2015/3-635
https://channel9.msdn.com/Events/dotnetConf/2015/Debugging-Performance-Issues-Using-Visual-Studio-2015
While we are at it, if Microsoft implements compile time bindings in WPF, it will dramatically boost performance. Binding speed can be as much as 10x faster as its no longer based on reflection. Add your vote to implement this feature here: https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/7810488-implement-x-bind-for-wpf
The question of whether or not to use to use a master/detail grid is an interesting related question. I was chatting to a dev who has been using WPF since it was released in 2006. He is a bit of a guru, and he was of the opinion that master/detail grids are not always the best thing from both a usability and speed point of view. The reason is that you almost always end up loading all of the details on startup, which is slow, unless you resort to some interesting tricks to lazy load in the background. And if you have a few of the detail grids expanded, the screen begins to look messy.
Its almost as if master/detail grids are possible, but they are not a generally a good idea. Look around for successful, usable software that uses master/detail grids. They are relatively rare on the evolutionary landscape, with the exception of Windows Explorer style interfaces. They are not very common in Apple land, which is a hint that they are perhaps not optimum from a users point of view.
And the really odd thing is that relatively experienced programmers tend to like master/detail grids - a lot. They seem to have an intuitive appeal to programmers because they model the data so well. But from a users point of view, they would rather have a flat grid that loads up quickly, and a Properties panel below the grid which always displays details of the current row we have selected.
Yes, this answer doesn't address your exact question - it's too philosophical for that - but it sure as hell would solve your speed problems in one neat refactor, and potentially make your app more intuitive for your users.

Dynamic Panel of Buttons WPF C# Common Use

I am trying to find a best or common practice for creating a table of buttons from a database list.
For my use I am creating more of a Point of Sale type screen. Where I want the categories to load as buttons on the entry screen. The buttons would have a simple task of showing a screen of more dynamically created buttons of the actual items. Those buttons would add the items to, lets call it, a ticket.
The solutions I found were few. I am also trying to code this so others can pick it up fairly quickly. I am extremely rusty and only code once in a while. So I try to follow common ways of doing it. I had some ideas but the code becomes hard to read, mostly because of me.
I saw the below link but was not sure if a ListBox was a good container for this.
Dynamic filling WrapPanel buttons from DB, setting the event handlers
I am sure a wrappenel is what I would have to use, but do I put it in a container or use it directly. Do I put it in Xaml or code it all, for issues like spacing between buttons? I think I am overthinking it and need to skip for a little bit.
Thank you,
It sounds like you want an ItemsControl, bound to your categories, with a WrapPanel as the ItemsPanel. The Button would go in the ItemTemplate.
<ItemsControl ItemsSource="{Binding CategoriesFromDatabase}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"
Command="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl},Path=DataContext.AddToTicketCommand}"
CommandParameter="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here I assumed that your view model has properties "CategoriesFromDatabase" (an IEnumerable that you populate from the database), and an ICommand "AddtoTicketCommand" which takes the category as a parameter.

Calendar Programming: How can I place, create and size items that represent a task using MVVM?

I just have a conceptual question here. First of all I am programming a Windows 8.1 App using C# and XAML as well as the MVVM-Light Toolkit.
I am working on a Schedule at the moment which is backed by a calendar. The calendar should have a "DayView" which consists of a timeline on the left that goes from top to bottom. Now on the right of that timeline I want to show something similar to a rectangle as a visualization for a given task with a height that represents its duration. Every task has a starting time so the top of that rectangle should be placed next to the corresponding entry in the timeline.
Well I hope it is clear what I am trying to say here basically that should look similar to the Calendar-App coming with Windows 8. I will post a screenshot of that below that post.
Now I have tried or thought of multiple things by now that lead to more or less complicated problems. The best approach I had was creating a List a placing it on the right of that timeline. Now that List would contain a huge amount of items (maybe one for each pixel) which will be changed in size. The size then is 0 or in relation to the duration of the task depending on whether there is a one or not.
But that leads to huge problems when it comes to changing an entry or having more than one task for the same time since those items wont overlap. And I don't want to place more than one or two of those per page since the performance-issue would be overkill...
One of the issues I have is that I am using the MVVM-Concept and don't know how to be able to create new UI-Elements such as a rectangle without touching the code-behind. That is why basically the only thing I have thought about by now were lists...
I hope it is clear what I meant.
Thank you very much for your help!
FunkyPeanut
Problem solved using a Grid as the ItemsPanel of an ItemsControl:
<ItemsControl Grid.Column="2" ItemsSource="{Binding Day.ItemsList, Source={StaticResource Locator}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Width="20"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="{Binding Height}" Width="20" Margin="{Binding Margin}">
<Grid.Background>
<SolidColorBrush Opacity="{Binding Opacity}" Color="{Binding ColorHash, Converter={StaticResource HexToColorConverter}}"/>
</Grid.Background>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
With the following Data:
ItemsList = new ObservableCollection<object>(tmpPeriodsList.Select((x, i) => new
{
ColorHash = x.ColorHash,
Index = i,
Margin = StartPosition(i),
Opacity = 0.6,
Height = 45,
}));

How to fill ItemsControl using an integer

I am trying to create a kind of progress bar control in WPF. It will be ellipses that fill in as each step completes. However, I am failing at coming up what I would consider a clean example (minimal code behind and waste). Here is what I have:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Fill="Blue" Stretch="UniformToFill"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then, I want to have two dependency properties:
TotalSteps
StepsCompleted
What I want is when TotalSteps = 14 then there will be 14 ellipses and the fill would work off of a trigger using StepsCompleted, a math converter and the ellipse's index. However, I haven't been able to figure out a way to do this unless I create a dummy list that is created with blank objects that will act as my DataContext...but, that seems like a waste. Is there a way to fill the template based off of the number, and not need a backing List?
Alternatively, I would accept a solution that would be an override of the progress bar template. However, I found nothing, so the user control seemed the easiest

Categories

Resources