An error occurred while updating the properties of the winform Data Binding object asynchronously in the background.

Source: Internet
Author: User
ArticleDirectory
    • A touch of asynchronism
    • Delegation by Interface
    • Delegation by Lambda
    • Bonus: Easier Writing of the notification code
    • The whole Controller

When one is trying to use the MVC model on the winforms, it is possible to use the inotifypropertychanged interface to allow databinding between the controler and form.

It is then possible to write a controller like this:

     
Public Class Mycontroller: inotifypropertychanged
{
// Register Default Handler to avoid having to test For Null
Public Event Propertychangedeventhandler propertychanged = Delegate {};

 

Public VoidChangestatus ()
{
Status = datetime. Now. tostring ();
}

Private String_ Status;

Public StringStatus
{
Get {Return_ Status ;}
Set
{
_ Status =Value;

// Configure y that the property has changed
Propertychanged (This,NewPropertychangedeventargs ("Status"));
}
}
}

 

The form is defined like this:


Public Partial ClassMyform: Form
{
PrivateMycontroller _ controller =NewMycontroller ();

 

PublicMyform ()
{
Initializecomponent ();

// Make a link between labelstatus. Text and _ controller. Status
Labelstatus. databindings. Add ("Text", _ Controller,"Status");
}

Private VoidButtonchangestatus_click (ObjectSender, eventargs E)
{
_ Controller. changestatus ();
}

}

 

The form will update the "labelstatus" when the "status" property of controller changes.

All of this code is executed in the main thread, where the message pump of the main form is located.

 

A touch of asynchronism

Let's imagine now that the controller is going to perform some operations asynchronously, using a timer for instance.

We update the controller by adding this:

 

PrivateSystem. Threading. Timer _ timer;

 

PublicMycontroller ()
{
_ Timer =NewTimer (
D => changestatus (),
Null,
Timespan. fromseconds (1), // startInOne second
Timespan. fromseconds (1) // every second
);
}

 

By altering the Controller this way, the "status" property is going to be updated regularly

The operation model of the system. threading. timer implies that the changestatus method is called from a different thread than the thread that created the main form. thus, when the code is executed, the update of the label is halted by the following exception:

Cross-thread operation not valid: Control 'labelstatus' accessed from a thread other than the thread it was created on.

The solution is quite simple, the update of the UI must be completed MED on the main thread using control. Invoke ().

That said, in our example, it's the databinding engine that hooks on the propertychanged event. we must make sure that the propertychanged event is called "decorated" by a call to control. invoke ().

We cocould update the Controller to invoke the event on the main thread:

 

Set
{
_ Status =Value;

 

// Configure y that the property has changed
Action action = () => propertychanged (This,NewPropertychangedeventargs ("Status"));
_ Form. Invoke (action );
}

 

But that wocould require the addition of winforms depend code in the controller, which is not acceptable. since we want to put the Controller in a unit test, calling the control. invoke () method wocould be problematic, as we wocould need a form instance that we wocould not have in this context.

 

Delegation by Interface

The idea is to delegate to the view (here the form) The responsibility of placing the call to the event on the main thread. we can do so by using an interface passed as a parameter of the Controller's constructor. it cocould be an interface like this one:

 

Public InterfaceIsynchronouscall
{
VoidInvoke (Action );
}

 

 

The form wocould implement it:

 

VoidIsynchronouscall. Invoke (action Action)
{
// Call the provided action on the UI threadUsingControl. Invoke ()
Invoke (action );
}

 

 

We wowould then raise the event like this:


_ Synchronousinvoker. Invoke (
() => Propertychanged (This,NewPropertychangedeventargs ("Status"))
);

 

 

But like every efficient Programmer (read lazy), we want to avoid writing an interface.

 

Delegation by Lambda

We will try to use Lambda functions to call the method control. invoke () method. for this, we will update the constructor of the controller, and instead of taking an interface as a parameter, we will use:

 

PublicMycontroller (Action <action> synchronousinvoker)
{
_ Synchronousinvoker = synchronousinvoker;
...
}

 

 

To clarify, we give to the constructor an action that has the responsibility to call an action that is passed to it by parameter.

It allows to build the Controller like this:

 

_ Controller =NewMycontroller (A => invoke ());

 

 

Here, no need to implement an interface, just pass a small Lambda that invokes an actions on the main thread. And it is used like this:

 

_ Synchronousinvoker (
() => Propertychanged (This,NewPropertychangedeventargs ("Status"))
);

 

 

This means that the lambda specified as a parameter will be called on the UI thread, in the proper context to update the associated label.

The Controller is still isolated from the view, but adopts anyway the behavior of the view when updating "databound" properties.

If we wowould have wanted to use the Controller in a unit test, it wowould have been constructed this way:

 

_ Controller =NewMycontroller (A => ());

 

 

The passed Lambda wocould only need to call the action directly.

 

Bonus: Easier Writing of the notification code

A drawback of using inotifypropertychanged is that it is required to write the name of the property as string. This is a problem for processing reasons, mainly when using refactoring or obfuscation tools.

C #3.0 brings expression trees, a pretty interesting feature that can be used in this context. the idea is to use the expression trees to make an hypothetical "memberof" that wocould get the memberinfo of a property, much like typeof gets the system. type of a type.

Here is a small helper method that raises events:

 

Private VoidInvokepropertychanged <t> (expression <func <t> expr)
{
VaR body = expr. BodyAsMemberexpression;

 

If(Body! =Null)
{
Propertychanged (This,NewPropertychangedeventargs (body. member. Name ));
}
}

 

A method that can be used like this:


_ Synchronousinvoker (
() => Invokepropertychanged () => Status)
);

 

 

The "status" property is used as a property in the code, not as a string. It is then easier to rename it with a refactoring tool without breaking the code logic.

Note that the lambda() => StatusIs never called. It is only analyzed byInvokepropertychangedMethod as being able to provide the name of a property.

 

The whole Controller

Public Class Mycontroller: inotifypropertychanged
{
// Register Default Handler to avoid having to test For Null
Public Event Propertychangedeventhandler propertychanged = Delegate {};

 

PrivateSystem. Threading. Timer _ timer;
Private ReadonlyAction <action >_synchronousinvoker;

PublicMycontroller (Action <action> synchronousinvoker)
{
_ Synchronousinvoker = synchronousinvoker

_ Timer =NewTimer (
D => Status = datetime. Now. tostring (),
Null,
1000, // startInOne second
1000 // every second
);
}

Public VoidChangestatus ()
{
Status = datetime. Now. tostring ();
}

Private String_ Status;

Public StringStatus
{
Get {Return_ Status ;}
Set
{
_ Status =Value;

// Configure y that the property has changed
_ Synchronousinvoker (
() => Invokepropertychanged () => Status)
);
}
}

/// <Summary>
/// Raise the propertychangedEvent ForThe property "get" specifiedInThe expression
/// </Summary>
/// <Typeparam name ="T"> The type of the property </typeparam>
/// <Param name ="Expr"> The expression to get the property from </param>
Private VoidInvokepropertychanged <t> (expression <func <t> expr)
{
VaR body = expr. BodyAsMemberexpression;

If (body! = null )
{< br> propertychanged ( This , New propertychangedeventargs (body. member. name);
}< BR >}

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.