Dealing with very low animation frame-rates - c#

Here's the scenario:
I've got a series of many views primarily containing listboxes. Listboxes, some with large amounts of data in them, some with very little.
Currently, I've got an attached behaviour that essentially just detects when new content has been loaded into the container for these views (a ContentPresenter) and triggers a simple XAML storyboard that offsets the container by 200px to the left, or to the right, and also lowers the opacity to 0% (at 0ms) then over 100ms it restores the opacity to 100% and reduces the offset back to 0px, creating a 'sliding into view' sort of effect.
Now, the issue:
The framerate of this animation varies horribly anywhere from a smooth transition to jittery, to outright just lagging at 1 frame until completion.
This is almost certainly due to the fact it's having to load the new view, render everything in it, then even further; lists with loads of data in them need even more render time, THEN whilst all that's happening it's trying to animate its location changing which seems to drastically affect the performance of the animation.
it shocks me that something as simple as moving some pixels across the screen is so graphically intensive for WPF to handle
Here's a crude representation of the animation itself:
My question:
Is there any sort of fundamental best-practice for dealing with intensive animations in WPF so as to improve their performance, or any kinds of small changes to things that when added up boost performance?
Or is WPF just terrible at animating and we just have to deal with it.

I think your problem is, that you try to do everything at the same time or same thread. Just try to do it async in splitted tasks. There are plenty of different approaches to that. Another approach would be to just handle the work with a semaphore, this should push your performance a bit up.

I've come up with a solution which, in my case, has always solved the jitter problem. It is certainly worth trying. The strategy is to take any element that will not change throughout the animation, insert it in a local reference framework such as a grid or canvas, make that grid or canvas store cached versions of its content, and then apply your animation to the grid or canvas, rather than the inside elements. It is as simple as this:
<Canvas ...Your canvas properties...>
<Canvas.CacheMode>
<BitmapCache />
</Canvas.CacheMode>
...Your UI elements...
</Canvas>
or, for a grid:
<Grid ...Your grid properties...>
<Grid.CacheMode>
<BitmapCache />
</Grid.CacheMode>
...Your UI elements...
</Grid>
You'll want to simply ensure that you do not update anything inside the canvas (or grid), as this will undo the cached version and you'll find yourself with a similar issue where WPF will regenerate its rendering on every frame.
By having cached versions of the content, what WPF will be moving across the screen is not a dynamically updated element, it is simply a bitmap of the last render of that element, and WPF will update that bitmap only if changes are applied to the element.
If that does not solve your issues, it means that performance is hindered by something occurring outside the generation of the local element. This means you'll want to look at your overall application, or reduce the entire frame rate of your application. However, in virtually all cases of jittering and jerky movements I found for my WPF animations, the BitmapCache solution seems to work all the time. Just be careful in not applying animations to objects inside the element being cached.

Related

How to initially place WPF controls on dynamic fullscreen application

Alright, so I'm trying to figure out the best way to accomplish this for my rather unique case. I have a fullscreen WPF application where controls are added to a Grid dynamically and the user is given the option to freely move/resize them. The controls can be individually moved or resized anywhere on the parent Grid but I have prevented them from overlapping via their MouseMove events. They also cannot be moved outside of their container. Since the app will be running on machines with different monitor sizes, I need to take that into consideration when making the original layout. I have an initial layout that I would like to use, but it would seem that I'm out of options:
I first tried using rows and columns just to (initially) place the controls that are added. This method places them correctly but becomes a problem when I need to move or resize the elements because the control is already assigned to a particular row and column. I could try to use this method and then remove any rows/columns after placing the controls, but I don't think that would work well.
I've also considered a Viewbox, but that's not practical in my case for fairly obvious reasons (as it merely resizes the controls to fit the screen). I'd prefer not to use this because I would only need it to standardize my initial layout. That's it. I also don't want to mess up any text that will be displayed on the window.
So yeah, this is more of a "best practice" question because any solution I can think of would not look very professional or elegant. Feel free to ask any questions if you need clarification.
Edit: As an additional note, I'd prefer to stick with a Grid as opposed to a Canvas as my container.
Edit 2: Just to be clear, I would not need the specific (inital) layout after the first launch. When the program exits, the layout (Margins, Width & Heights, etc. for each element) is saved to a file to use for the next launch.
I've developed a number of kiosk/interactive applications using WPF. If you are trying to show the element transitions (while moving), then it might be best to use a parent Canvas and bind the Canvas.Left and Canvas.Top properties. You can mimic the grid alignment, using a Canvas, if you put in place mechanisms (e.g. Manipulation/Mouse events, converters) to make sure that the Canvas attached properties adjust to the row/column offsets.
It is not uncommon at all to use the Viewbox to mitigate display differences (and your use case is not "rather unique"). You set the Viewbox to a target resolution (e.g. 1920x1080) and allow the control to fill the available space. The other alternative would be to dynamically apply a content template based on the application window size/ratio.
After looking at multiple options, I've decided to just use a calculation to (sort of) simulate the behavior of rows/columns. Because my application is fullscreen, I can take my SystemParameters.PrimaryScreenWidth and SystemParameters.PrimaryScreenHeight to orient my layout. Using a combination of universal padding (static values) and ratio-based calculations (dynamic values), I can smoothly set my initial layout.
For instance, I'm dividing the width of my monitor by 6 (rounded up to avoid decimals) and using that (minus half the control's width) as the control's Margin.Left property, centering it on a 'column' of sorts.
Honestly, my initial layout is fairly simple right now, so we'll see if this will suffice going forward. Thanks to everyone who contributed to the question, and sorry if I was unclear on what I was asking.

How to draw 1216 canvas elements without hanging application in WPF

I am developing an app I want to add some cool icons. Because I am using the beautiful MahApps library, I want to have a visual on the icons in MahApps.Metro/MahApps.Metro.Resources/Icons.xaml, so I did some string manipulations to grab the x:Key part of each <Canvas x:Key="appbar_3d_3ds" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0"> line. In short, all the string manipulations I did ended up with 1216 copies of the following:
<controls:Tile
Title="appbar_zune" Count="1215" Grid.Row="121" Grid.Column="15" TiltFactor="2" Width="1*" Height="1*" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Rectangle Margin="0" Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}">
<Rectangle.OpacityMask>
<VisualBrush Stretch="Fill"
Visual="{StaticResource appbar_zune}" />
</Rectangle.OpacityMask>
</Rectangle>
</controls:Tile>
Note that each copy of the <control:Tile has the appropriate properties Count, Grid.Row and Grid.Column correctly set.
However, I always end up with the Application not responding window message. Now as I said, my motive is just to get a visual on that collection of pretty icons and instead I get a fat application crash. I just want to know if there is a way to display such a huge collection without crashing anybody's computer (Note: the system is really low on RAM: one of my test machines that run inside virtualbox).
First, make your Fill binding have Mode=OneWay. I bet you don't need it to be TwoWay and it may be the defalt in your setup. TwoWay bindings cost much more.
Second, consider using ever harsher version: Mode=OneTime. Since the icons are unlikely to change, you don't need any change tracking at all. This will save you even more resources.
In your case, First+Second will probably not give you a huge boost, but it's worth trying and remembering.
Third, how about your VisualBrushes? Do all of them use the same Visual="{StaticResource appbar_zune}"? Then why do you create thousands of instances? Instead of copy-pasting, create just one instance and make all items use that one instance. You may save much time and memory.
Fourth, and most important and usually giving the greatest speedup, is - you have tons of items. I bet you have some scrolling, horizontal or vertical. But how do you generate and display those pile of items? Creating them ALL at once is .. wasteful. They don't fit all on screen, right?
Do you have some ItemsControl that generate that thousand items? Investigate the ItemsPanel property of that ItemsControl and turn on virtualizing option on that panel. This will cause it to link to the scrollbar and it will start dynamically creating only those items that are on-screen and destroying items that moved off-screen. Well, I oversimplified it, but letssay it works like that. Note that containers like ListBox (and many others) is an ItemsControl too, so it also applies here.
Or maybe you have huge explicit XAML file with that thousand controls inside some StackPanel with no ItemsControl? That's not really wise. But oh well.. you can still turn on virtualization on that StackPanel.
Turning on virtualization usually is a good idea if you have more than few tens of items. It's often a must of you have a hundred, and it is a must if you reach thousands and more. However, virtualization costs: it very often resets/reinitializes items. If your ItemTemplate is really complex, virtualization may cause the scrolling to became "jaggy/laggy", I don't know how to express that in english, sorry. The compositor thread may simply not have enough time to recalculate and relayout all the fast moving items. If you hit that problem, try setting the Height of the Items to a unchanging truly fixed constant value. It helps greatly in speeding up the layout. but if your ItemTemplate is really wickedly complex, it may not help either. In such dead-end case your only option is ... redesign and to simplify the item template.
EDIT:
Of course all of that won't gain you anything if you don't have scrollbar and if you are trying to display a ton of items at once. In this case, strive to simplify or remove Bindings, Templates, component nesting (sometimes it's better to calculate positions manually than using three embeded Grids), use rendering cache or (...).. Sorry, I start making too many guesses, too many options..
EDIT:
I just noticed Width="1*" and Stretch, so you probably have a Grid at the top, not StackPanel. Since you want them equally-sized, the UniformGrid may have better performance. Also, with some work you can add virtualizing to the Grids, too:
from 4.5 and up, it's much easier - article: WPF 4.5 new virtualizing features
below, it requires more work, see Dan Crevier's 4-part series blog:
One: http://blogs.msdn.com/dancre/archive/2006/02/06/implementing-a-virtualized-panel-in-wpf-avalon.aspx
Two: http://blogs.msdn.com/dancre/archive/2006/02/13/531550.aspx
Three: http://blogs.msdn.com/dancre/archive/2006/02/14/532333.aspx
Four: http://blogs.msdn.com/dancre/archive/2006/02/16/implementing-a-virtualizingpanel-part-4-the-goods.aspx
If virtualizing the Grid is not enough, try moving to Canvas instead and force some width/height/positions manually. Removing automatic layouting sometimes saves a lot. Then, you can use VirtualizedCanvas and if you really put there constant-sized items, you'll probably get it as fast as possible. But that's a last-resort option. Things mentioned earlier should work well.
Oh, and one last word about virtualizing: remember that when ScrollView is working in virtualizing mode, then the Position is not counted in pixels/points anymore. In v-mode, the Position of the scrollbar is counted in items, that is, position=2.5 means that the scroller is at halfway through the third item (2 items passed and a half more), not at pos=2.5 "pixels".
Side note: "million point canvas": https://blogs.msdn.microsoft.com/kaelr/2010/08/11/zoomableapplication2-a-million-items/

Optimize rendering thousands of elements

I'm facing a big problem with optimizing rendering a lot of custom controls. I'm developing a system that will be diplaying thousand custom controls and update several values of these controls every second or frequently. There will be also support for zooming and panning. But I found out that rendering(on canvas) takes a lot of time, zooming and panning is laggy.
Does anyone have an experience with this kind of wpf application?
In my experience this is almost always due to inadvertently disabling virtualization and container recycling, usually through styling/templating. My first step would be to examine the WPF Tree Visualizer at run-time and make sure you're only drawing the elements that are actually visible on-screen.
You can use BitmapCache to disable rerendering controls that have not changed.
In my experience this greatly improves zooming and panning performance.
Enable it for the more complex controls.
Example:
<Rectangle CacheMode="BitmapCache" />
<Rectangle>
<Rectangle.CacheMode>
<BitmapCache/>
</Rectangle.CacheMode>
</Rectangle>
Details here
Use 2 canvases that are the same size and at the same position (a top canvas overlaying a bottom canvas).
Bottom Canvas: displays all controls. This is a static image (cached) which might get refreshed every 30 seconds or so.
Top Canvas: displays every control that has changed in the last 30 seconds.
The 2 canvases are exactly aligned so every recently-changed control on the top canvas will exactly obscure its out-of-date twin on the bottom canvas.
This way you only have to draw the few controls that have changed rather than all thousand controls.

Using CacheMode From Worker Thread

I'm working with a WPF Canvas that contains a lot of elements. It needs to be able to pan and zoom. WPF cannot handle the layout of the canvas since it has too many elements (I don't know how many, but the XAML file is above 20mb).
The canvas is zoomed and panned using scale and translate transforms on the canvas' rendertransform.
A good trick I found is to use CacheMode. This fixes all panning problems as I generate a cache for the zoom level and set that. Easy, like this:
canvas.CacheMode = new BitmapCache(scale);
The problem is that I need to do it every time the scale changes (which is when the user zooms).
I understand that it will take some time to render the canvas and that is probably unavoidable. But how do I move that rendering to another thread so that it doesn't block the whole UI?
I understand the concept of Background Workers and Dispatcher and such, but I'm not sure how to handle this when the task that actually takes up all the time is the setting of a property that I can only access in the UI thread.
Is it possible to somehow generate the cache in another thread and then transfer it to the UI somehow?
Any other good ideas on how to lessen impact on the UI?

Showing content in frame interferes with animation on the page

I ran into a little problem while writing Windows 8 application on C#+xaml. My app has one main page, which is divided in two parts – ListView as a navigation panel on the left, and Frame on the right. All of that looks and works a bit like PC Settings panel. But there is one difference – when page into frame has enough content and it’s scrollable, I perform animation of collapsing navigation panel to icons-only state (when in full width it has text and icons).
So, here is the problem. Let’s say I’m on the first page and panel is collapsed. Then I go to the second page, and I've got to return to full-width state of panel. But since I’m doing it at the same time as showing content in the frame, there is a little freeze of animation, and it's becoming really noticeable when GridView has got a lot of items. So animation freezes on a half way, then GridView render all items images, and then it continue.
Only way to fix this problem I come up with, is to do navigation after animation completed. It’s doesn’t look very pretty, so I go on a blank page first, perform animation, and then go on target page. But this solution still feels wrong, and there still some problems with animation after resume from suspending.
So, is there any way to ensure that rendering of GridView will not interfere with my animation?
Thank you, and sorry for my English.
You can set NavigationCacheMode on the pages to Required and navigate through all the pages to preload them while hiding the Frame using the Opacity property, but that will be slow the first time you do it. Try to limit the number of bitmap pixels you need to decode and render on each page too. If all else fails - see if the Preload() method of the AlternativeFrame control from WinRT XAML Toolkit might be of help.

Categories