Cross-Thread Form Binding - Can it be done?

  • Thread starter Thread starter jehugaleahsa
  • Start date Start date
J

jehugaleahsa

Hello:

I have a business object that reports the updated status of a download
taking place in another thread. I wanted to simplify my code by having
the interface bind to my business object directly. My business object
implements INotifyPropertyChanged.

I get a cross-thread error since the download is taking place in
another thread. What is *the* way for handling this type of issue. If
it makes any difference, I am binding a DataGridView.

Thanks,
Travis
 
Cross-thread form binding? It's not a sound idea. The usual way to
deal with this is to use Invoke and CallBacks. There's a good example
at:http://www.codeproject.com/KB/miscctrl/progressdialog.aspx

Are there few enough lines for the code to be posted here?

Kofi Sarfo

Cross-thread form binding? It's not a sound idea. The usual way to
deal with this is to use Invoke and CallBacks. There's a good example
at:http://www.codeproject.com/KB/miscctrl/progressdialog.aspx

Are there few enough lines for the code to be posted here?

Kofi Sarfo

I had already written my code to use Invoke, and all my research
points back that way. I just feel like there should be a way of
designing the UI using a business object.

If there was a way to do that without the interface trying to bind to
my class, that would be excellent. It wouldn't be hard reverting back.

Thanks,
Travis
 
Managed it by educating BindingList<T> about sync-context; a "keeper" I
reckon ;-p

Most of this code is demo; it is just the list that you need...

Marc

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
public abstract class EntityBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;

protected virtual bool UpdateField<T>(ref T field, T value, string
propertyName) {
if (EqualityComparer<T>.Default.Equals(field, value)) return
false; // no change
field = value;
if (!string.IsNullOrEmpty(propertyName))
OnPropertyChanged(propertyName);
return true;
}
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

public class SomeEntity : EntityBase
{
public SomeEntity() { }
public SomeEntity(string forename, string surname, DateTime
dateOfBirth)
{
Forename = forename;
Surname = surname;
DateOfBirth = dateOfBirth;
}
private string forename, surname;
private DateTime dateOfBirth;

public string Forename {
get { return forename; }
set { UpdateField(ref forename, value, "Forename"); }
}
public string Surname {
get { return surname; }
set { UpdateField(ref surname, value, "Surname"); }
}
public DateTime DateOfBirth {
get { return dateOfBirth; }
set { UpdateField(ref dateOfBirth, value, "DateOfBirth"); }
}
}

static class Program
{
[STAThread]
static void Main()
{
ThreadedBindingList<SomeEntity> data = new
ThreadedBindingList<SomeEntity>();
data.Add(new SomeEntity());
data.Add(new SomeEntity());
data.Add(new SomeEntity());
Application.EnableVisualStyles();
using(Form f = new Form())
using (DataGridView dgv = new DataGridView())
{
dgv.Dock = DockStyle.Fill;
dgv.DataSource = data;
f.Controls.Add(dgv);
f.Load += delegate {

ThreadPool.QueueUserWorkItem(delegate
{
StartRandomEditing(data);
});
};
Application.Run(f);
}
}
static void StartRandomEditing(IList<SomeEntity> data)
{
Random rand = new Random();
while (true)
{
Thread.Sleep(1000);
int index = rand.Next(data.Count);
SomeEntity item = data[index];
switch (rand.Next(3))
{
case 0:
item.Forename += "#";
break;
case 1:
item.Surname += "#";
break;
case 2:
item.DateOfBirth += TimeSpan.FromDays(1);
break;
}
}
}
}

public class ThreadedBindingList<T> : BindingList<T>
{
protected override void OnAddingNew(AddingNewEventArgs e)
{
SynchronizationContext ctx = SynchronizationContext.Current;
if (ctx == null)
{
BaseAddingNew(e);
}
else
{
SynchronizationContext.Current.Send(delegate
{
BaseAddingNew(e);
}, null);
}
}
void BaseAddingNew(AddingNewEventArgs e)
{
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e)
{
SynchronizationContext ctx = SynchronizationContext.Current;
if (ctx == null)
{
BaseListChanged(e);
}
else
{
SynchronizationContext.Current.Send(delegate
{
BaseListChanged(e);
}, null);
}
}
void BaseListChanged(ListChangedEventArgs e)
{
base.OnListChanged(e);
}
}
}
 
Oops; minor optimisation (that I prep'd for, then forgot...):

replace:
SynchronizationContext.Current.Send(
with:
ctx.Send(

Marc
 
Because the thread calling the ThreadedBindingList methods may not be the thread that created the list, you will want to move the SyncronizationContext object out of the methods and into a class variable. This way, the sync context will always be the one that created the object:

public class ThreadedBindingList<T> : BindingList<T>
{
SynchronizationContext ctx = SynchronizationContext.Current;
protected override void OnAddingNew(AddingNewEventArgs e)
{
//SynchronizationContext ctx = SynchronizationContext.Current;
if (ctx == null)
{ BaseAddingNew(e); }
else
{ ctx.Send(delegate { BaseAddingNew(e); }, null); }
}

void BaseAddingNew(AddingNewEventArgs e)
{ base.OnAddingNew(e); }

protected override void OnListChanged(ListChangedEventArgs e)
{
//SynchronizationContext ctx = SynchronizationContext.Current;
if (ctx == null)
{ BaseListChanged(e); }
else
{ ctx.Send(delegate { BaseListChanged(e); }, null); }
}

void BaseListChanged(ListChangedEventArgs e)
{ base.OnListChanged(e); }
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top