ListBox does not update when DataSource is changed
-
Wow! You have such a myopic view of what's going on. The reason you need Invoke is because controls are NOT thread safe. What you see in the toolbox are .NET wrappers around standard window variants you find in Win32. They are not thread safe because of limitations in the Windows user interface. The golden rule is that any operation on a control is required to be done by the thread that created the window handle. It's possible to wrap all this code in synchronzation code, but that would have been a ton of code and a rather large performance hit. But, but, but "I'm just changing a property!" Yeah, I know. A property is nothing but a method that gets/sets a value, but that method can also do other things like validation, modifying other values inside the control, updating the controls internal state machine, kicking off events, and whatever else the controls need. What you're forgetting is that Windows is a shared system. Not only can your code cause code in the contorl to run, but Windows can too, AT ANY TIME. It can send your control a WM_PAINT message to get your control to repaint itself, but, what if you were changing the Text or Forecolor properties, from your thread, at the exact same time the control was getting these values to use in the paint code?? Invoke is there because .NET, like any other application, has to follow the "UI thread rule" like any other application. A little tidbit: The only methods on controls that ARE thread safe are Invoke, BeginInvoke, EndInvoke and CreateGraphics.
A guide to posting questions on CodeProject[^]
Dave KreskowiakWow! You have such a myopic view of what's going on.
I don't think so. Let's revisit this one more time. So I'm supposed to put all code that modifies a control into wrapper code that calls invoke and this wrapper code ends up being a conditional test to see if invoke is required and then recursively calling the same function thru a special delegate that is created just for that invoke call. I've repeated my example code below:
private delegate void RefreshLbxDg8();
private RefreshLbxDg8 _refreshLbx;
...
_refreshLbx = RefreshLbx;
...
public void RefreshLbx()
{
if (lbxQuotes.InvokeRequired)
{
lbxQuotes.Invoke(_refreshLbx);
}
else
{
lbxQuotes.DataSource = null;
lbxQuotes.DataSource = _quotes;
}
}All this is required in order that the code gets executed on the GUI thread. Now, I'm simply updating a textbox with a sample (number). Nothing else. I guess I could have stated my point better: yes, it is an asynchronous system and it is *possible* that a WM_PAINT would get processed while the textbox text is changing. I am aware of this. My complaint was that .NET designers used this sledgehammer solution to prevent any presentation rendering mishap (ie. my number is rendered while it is changing) by constraining all code to execute on the GUI thread. So this means that for every single interaction with any control - and there's a LOT of them, I'm supposed to write wrapper code around everything, and this wrapper code involves defining and creating delegates and rewriting all my code to include a conditional statement which tests for this invokability. AND YOU'RE FINE WITH THIS. Me, I think this is a horrible and terribly primitive solution and design. It would be better to leave me with using SendMessage() to inject my action into the GUI's Message-Loop. And that's not to mention that the compiler gave no warnings, there was no runtime error, the code SILENTLY failed, and I had no idea that the timer implemented its actions with a (hidden) separate thread. I am LESS than impressed.
-
Wow! You have such a myopic view of what's going on.
I don't think so. Let's revisit this one more time. So I'm supposed to put all code that modifies a control into wrapper code that calls invoke and this wrapper code ends up being a conditional test to see if invoke is required and then recursively calling the same function thru a special delegate that is created just for that invoke call. I've repeated my example code below:
private delegate void RefreshLbxDg8();
private RefreshLbxDg8 _refreshLbx;
...
_refreshLbx = RefreshLbx;
...
public void RefreshLbx()
{
if (lbxQuotes.InvokeRequired)
{
lbxQuotes.Invoke(_refreshLbx);
}
else
{
lbxQuotes.DataSource = null;
lbxQuotes.DataSource = _quotes;
}
}All this is required in order that the code gets executed on the GUI thread. Now, I'm simply updating a textbox with a sample (number). Nothing else. I guess I could have stated my point better: yes, it is an asynchronous system and it is *possible* that a WM_PAINT would get processed while the textbox text is changing. I am aware of this. My complaint was that .NET designers used this sledgehammer solution to prevent any presentation rendering mishap (ie. my number is rendered while it is changing) by constraining all code to execute on the GUI thread. So this means that for every single interaction with any control - and there's a LOT of them, I'm supposed to write wrapper code around everything, and this wrapper code involves defining and creating delegates and rewriting all my code to include a conditional statement which tests for this invokability. AND YOU'RE FINE WITH THIS. Me, I think this is a horrible and terribly primitive solution and design. It would be better to leave me with using SendMessage() to inject my action into the GUI's Message-Loop. And that's not to mention that the compiler gave no warnings, there was no runtime error, the code SILENTLY failed, and I had no idea that the timer implemented its actions with a (hidden) separate thread. I am LESS than impressed.
Liek I said, it's a limitation in Windows Win32 since the dark arges, not .NET. If this bugs the crap out of you, go write code for Linux.
A guide to posting questions on CodeProject[^]
Dave Kreskowiak -
Liek I said, it's a limitation in Windows Win32 since the dark arges, not .NET. If this bugs the crap out of you, go write code for Linux.
A guide to posting questions on CodeProject[^]
Dave Kreskowiak -
A SendMessage() call works between threads... at least I thought it did. Thus I thought this was a .NET constraint. If it's not a .NET new constraint then I just learned something.
Dave Kreskowiak wrote:
go write code for Linux.
No! (so there)
rbsbscrp wrote:
No! (so there)
Then quit bitching...
A guide to posting questions on CodeProject[^]
Dave Kreskowiak -
rbsbscrp wrote:
No! (so there)
Then quit bitching...
A guide to posting questions on CodeProject[^]
Dave Kreskowiak -
Wow! You have such a myopic view of what's going on.
I don't think so. Let's revisit this one more time. So I'm supposed to put all code that modifies a control into wrapper code that calls invoke and this wrapper code ends up being a conditional test to see if invoke is required and then recursively calling the same function thru a special delegate that is created just for that invoke call. I've repeated my example code below:
private delegate void RefreshLbxDg8();
private RefreshLbxDg8 _refreshLbx;
...
_refreshLbx = RefreshLbx;
...
public void RefreshLbx()
{
if (lbxQuotes.InvokeRequired)
{
lbxQuotes.Invoke(_refreshLbx);
}
else
{
lbxQuotes.DataSource = null;
lbxQuotes.DataSource = _quotes;
}
}All this is required in order that the code gets executed on the GUI thread. Now, I'm simply updating a textbox with a sample (number). Nothing else. I guess I could have stated my point better: yes, it is an asynchronous system and it is *possible* that a WM_PAINT would get processed while the textbox text is changing. I am aware of this. My complaint was that .NET designers used this sledgehammer solution to prevent any presentation rendering mishap (ie. my number is rendered while it is changing) by constraining all code to execute on the GUI thread. So this means that for every single interaction with any control - and there's a LOT of them, I'm supposed to write wrapper code around everything, and this wrapper code involves defining and creating delegates and rewriting all my code to include a conditional statement which tests for this invokability. AND YOU'RE FINE WITH THIS. Me, I think this is a horrible and terribly primitive solution and design. It would be better to leave me with using SendMessage() to inject my action into the GUI's Message-Loop. And that's not to mention that the compiler gave no warnings, there was no runtime error, the code SILENTLY failed, and I had no idea that the timer implemented its actions with a (hidden) separate thread. I am LESS than impressed.
rbsbscrp wrote:
Me, I think this is a horrible and terribly primitive solution and design.
Microsoft had the same idea; that's why the threaded version of the timer isn't in the WinForms designer-toolbox. The Timer from the toolbox simply raises events on the GUI-thread. No syncing required.
Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]
-
rbsbscrp wrote:
Me, I think this is a horrible and terribly primitive solution and design.
Microsoft had the same idea; that's why the threaded version of the timer isn't in the WinForms designer-toolbox. The Timer from the toolbox simply raises events on the GUI-thread. No syncing required.
Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]
Hmmmmm... I am using a System.Windows.Forms.Timer. Isn't that the one you are referring to that doesn't use a separate thread? Okay, now I'm confused. My code works if i execute it from a button. It works if I invoke it thru a delegate. Other than that nothing happens.
-
Hmmmmm... I am using a System.Windows.Forms.Timer. Isn't that the one you are referring to that doesn't use a separate thread? Okay, now I'm confused. My code works if i execute it from a button. It works if I invoke it thru a delegate. Other than that nothing happens.
Let me add to the confusion; there's not two, but three[^] timers available. So, which timer is it? :) ..and there's a picture in that article of a second timer in the toolbox. Nice to see my previous post invalidated that quickly.
Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]
-
Let me add to the confusion; there's not two, but three[^] timers available. So, which timer is it? :) ..and there's a picture in that article of a second timer in the toolbox. Nice to see my previous post invalidated that quickly.
Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]
-
Wow, wonderful article. Again, the timer that I'm using is the System.Windows.Forms.Timer one (I'm cut&pasting this from the .Designer.cs file - and I don't see any other timers) Thx for the article. I'll look at all this in a bit.
rbsbscrp wrote:
the timer that I'm using is the System.Windows.Forms.Timer one
That one runs on the UI thread, and doesn't need any invocations to update controls created.
using System;
using System.Windows.Forms;
using System.Threading;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Thread.CurrentThread.Name = "UI Thread";
}private void timer1\_Tick(object sender, EventArgs e) { Text = String.Format("{0}, {1}", Thread.CurrentThread.Name, Environment.TickCount); // Thread.Sleep(1000); Try resizing after uncommenting this } }
}
The timer is based on the Windows message-pump;
// System.Windows.Forms.Timer.TimerNativeWindow
protected override void WndProc(ref Message m)
{
if (m.Msg == 275)
{
if ((int)m.WParam == this._timerID)
{
this._owner.OnTick(EventArgs.Empty);
return;
}
}
else
{
if (m.Msg == 16)
{
this.StopTimer(true, m.HWnd);
return;
}
}
base.WndProc(ref m);
}Bastard Programmer from Hell :suss: If you can't read my code, try converting it here[^]
-
Wow but this is weird. Okay, I found the solution because I tried the recommendation given below:
lbxQuotes.DataSource = null;
lbxQuotes.DataSource = _quotes;
lbxQuotes.SelectionMode = SelectionMode.None;
lbxQuotes.SelectionMode = SelectionMode.One;That is, I added the SelectionMode two lines... and this caused (only once) a "from different thread" exception. Weird, I'm NOT running a method - I'm changing data... hmmm... I wonder if the DataSource is instead a Property (which secretly runs a method). D'oh! Okay, so the solution was to create a delegate in the main form, for the RefreshLbx method, and have the RefreshLbx method recursively call this, as below:
public void RefreshLbx()
{
if (lbxQuotes.InvokeRequired)
{
lbxQuotes.Invoke(_refreshLbx);
}
else
{
lbxQuotes.DataSource = null;
lbxQuotes.DataSource = _quotes;
}
}with the _refreshLbx delegate defined in the form's class as:
private delegate void RefreshLbxDg8();
private RefreshLbxDg8 _refreshLbx;Then just init the delegate in the form constructor (after the InitializeComponent() call):
\_refreshLbx = RefreshLbx;
Now I am able to update the listbox contents from a timer handler.
-
I know that this is a bit late, but have you ever considered using the BindingList(Of T) Class[^] instead? It will notify any changes to the list so that the bound control can take action.
-
I'm not familiar with BindingList. I took a quick look at the link but am unsure of its benefit here. Thx for the heads up, though.
Perhaps I misunderstood your problem, I thought that it was that the ListBox was not automatically reflecting changes to the underlying DataSource List. The BindingList raises events that would cause the ListBox to update automatically when the List changes. Here is a simple example. New WinForm project with two buttons and two ListBoxes. Clicking Button1 adds items to the underlying lists, but only the ListBox with the BindingList as the DataSource is update. Clicking Button2 tells ListBox1 to Refresh it's data.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}List<int> L1 = new List<int>(); BindingList<int> L2 = new BindingList<int>(); private void Form1\_Load(object sender, EventArgs e) { L1.Add(1); L1.Add(2); L2.Add(11); L2.Add(22); listBox1.DataSource = L1; listBox2.DataSource = L2; this.button1.Click += new System.EventHandler(this.button1\_Click); this.button2.Click += new System.EventHandler(this.button2\_Click); } private void button1\_Click(object sender, EventArgs e) { L1.Add(3); // adds a value to L1, but listBox1 does not display it until the binding is refresshed L2.Add(33); // adds a value to L2 and listBox2 is automatically refreshed to display it } private void button2\_Click(object sender, EventArgs e) { // tell listBox1 to refresh the data ((CurrencyManager)this.BindingContext\[L1\]).Refresh(); } }
}