Winforms Usercontrol: Perfomance issues when creating and adding to parent - winforms

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.

Related

SuspendLayouts, resumeLayouts and grid columns

I use these two functions suspendLayouts and resumeLayouts in order to optimize large grid rendering. In particular, I use them just before and after I hide programmatically some grid columns. I do it like this:
Ext.suspendLayouts();
... a lot of code, some code may hide a lot of grid columns
Ext.resumeLayouts();
If I do not use them, browser either does not respond or responds with an alert about a long lasting script. If I use them, then it runs quite quickly, but not in all browsers. For example, in Chrome I see some columns do not get hidden, while their column titles/headers hide.
In small grids with a relatively small amount of columns, this code without suspending and resuming layouts works nice. But I need it to work in large grids.
By the way, I hide columns like this
var cols=grid.headerCt.getGridColumns()
Ext.each(cols, function (item, index, all){
... some code
if(ok) item.setVisible(false);
})
Probably, there is another better way of hiding grid columns.
Well, I found a solution. I should have used grid.suspendLayouts() and grid.resumeLayouts() instead of Ext.suspendLayouts() and Ext.resumeLayouts(). Now, it works as expected.
Ext.suspendLayouts() didn't worked for me. Grid is still very slowly redrawn.
I used grid.reconfigure():
Ext.each(grid.initialConfig.columns, function (columnCo, index, all){
... some code
if(ok) columnConfig.hidden = false;
})
grid.reconfigure(grid.store, grid.initialConfig.columns);

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....

Paged data in a WPF Grid control

I have a Rest service that returns the data in pages. I know how many pages of data there is after getting the first result set. Now I want to consume this service in a WPF application, e.g. display the result in a Grid Control (or a list view).
The problem is that the whole paging mechanism should be transparent to the end user, so they shouldn't trigger data fetching by any means other than scrolling in the grid. Is this possible and how would you tackle this problem?
Here is another possible solution of your task: http://www.devzest.com/blog/post/wpf-data-virtualization.aspx
Main idea is to create your own implementation of IList which will encapsulate all async page loading features.
As a bonus that article contains full example code with a set of additional features:
Selection, sorting and filtering works well as if all data are stored locally;
Data loading as needed, in a separate thread, without blocking the UI;
Visual feedback when data is loading; if failed, user can retry the last failed attempt.
Put your grid or list into the ScrollViewer, subscribe to the ScrollChanged event, then use event args properties to determine if you are close enough to the end of scrollable area, and request next page from your service, and finally add received data to the end of list or grid.
That's in short. If you need more concrete example, let me know.
EDIT: Okay, assuming you're using the System.Windows.Controls.DataGrid control to display your data. I'm making this assumption because you've said your grid does have the scrolling capabilities built in, and no other control with name similar to grid does have it. And also because it makes sense to use DataGrid for data display. :)
You declare your DataGrid like this:
<DataGrid HorizontalAlignment="Left" Margin="20,10,0,0"
VerticalAlignment="Top" Height="301" Width="498"
ScrollViewer.ScrollChanged="DataGrid_ScrollChanged_1"
ItemsSource="{x:Static Fonts.SystemFontFamilies}">
</DataGrid>
Notice that I'm using ScrollViewer.ScrollChanged routed event. This is possible because DataGrid indeed has the ScrollViewer built in. This means it's possible to subscribe to that event and analyze it's arguments.
Here's how I handle this event for testing purposes:
private void DataGrid_ScrollChanged_1(object sender, ScrollChangedEventArgs e)
{
Debug.WriteLine("Extent height: " + e.ExtentHeight +
", vertical offset: " + e.VerticalOffset +
", viewport height: " + e.ViewportHeight);
}
When my datagrid is scrolled to the top, I see the following output:
Extent height: 267, vertical offset: 0, viewport height: 13
When it's scrolled to the bottom:
Extent height: 267, vertical offset: 254, viewport height: 13
So it's quite easy to determine when you're close to the bottom and act accordingly:
const int threshold = 20;
if (e.ExtentHeight <= e.VerticalOffset + e.ViewportHeight + threshold)
{
AskForNextPage();
}
Of course, there are some nuances here. You need to keep track if you've already downloading some page, and how many pages you've already loaded, to avoid data duplication and other inconsistencies. And, honestly speaking, this will be the hardest part to do, compared to what I've written here. :)
I thought the problem was interesting but the answer is too long for stackoverflow window so I built a simple app that uses a prefetching collection view.
It's sort of similar approach as the one posted by Woodman.
https://github.com/mrange/CodeStack/tree/master/q14793759/AutoFetching
The interesting code is in the class: PrefetchingCollectionView

WPF Textblock Performance Poor

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.

Odd DataGridView vertical scrolling behavior

I have a windows forms DataGridView. That I fill and update using the code below, all pretty straightforward and it is all done on the UI thread.
For some strange reason sometimes the size of the vertical scrollbar (wich I set to be only visible when needed) does not reflect the amount rows available. If I scroll all the way down, I still cannot see the very last rows. I can tell by selecting the lines below (and bringing them into view) by using the arrow down key.
What could possibly be a reason for this. Do I need some sort of BeginUdate or SuspendLayout or something? The control is embedded through interop in an MFC application.
Andy idea how to track down this problem? Is this a known bug? Google doesn't think so, it seems.
Here is the code I use.
adding or inserting a row:
int newRowIndex = insertAt;
if (insertAt < 0 || insertAt > this.dataGridView.Rows.Count)
{
newRowIndex = this.dataGridView.Rows.Add();
}
else
{
this.dataGridView.Rows.Insert(insertAt, 1);
}
removing a row:
this.dataGridView.Rows.Remove(index);
clearing:
this.dataGridView.Rows.Clear();
updating a row:
this.dataGrid[0, rowIndex].Value = someString;
this.dataGrid[1, rowIndex].Value = someBool;
this.dataGrid[2, rowIndex].Value = someInt;
I had the same problem and found that when I set the DataGridView's scrollbar property in code as opposed to the designer, it worked just fine. So I just had something along these lines:
foreach(Listitem item in list)
{
//populate grid
}
dataGridView.ScrollBars = ScrollBars.Both;
Have no idea why it worked however :)

Resources