Databinding property of object in other thread

J

Jason Wolf

I have an object which runs in a sperate thread, from the main thread, and
while running it updates a public property.
In my main form I defined databinding on the property and I would like to
see this databinding update my form property to reflect the value of the
other components property, but it does not happen.

My binding is Databinding.Add("Text",renderEngine,"Framerate");
When the property is initially set, to minus one, the text property is
updated to reflect this, but later changes do not show.
I tried setting a timer, on the main form, to call my binding with
WriteValue(), but that does nothing to better the situation.
If I, in the timer event, just say Text=renderEngine.Framerate.ToString()
then it works fine, but then I loose the nicity of databinding.

What might be the solution to this problem?
 
N

Nicholas Paldino [.NET/C# MVP]

Jason,

The reason this occurs is because you are triggering a UI update from
the underlying thread (through the updating of the object, which updates the
UI through the binding).

In order to fix this, in your thread, when you assign the value to the
object which will trigger the data binding, you have to do it in a method
which is called on the UI thread through a call to the Invoke method,
passing that method (using a delegate) and the parameters.

Assuming you are using C# 2.0, you can use anonymous methods to make
this easier. Assume this is your old code that runs in the thread.

// This is the object which is data bound and triggers an update on the UI
thread.
myObject.SomeValue = 5;

You can marshal that call to the UI thread like this:

// Create the delegate which will assign the value.
Action<int> action = delegate(int value) { myObject.SomeValue = value; };

// Pass to invoke.
form.Invoke(action, new object[]{5});
 
J

Jason Wolf

// Create the delegate which will assign the value.
Action<int> action = delegate(int value) { myObject.SomeValue = value; };

// Pass to invoke.
form.Invoke(action, new object[]{5});

As far as I can see this would require my other object (the renderer) to
know about the form in order for it to be able to call invoke on the form..?
I would rather not have that kind of dependency.
Is there perhaps a way to invoke on the "main thread" without knowning
anything about which objects run in that mysterious "main thread"?
 
N

Nicholas Paldino [.NET/C# MVP]

Jason,

You would have to modify your renderer to take an implementation of
ISynchronizeInvoke. Then, you can call the Invoke implementation on that
interface. The Control class implements that interface.

If you don't have the interface implementation, you can set the property
directly. Otherwise, you create the delegate and pass it to the Invoke
method on ISynchronizeInvoke.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Jason Wolf said:
// Create the delegate which will assign the value.
Action<int> action = delegate(int value) { myObject.SomeValue = value; };

// Pass to invoke.
form.Invoke(action, new object[]{5});

As far as I can see this would require my other object (the renderer) to
know about the form in order for it to be able to call invoke on the
form..?
I would rather not have that kind of dependency.
Is there perhaps a way to invoke on the "main thread" without knowning
anything about which objects run in that mysterious "main thread"?
 
J

Jason Wolf

You would have to modify your renderer to take an implementation of
ISynchronizeInvoke. Then, you can call the Invoke implementation on that
interface. The Control class implements that interface.

If you don't have the interface implementation, you can set the
property directly. Otherwise, you create the delegate and pass it to the
Invoke method on ISynchronizeInvoke.

Thanks for your answer. If I understand you correctly there is no simple and
clear way of using databinding between threads then?
...but.. passing the form, as an ISynchronizeInvoke, is not that big a deal
after all. The renderer receives things such as handle to render window in
the constructor.
I will try your suggestion and see if it makes sence to connect the
renderers properties that strongly to the form.
 
B

bob clegg

Hi Jason,
Why not have the threaded object raise a custom event with the info as
the payload.
regards
Bob
 
J

Jason Wolf

You would have to modify your renderer to take an implementation of
ISynchronizeInvoke. Then, you can call the Invoke implementation on that
interface. The Control class implements that interface.

I tried your suggestion, as I understand it, and It still doesnt quite do
what I want.

The constructor is now
public GfxEngine(IntPtr windowHandle, ISynchronizeInvoke mainThreadInvoker)
: this(windowHandle)
{
this.mainThreadInvoker = mainThreadInvoker;
}

So I keep the ISynchroniceInvoker for later use. I pass the main form in the
constructor.

In my property I have

delegate void setFramerateCallback(int framerate);
public int Framerate
{
get
{
return framerate;
}
private set
{
if (mainThreadInvoker.InvokeRequired)
{
setFramerateCallback sfc = new
setFramerateCallback(setFramerate);
mainThreadInvoker.Invoke(sfc, new object[] { value });
}
else
framerate = value;
}
}

private void setFramerate(int rate)
{
Framerate = rate;
}


The databinding in the main form is simply
DataBindings.Add("Text", gfxEngine, "Framerate");
And it initializes Text to -1 which is the initial framerate of the
renderer.

When I, from my render thread, call the private set for framerate, the
mainThradInvoker.InvokeRequired is true and the delegate is created (it is
done locally for clarity right now) and it is called and sets the property,
without further invocations.

Any hints as to what I am doing wrong here? Why does it not work?
Secondly, can I not skip the extra setFramerate method and someho use a
property directly as a method in the delegate?
 
J

Jason Wolf

Why not have the threaded object raise a custom event with the info as
the payload.

That was my first verion actually. OnFramerateUpdated which the form
subscribed to. I thought it was nicer to just set up databinding one place
and have it worl automatically under the hood. I still think that is
prettiest, but it seems that it makes the calling end somewhat more messy so
perhaps I will return to the event idea
 

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

Top