WPF Textblock Performance Poor - wpf

I have been having trouble with the WPF DataGrid and listbox GridView performance when displaying even small amounts of data. I though this problem was simply WPF having poor performance in general, but the problem seems to lie in the textblock control only.
I created a sample panel that I added several items to. If I add rectangles that are simply filled, the resizeing/scroll performance is perfect, but once I use textblocks, the performance goes out the window.
It looks like the performance issue arises from:
child.Measure(constraint);
When the textblock gets measured, it brings performance to a grinding halt. Is there anything that I can to to override the measurement of a textblock or something to improve performance? (I will set the size of the children explicitly)
EDIT: I have now created simplified code to arrange the items as I wanted.
The performance of this code is great except...when the width of the text inside the textblock exceed the actual width of the textblock. This brings my performance back down to a crawl - possibly because it is trying to measure the elements again?
public class TestPanel : Panel
{
private int _rowHeight = 20;
private int _columnWidth = 50;
public TestPanel()
{
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 20; j++)
{
TextBlock cell = new TextBlock();
cell.ClipToBounds = true;
cell.Width = _columnWidth;
cell.Height = _rowHeight;
cell.Text = i.ToString() + ":" + j.ToString();
this.Children.Add(cell);
}
}
}
protected override Size MeasureOverride(Size constraint)
{
return new Size(_columnWidth*20,_rowHeight*100);
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
UIElementCollection children = InternalChildren;
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 20; j++)
{
UIElement child = children[i*20+j];
child.Arrange(new Rect(j * _columnWidth, i * 20, _columnWidth, 20));
}
}
return arrangeBounds;
}
}
public MainWindow()
{
InitializeComponent();
TestPanel myPanel = new TestPanel();
ScrollViewer scroll = new ScrollViewer();
myPanel.Background = Brushes.Aqua;
scroll.Content = myPanel;
this.Content = scroll;
}

The performance difference between TextBox and Rectangle is due to the different complexity of these controls. Just compare the complexity of the resulitng visual trees (i.e. using XamlPad). A Rectangle most likely just knows its desired size. A TextBox, on the other hand, needs to consider many different factors when calculating the desired size, such as the desired size of the acutal text (I guess this is the real bottleneck).
Having that said, there are some optimizations you might want to try. The goal of the measure pass is to determine your desired size. Furthermore, you propagate the measure pass by calling measure on all child elements. However, you only need to do this if you expect a change of the desired size. It seems like you know a lot about your layout having _rowHeight and _columnWidth fields. So do the following:
Measure your children using: child.Measure(new Size(_columnWidth, _rowHeight)). This is the actual constraint right?
Reduce the number of measure runs for your child elements. Do this by moving all this code out of MeasureOverride and only call this function if _rowHeight or _lineWidth changes (also, this will be the method which calls Measure on your child elements). Implement these fields as DependencyProperties to be able to listen to changes (you can use INotifyPropertyChanged if you don't like DependencyProperties)
Most likely, you can implement MeasureOverride (now without having to measure your child elements) in constant time (e.g. numberOfColumns * _columnWidth...)
Implement similar logic for ArrangeOverride.
In other words: don't do layout logic (i.e. deciding questions like "does this element fit into this line") in MeasureOverride/ArrangeOverride
This approach, however, does not respect the desired size of the TextBox elements. You can either not care or solve this separately:
Listen to text changes, choose the appropriate event.
If the text in a TextBox changes, call Measure only for this particular text box. (You can measure this textbox with positive infinity as constraint)
Adapt your ColumnWidth and RowHeight properties
Apart from improving your MeasureOverride/ArrangeOverride implementations you can use a different (e.g. more lightweight) ControlTemplate for the TextBox. I would opt for rewriting MeasureOverride/ArrangeOverride.

First of all, after testing your code it appears that you've rewritten the WrapPanel that already exists in WPF. When I replaced your TestPanel with the WrapPanel the behavior was exactly the same, with an improvement to performance.
Secondly, I am wondering what kind of hardware, specifically, video card you are using. When I ran this sample on my PC I saw little to no lag. Certainly not the "grinding halt" that you are speaking of.
Finally, the only way I know of to improve the performance of text-rendering is to use low-level text objects. FormattedText comes to mind. This is far more difficult to work with than TextBlock, however, so I would encourage you to think about what it is you are trying to accomplish before switching to FormattedText.
EDIT:
The real area where WPF is hurting your performance is in the Measure and Arrange passes of the layout system. It's important to understand that every time you resize the window, WPF is re-calculating the sizes and positions of every user-interface element, and then rearranging them accordingly. This is extremely useful for achieving flexible layouts or creating dynamic user interfaces, but for a static grid of data (which seems to be what you have in mind), this is doing more harm than good. On most machines, WPF will offload as much work as possible to the GPU. In your case, however, the CPU is handling everything, hence the "churn" you are seeing on resize.
FormattedText would be faster, but would not lend itself to working with a data-grid. Rather than writing your own layout panel (a 1% scenario in WPF), I would switch to a ListView or a third-party grid component and see how performance is at that point. These kind of components are built (and optimized) to display vast rows of changing data-- the WPF layout panels are built to contain other user interface elements and drawings.

Related

xaml schedule control how to cope with items of vastly differing durations

I have to create a schedule control using WPF XAML for displaying items with duration as little as 1 seconds and potentially as large as couple of hours.
First thing which seems clear is that I will need some kind of zoom capability, for drilling into the items with very short durations and zooming out to view the items with very large durations.
I have a UI style in mind where by when the control is zoomed to view the large items, any small duration items which generally occur between the large duration items are some how collected in to a 'bucket' which has a height >= a minimum display height. So that I can perhaps hover a mouse over this bucket and get a tool tip which renders the contained items into a time ordered list view.
I am at a loss as to where to begin. I feel that I could perhaps achieve something in the Measure/Arrange overrides of a custom Panel. But I think this could adversely affect render performance. If I were to do it this way, I guess I would need to inject a 'bucket' view into the panels children collection, but that would break the use of ItemTemplate in my Custom Items Control?
My second though is that I create a custom observable collection which has a bindable Scale property which then organises the collection into 'buckets' of a duration large enough to be displayable.
Does anyone have any thoughts on how I should best approach this problem?
I made a similar sounding control which was actually used for inputting employee time shifts. It uses a simple data type in an ObservableCollection data bound to a ListBox.ItemsSource property. I defined a DataTemplate to design the look of each time segment in the UI. It looks like this:
There are certain ComboBoxes along the top which, when changed, change the number of items in the collection. So for example, when the Minutes/segment ComboBox value is changed to Thirty, twice the number of (same sized) items appear in the UI, each now relating to thirty minutes, not sixty. This is done by simply calling a method that repopulates the collection from the relevant property setters. Here is an example:
public TimeSegmentDivision MinutesPerSegment
{
get { return minutesPerSegment; }
set
{
minutesPerSegment = value;
InitializeTimeSegmentsRowViewModels();
NotifyPropertyChanged("MinutesPerSegment");
}
}
private void InitializeTimeSegmentsRowViewModels()
{
if (DayCount == 5) AdjustStartDate(); // (DayCount == 5 is 'Monday - Friday')
DateTime tempDate = Date;
AllTimeSegments = new TimeSegmentsCollection();
for (int m = 0; m < DayCount; m++)
{
TimeSegmentsRowViewModel viewModel = new TimeSegmentsRowViewModel();
viewModel.TimeSegments = InitializeTimeSegments();
AllTimeSegments.Add(viewModel);
date = Date.AddDays(1);
}
NotifyPropertyChanged("AllTimeSegments");
date = tempDate;
}
The specifics of the InitializeTimeSegmentsRowViewModels method is unimportant here, but you should be able to get the right idea from this example.
I worked on custom schedule for Silverlight. It has similar layout loading process so I would try to answer.
I guess your main problem lies in the 'time bar'... Stop thinking about schedule as the big consistent control, it's some pack of custom controls. Take a look on grid. Such controls has a lot peaces.
At start we have to solve first problem - time bar. Right implementation will lead you to painless future))) So time bar it's some control that contains total time, or duration of the employee labors(this value can be set by user). And such thing like step , exactly step and duration will tell you size and position of labors. For time bar control step will tell where are visual ticks should be displayed. I mean time bar will look like rulers, but with time values instead of inches. So we are getting next problem - how to translate time into pixels? Unfortunately I didn't found best solution - I assumed 1min is equal to 1.6 pixels It was perfect for me... But you will have dynamic value that will dynamically increase or decrease pixel length of step. So we have total duration in time, we can convert it into pixels length. But total length could be much bigger then available size. So, now we have another time bar property - visual duration...
okay, I guess you understand my way of thinking... Changing pixel size of step you will get perfect zoom(not step time size, but pixel length).
And yes, you are on the right way if you want create custom panels with overrated measure\arrange methods. And don't worry about item template. It's easiest part. Your parent schedule control will have items source property of object type. All your labors will be content controls....

WPF: Custom control layout

I'm working on a custom control in WPF that implements OnRender by calling a visit function with itself as the visitor. The control implements the visitor interface that draws lines, circles, etc. as appropriate. There are no child controls.
This all works, when the control renders I can see the primitives being rendered from my OnRender call.
However, what I'm struggling with is controlling the layout resulting from this. The Image control does exactly what I want to do. How do I replicate that behavior? Specifically:
If the user does not specify width or height, I want to set the rendered width/height of the control (either in my constructor or from another function that controls the layout).
If the user sets width or height to a specific value, I want to transform my drawing such that the aspect ratio of the control and drawing is preserved.
I'm trying to use the MeasureOverride functions to implement the behavior I want, but I'm not getting any results. My control is laid out with zero width/height, and then all my drawings get drawn on top of neighboring controls. Here is what I'm trying so far, hopefully this illustrates what I'm attempting to do:
protected override Size MeasureOverride(Size constraint)
{
SymbolLayout symbol = new SymbolLayout(this);
component.LayoutSymbol(symbol);
Point b1 = MapToPoint(symbol.LowerBound);
Point b2 = MapToPoint(symbol.UpperBound);
return new Size(b2.X - b1.X, b2.Y - b1.Y);
}
I'm not even sure that MeasureOverride is the right function to be using to accomplish this...
You also need to override the ArrangeOverride method. Layout in WPF is performed by a pair of recursive operations, Measure and Arrange. Measuring simply asks the visual tree what the required size of all the child controls is. This is done recursively, so if you had child controls, you would need to call Measure on all of your child elements as part of your Measure Override. Arranging is the second step in which the framework tells the control how much space it actually gets. Like Measuring, this is done recursively, and normally requires calling Arrange for each child control if there are any.

WPF Flow Layout

I have a ListBox with a horizontal WrapPanel inside to create a grid of content.
It produces the correct layout but it's really slow with greater than a hundred or so items.
I see a few half-baked attempts at virtualising a WrapPanel on google but none of them seem production ready.
Am I missing a trick?
How do I get performant and flexible (needs to reflow on resize) panel layouts?
(Note: cells are fixed size).
Yes, virtualizing is the correct step. WPF cant handle large amounts of UI items at once.
Writing your own VirtualizingWrapPanel takes quite a lot of experience in WPF. WPF has no "ready" solution, so you either have to write it, or use someone else work.
Unlike VirtualizingStackPanel which offers the same functionality of StackPanel, this does not fully behave as the default WrapPanel that comes with WPF, there are some reasons for this:
1- WrapPanels can grow in two directions, while StackPanels only grow
vertically or horizontally. Also, the growth in the direction
orthogonal to the panel's orientation depends on the size of the items
in the direction of the panel's orientation, so it is impossible to
wrap into a new line/column unless the size of all items on the last
line/column is known, but this is impossible if the two directions of
the panel are being virtualized(since it does non-pixel based
measurement for performance improvement).
2- The size of the extent is the same as the number of
lines/columns(sections). On the StackPanel this is not a problem:
Since only one item per section is allowed, the extent width/height is
the number of items. On the WrapPanel, the number of sections depends
highly on the size of items, and since the size of the items is not
fixed and can change in any direction, it is impossible to calculate
the number of sections on a WrapPanel.
The following solution was adopted when developing this panel:
1- The panel's extent is never known for sure. Instead, the number of
sections is estimated after each MeasureOverride using the following
expression : 'numberofitems/averageitemspersection'. Since the average
number of items per section is updated after each scrolling, the
extent of the panel is dynamic. The extent will be static if the items
size in the panels orientation is fixed.
2- The items section/sectionindex can only be calculated sequentially,
so if you are viewing the first items and you jump to a section that
bypasses one or more unrealized sections, the panel will use the
estimation to know which item is the first visible, this will be
corrected if you go back to a realized section and go back
sequentially. For example: if the panel knows that item 12 is in
section 1 and the panel estimates 10 items per section,(section 0 is
the first), if you jump to section 9 the panel will show the 100th
item as the first visible item(that will only be correct if from
section 1 to 9 there are exactly 10 items per section). But if you go
back to section 1 and access all the sections sequentially until you
reach section 9, then the panel will store all the items sections
correctly so no further estimations will be made up to section 10.
3- Normally a WrapPanel inside a scrollviewer would be allowed to
scroll vertically and horizontally, but since I can only virtualize
one direction, this wrappanel will only scroll in the direction
orthogonal to the panels orientation.(Meaning you shouldnt set the
Virtualizing Panel Height/Width explicitly).
Copied from; http://virtualwrappanel.codeplex.com/
I didn't understand what you meant by fixed cells, but if you have fixed width / height per item, you could write better virtualization than the one I provided(VirtualWrapPanel). Just browse through code and try to understand what is going on.
What you call "doesnt quite work" is probably because it's not possible to make nice VirtualizingWrapPanel that would work exactly like the one WrapPanel(due to fact that the specific row of items depends on the next items and so on. ), but if you combine it with the fact that each item has the same width/height, I believe you could do better.
Also, you can get started by writing your own solution, based on these articles;
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
How about try to use Grid and add the item to Grid such as :
( But I didn't test its performance)
public void InitializeGridAsFourColumns()
{
for (int i = 0; i < 4; i++)
{
_customGrid.ColumnDefinitions.Add(new ColumnDefinition());
}
}
private static int row = -1;
private static int column = 0;
public void AddItem(item)
{
//Add new RowDefinition every 4 items
if (column % 4 == 0)
{
RowDefinition rd = new RowDefinition();
rd.Height = GridLength.Auto;
_customGrid.RowDefinitions.Add(rd);
row++;
}
item.SetValue(Grid.RowProperty, row);
item.SetValue(Grid.ColumnProperty, column % 4);
column++;
_customGrid.Children.Add(item);
}

How to smooth WPF animation?

I am struggling in smoothing WPF animation
Actually my animation code is as follows:
private void AnimateX ( FrameworkElement element, double XMoveStart, double XMoveEnd, int secondX)
{
SineEase eEase = new SineEase();
eEase.EasingMode = EasingMode.EaseInOut;
Storyboard sb = new Storyboard();
DoubleAnimation daX = new DoubleAnimation(XMoveStart, XMoveEnd, new Duration(new TimeSpan(0, 0, 0, secondX, 0)));
daX.EasingFunction = eEase;
Storyboard.SetTargetProperty(daX, new PropertyPath("(Canvas.Left)"));
sb.Children.Add(daX);
element.BeginStoryboard(sb);
}
The above code is a method to move an object horizontally with sine ease. When only one object is moving, it is OK. However, whenever two or more objects move together (call AnimateX method on another object when the previous animation has not yet completed), the animation starts to become jittery. By jittery I mean, the objects are kind of shaking during the course of animation.
I faced the same problem many times. I found out that depending on the objects you add to your canvas, WPF will often have to regenerate representations of these objects on every frame (which I believe might be your case, depending on the type of UI elements you are manipulating). You can solve the jitter issue by telling WPF to cache a representation of your canvas in a bitmap. This is done very simply as follows, in your Xaml definition of the canvas:
<Canvas ...Your canvas properties...>
<Canvas.CacheMode>
<BitmapCache />
</Canvas.CacheMode>
...Your objects...
</Canvas>`
This reduces the load on your WPF application, as it simply stores the representation of your objects as a bitmap image, and as a consequence your application does not have to redraw them on every frame. This solution only works if your animation is applied externally to the canvas, and that there is no on-going local animations applying to the individual objects drawn in your canvas. You'll want to create separates canvases with their own caching if other animations in your code move the two objects with respect to each other.
Note that some UI elements will not be eased by this strategy. However, I've seen this strategy work efficiently for many elements, including TextBoxes and the likes, as well as geometric shapes. In any case, it's always worth the try.
Secondly, if caching local representations does not suffice, then you might want to have a look at the performance of your code and see if any process could be responsible for blocking the UI momentarily. There is no uniform solution regarding this aspect and it depends on what else is putting strain on your application UI. Cleaning the code and using asynchronous processes where relevant could help.
Finally, if, after all these checks the overall demand on your application remains too high, you can somewhat remove some strain on the application by reducing its general frame rate, the default being 60. You can try 30 or 40 and see if this improves the jittering by including the following code in your initialization:
Timeline.DesiredFrameRateProperty.OverrideMetadata(typeof(Timeline), new FrameworkPropertyMetadata { DefaultValue = 40 });
Just a guess, but what happens if you directly animate the property, withoud using a Storyboard?
private void AnimateX(FrameworkElement element, double xMoveStart, double xMoveEnd, double durationSeconds)
{
DoubleAnimation animation = new DoubleAnimation
{
From = xMoveStart,
To = xMoveEnd,
Duration = TimeSpan.FromSeconds(durationSeconds),
EasingFunction = new SineEase { EasingMode = EasingMode.EaseInOut }
};
element.BeginAnimation(Canvas.LeftProperty, animation);
}

Winforms Usercontrol: Perfomance issues when creating and adding to parent

I have built a UserControl for display a list of other UserControls which themselves are bound to individual data objects. The link below shows an example implementation of this control.
Each of the individual user rows is its own UserControl/XtraUserControl, laid out in a FlowLayoutPanel.
The problem I have is with perfomance, to populate the list above takes around 500ms (excluding any data loading) - this is a combination of creating each control and then adding them to the FlowLayoutPanel using the AddRange(controls[]) method.
Does anyone know any way I can improve perfomance here? Do I have to manually paint the items instead of using User Controls?
Thanks in advance.
EDIT: I've added my own response below showing the solution I have stuck with for now.
Whether manually painting would help is a guess. Even if it were right (which I doubt) it's better not to guess.
I've seen this kind of issue before, and chances are there's a lot of stuff that goes on in the binding.
The way I've solved the problem is with this approach, but it's definitely "out there" in terms of programmer acceptance.
I gues you are using devexpress controls because you mention XtraUserControl. If so, why don't use an XtraGrid?You can add images column and button columns, and I think you'll get better performance and simpler/less code
First of all, try use pair SuspendLayout()/ResumeLayout(), then it has sense to stop painting at all by hiding the container control until all child usercontrols added.
Anyway, placing lots of child controls to a container is not a good idea.
You can have the same result by using highly customized grid or by custom painting (which is preferable).
Good luck!
I had a brainwave for another solution which I'm not quite sure is appropriate. I would really appreciate any feedback on this.
Two rationales led to this solution:
Firstly I wanted the flexibility of creating rows like any other control.
Secondly the lists that would use this approach only intend to display brief chunks of data, never more than say 20 items - for anything larger, ListViews are used.
So anyway, what I decided to do was cache a set number of the Panels (I've referred to the custom controls or rows as Panels throughout the code) and to build up this cache as the control is created. When populating the control with BusinessObjects, the existing cached Panels are displayed with their bound BusinessObject. You can see how this works exactly from the code below, so there is no need for a in-depth description.
The fact of the matter is that I've managed to reduce the data population time (after the initial cache setup of around 180ms for 10 Panels) from 500ms to 150ms for the list shown in the image above.
private int cacheSize = 10;
private List<P> cachedPanels = new List<P>(10);
private void InitItems()
{
this.contentPanel.SuspendLayout();
// Create the cached panels from the default cache value.
for (int i = 0; i < cacheSize; i++)
cachedPanels.Add(new P() { Margin = new Padding(0), Visible = false });
this.contentPanel.Controls.AddRange(cachedPanels.ToArray());
this.contentPanel.ResumeLayout(true);
}
private void PopulateListFromCache()
{
this.contentPanel.SuspendLayout();
// Iterate against both BusinessObjects and Panels to ensure that nothing is missed, for
// instance, where there are too many panels, the rest are hidden, and too many Business
// Objects, then more Panels are created.
for (int i = 0; i < this.businessObjects.Count || i < this.cachedPanels.Count; i++)
{
if (i >= this.cachedPanels.Count)
{
// Here, we have more BusinessObjects than Panels, thus we must create
// and assign a new panel.
this.cachedPanels.Add(new P() { Margin = new Padding(0) });
this.cachedPanels[i].Item = this.businessObjects[i];
this.contentPanel.Controls.Add(this.cachedPanels[i]);
}
else if (i >= this.businessObjects.Count)
{
// Here, we still have Panels cached but have run out of BusinessObjects,
// let's just hide them and clear their bindings.
this.cachedPanels[i].Item = default(T);
this.cachedPanels[i].Visible = false;
}
else
{
// Here, we have both BusinessObjects and Panels to put them in, so just
// update the binding and ensure the Panel is visible.
this.cachedPanels[i].Item = this.businessObjects[i];
this.cachedPanels[i].Visible = true;
}
}
this.contentPanel.ResumeLayout(true);
}
Obviously, more optimizations can be made, such as un-caching Panels after a certain amount of time of not being used etc. Also, I'm not entirely sure if keeping these controls - which are rather simple - in a cache will affect memory usage much.
If anyone can think of any other pointers then please, be my guest. Oh, and if you got this far, then thank you for reading this.

Resources