Odd DataGridView vertical scrolling behavior - winforms

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 :)

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);

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);
}

Dock layout issues in WinForms + Devexpress

I have a WinForms dialog with a lot of DevExpress elements on it.
The form has few SplitContainers and a TextEdit element and DevExpress XtraGrid controls.
All of the controls are Dock=Fill or, in the case of the toolbars, Dock=Top.
When I resize the form it doesn't relayout all the elements, with these results: http://dl.dropbox.com/u/51344/winforms1.png.
It's not until I resize one of the SplitContainers manually that the form relayouts and everything is ok again ( http://dl.dropbox.com/u/51344/winforms2.png ).
I've tried to invoke all of the "PerformLayout, Refresh, Redraw" function I could find in the OnResize event of the form with no luck.
The problem results in even worse layout issues when running in RemoteDesktop.
Does anyone have an idea how to debug this issue?
Edit:
I hate to answer my own question, been battling with this on and off for few days.
This ugly piece of code seems to do the trick. Resize the splitcontainers by one pixel to force a re-layout:
splitContainerControlQuery1.SplitterPosition += (splitContainerControlQuery1.SplitterPosition % 2 == 1) ? 1 : -1;
splitContainerControlQuery2.SplitterPosition += (splitContainerControlQuery2.SplitterPosition % 2 == 1) ? 1 : -1;

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.

Why don't my scrollbars work properly when programmatically hiding rows in silverlight Datagrid?

I have a Silverlight datagrid with custom code that allows for +/- buttons on the lefthand side and can display a table with a tree structure. The +/- buttons are bound to a IsExpanded property on my ViewModelRows, as I call them. The visibility of rows is bound to an IsVisible property on the ViewModelRows which is determined based on whether or not all of the parent rows are expanded. Straightforward enough.
This code works fine in that if I scroll up and down the grid with PageUp/PageDown or the arrow keys, all the right rows are hidden and everything has the right structure and I can play with the +/- buttons to my hearts content. However, the vertical scroll bar on the right hand side, although it starts off the correct size and it scrolls through the rows smoothly, when I collapse rows and then re-expand them, doesn't go back to its correct size. The scrollbar can still usually be moved around to scroll through the whole collection, but because it is too big, once the bar moves to the bottom, there are still more rows to go through and it sort of jerkily shoots all the way down to the bottom or sometimes fails to scroll at all. This is pretty hard to describe so I included a screenshot with the black lines drawn on to show the difference in scrollbar length even though the two grids have the same number of rows expanded.
I think this might be a bug related to the way the Datagrid does virtualization of rows. It seems to me like it isn't properly keeping track of how tall each row is supposed to be when expansion states change. Is there a way to programmatically "poke" (read hack) it to recalculate its scrollbar size on LoadingRow or something ugly like that? I'd include a code sample but there's 2 c# files and 1 xaml file so I wanted to see if anyone else has heard of this sort of issue before I try to make it reproducible in a self-contained way. Once again, scrolling with the arrow keys works fine so I'm pretty sure the underlying logic and binding is working, there's just some issue with the row height not being calculated properly.
Since I'm a new user, it won't let me use image tags so here's the link to a picture of the problem:
http://img210.imageshack.us/img210/8760/messedupscrollbars.png
This is a known issue according to what I read today (and is apparently still an issue in the as-yet-unreleased Silverlight 4):
http://forums.silverlight.net/forums/p/153997/343790.aspx

Resources