How update the grid based on the BindingList<T> with several rows simultaneously?
-
Hello.
Thank you for this "strange" question. Indeed, removing and adding from/to a binding list is not the best way to update the content, especially in the beginning of the container because it is very expensive operation.
Instead of addind/removing the data it will be better to implement INotifyPropertyChanged interface in the QLevelII:public class QLevelII : INotifyPropertyChanged
{
private decimal _price;
public long AskQty { ... }
public long BidQty { ... }
public decimal Price
{
get { return _price; }
set
{
if (_price != value)
{
_price = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Price"));
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}In this case the BindingList<> subscribes to notifications from QLevelII and will forward them to the grid via ListChanged event with specified PropertyDescriptor. Therefore your code will look like as following :
BindingList<QLevelII> LevelII = ...;
LevelII[i].Price = levels.rows[i].Price;Best regards,
The Dapfor Team
0 -
Hi Team!
Thank you for your replay.
Either I have not understand your answer or you haven't understand my question at full range...
So,
Your answer is great for updating the data horizontally. I have THE row and using the INotifyPropertyChanged we update the separate fields.
I do it because it is the best way to get the result without blinking in the SAME row.
But in my case we have the data (Level II table) which are updated by vertically! Let my describe the problem.
Each time, the exchange send to the client packet of data for Level II table.
The packet can be describer like this:
Price Qty
-------- -------
100 5
102 2
106 23
107 5
110 54
120 3
122 4
125 6
The packet above describe the initial state of the table.
Take the look on the following (the second) package:
Price Qty
--------- -------
100 17
104 21
122 0
The second packet means that we should UPDATE the row with the price= 100, INSERT the row with the price= 104 and qty= 21, DELETE the row with the price 122
Each packet may contains 50-100 rows with different operations such as UPDATE, INSERT or DELETE.
I hope that I am clear in my descriptions at this point... +)
Next, now we have the following: the grid with the DataSource as BindingList<QLevelII>.
The class QLevelII is below:
public class QLevelII : INotifyPropertyChanged
{
#region Fields
private double _price;
private long _askquantity;
private long _bidquantity;
#endregion
public QLevelII()
{
}
//[DoubleFormat(4)]
public double Price
{
get { return _price; }
set {
if( _price != value ) {
_price = value;
FirePropertyChanged("Price");
}
}
...
}
When new packed was arrived by network we should process the data row by row. If the packed contains several INSERT operation and several DELETE operations we will see very disagreeable redrawing of the grid!
The Delphi has its own grid control which have 2 very useful methods: BeginUpdate() and EndUpdate().
That methods allow freeze the control(grid) during performing some of the complicated operations.
In other word we can do the following:
try {
BeginUpdate()
// process the packet and update the internal array of the quote here
// performing the INSERT and DELETE at the array of the quote here
// as result we will have tha array with 40 rows
}
finally {
EndUpdate();
}
After processing the next packed (with INS, DEL and UPD operations) we will see the array with 40 rows AGAIN.
And after each packet we will have the array with 40 rows.
But, as I sad already, the current version of your grid will reflect each operation on the internal array!!!
As the result the user will see the grip with 42 rows, or 44 rows. It will be shown by the grid on the sort time, that is looks like "blinking"
How I can perform the INSERTION of new data or DELETING the old data from the Level II table without reflection of the interim operations?
(without nose of the redrawing)
Thank you!
BR,
dx20030 -
I have made small video which showing the issues.
level2.03.01.7z0 -
Hi,
Now we understand better your needs. We will add similar methods to prevent from vertical scrollbar appearing/disappearing while adding/removing a packet of rows. At the moment you can try the next: Process all DELETE operations and then INSERT and UPDATE ones. Therefore the number of rows will be always <= 40. We will do our best to release the version with required functionality as soon as possible.
Best regards,
The Dapfor Team
0 -
Thank you Team.
I think it will be one of the interesting additional to your grid.
We are waiting for the new release very, very much.
Thank you again!
0 -
Thanks,
The version 2.6.1 is now available. It should fix the problem.
Kind regards,
The Dapfor Team
0 -
Hi Team!
Very huge step on the way of improving your control!
In current realization of BeginUpdate()/EndUpdate() methods you solve the described problem in case of changing the data(BindingList<T>) within the GUI thread only.But in case of processing the data from non-GUI thread, including the invoke BeginUpdate()/EndUpdate(), we still have the problem... +(
Just invoking the EndUpdate() from the non-GUI thread generate the error now:
A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
Additional information: Cross-thread operation not valid: Control 'grid' accessed from a thread other than the thread it was created on.
I am not sure but it seams to be rose due to the following part of your the code(which is executed from non-GUI thread):public void SetVerticalScroll(...)
...
SafeNativeMethods.SetScrollInfo(this.grid.Handle, 1, ref scrollinfo, redraw);
...
Sorry +(
More other, in case of using the delegate like this:
...
private delegate void DelegateGridEndUpdate();
private void GridEndUpdate()
{
if( !grid.IsDisposed ) {
// invokes from non-GUI thread
grid.EndUpdate();
}
}
...
// invokes from the GUI thread....
this.Invoke(new DelegateGridEndUpdate(GridEndUpdate));
...
We get the situation where ALL insertion or deleting reflects on the grid at full range +(
I can provide you with full code of the test application which remove and insert a various count of the rows in both GUI thread or non-GUI thread.
Thank you.0 -
Thank you for this post.
This is normal, because BeginUpdate/EndUpdate are not designed to be called from non-GUI thread. Indeed, if we talk about separation business logic from GUI, the logic should know nothing about how it will be presented (even in which thread). While binding, we know both the logic & GUI, but when the logic sends notifications via INotifyPropertyChanged/IBindingList it knows nothing about graphical controls and therefore may have no references to the grid (ideally of course!). But when you are using BeginUpdate/EndUpdate, these methods are not covered by standard Microsoft interfaces like INotifyPropertyChanged and you have to add references to the grid into the business logic. Since you have references to the grid, to be safe you can call the following:
grid.Invoke(new MethodInvoker(grid.BeginUpdate))
...
grid.Invoke(new MethodInvoker(grid.EndUpdate))In the future version we will add thread-safe protection to these methods which will be based on principles, described above.
Some remarks about Control.Invoke call: For performance reasons don’t use your custom delegates in the invoke method, because they are executed by reflection (look at Control.InvokeMarshaledCallbackDo method which is called to execute a delegate). The right way is to use a standard delegate MethodInvoker. If you need to call a delegate with multiple parameters, use lambda/anonymous methods inside of MethodInvoker.
Best regards,
Dapfor
0 -
Hi Team.
Thank you for your explanations.
Just for my understanding:
is it possible to update the grid's data thought IBindingList object and use BeginUpdate/EndUpdate methods as you described above from non-GUI thread?
Will the grid (v2.6.1) redraw without the artifacts (interim ins and del operations) in that case?Thank you!
BR,
dx20030 -
Hello,
Yes, you can. The approach described above is thread-safe. Normally, you shouldn’t have artifacts. But you have to note: Graphical thread works in parallel to the non-GUI thread and BeginUpdate/EndUpdate are not atomic. These methods lock only scrollbar’s updates (they are painted in WM_NCPAINT messages – not the classic painting routine). Also you have to note that Windows doesn’t send WM_PAINT immediately after calling the Control.Invalidate method but about 15 msec later. Therefore the time between BeginUpdate and EndUpdate calls should be less than 15 msec (Windows XP/Vista/7). It's not a big problem if the grid repaints the content before EndUpdate(). It will repaint the content again when the EndUpdate() is called.
Hope these explanations will help you,
Best regards,
Dapfor
0 -
Hi Team,
Okay, the current realization of BeginUpdate/EndUpdate methods influence on scrolbars only...
Do we have the chance to see the realization of the BeginUpdate/EndUpdate methods as it was done in the ListBox control (System.Windows.Forms.ListBox )?
Please, see the more detail on the following link:
http://msdn.microsoft.com/en-us/library/system.windows.forms.listbox.beginupdate.aspxThank you.
BR,
dx20030 -
Hello,
In the GUI thread Dapfor .Net Grid has exactly the same behavior. Nevertheless it seems that our explanations are not very clean.
The main problem with grid/list components is not the content drawing, but vertical scrollbars! Did you notice when you add a huge count of items to Microsoft’s grid, the content is not drawn, but the vertical scrollbars does? It happens because scrollbars aren’t drawn in the classic asynchronous way Invalidate()/WM_PAINT routine. When the method SetScrollBarInfo is called – Windows repaints them synchronously. This slows down the application. BeginUpdate/EndUpdate methods prevent a grid from scrollbar updating (this is not well documented in MSDN) and when scrollbars are blocked, row adding happens much faster. The price for that is when the EndUpdate() is called, the grid redraws both – the whole content (via Invalidate()) and scrollbars. Of course this call is very expensive and can’t be done at each row adding.
Now about non-gui thread. As we’ve noticed, BeginUpdate()/EndUpdate() are not atomic. Nevertheless if you call them consecutively in the same message handler, WM_PAINT can’t come between these two calls (because you are already in the GUI thread). So if you change your code to following, you won’t get artifacts.
grid.Invoke(new MethodInvoker(delegate
{
grid.BeginUpdate();
//your code here...
grid.EndUpdate();
}));Best regards,
Dapfor
0 -
Hi Team!
OMG!!!!! It seams to be worked!!!!
The code from your previous answer looks to be worked without any artifacts at all!!!
I can't believe that....................... +)
I will retest it 100000 times but now I would like to thank you very much!Thank you again!!!
BR,
dx20030
Please sign in to leave a comment.
Comments
13 comments