📜 ⬆️ ⬇️

We saw a simple Binder ...

image


It was like this ... I needed one-sided binder. Only not from the control to the source, but vice versa. I am in the code a hundred thousand times changing the value of the source - and I don’t want my head to hurt about any textboxes there. I want them to be updated ...

In fact, the bourgeoisie already have a built-in binder, very powerful and cool. So powerful and cool that almost no documentation. More precisely, it is too much of it - and everywhere is vague, unclear. In short, I spat on the bourgeois technology and decided to use THIS hand to cut my binder ...

The task: to bind the control (in my case, any text-boxes) to the source so that each time the source is updated, the control itself is updated (automatically).
')
Conceptually, this task requires two things: the namespace System.Reflection and the event in the set accessor of the source object. Something like "PropertyChanged". Here is how it looks in code:

Class of source object
string _Property; public string Property { get { return this._Property; } set { this._Property = value; if (this.PropertyIsChanged != null) { this.PropertyIsChanged.Invoke(); } } } public event Action PropertyIsChanged; 


Please pay attention to two things:

1) if (this.PropertyIsChanged! = Null) {this.PropertyIsChanged.Invoke (); - this piece of code is necessary, because otherwise, you can run into a nullReference-exception.

2) public event Action PropertyIsChanged; - the event, as is customary, is declared with the public modifier, and it is based on the built-in Action delegate, which returns void and takes no parameters . Why is this delegate, and not any other? Because our Binder will connect its event handler to this particular event, which will only do one thing: read the new source value (when the set accessor was running, it was set, the event also worked - if he had at least one listener, of course) and assign it to the specified property (for example. Text) of a particular control. In other words, the event handler returns void and has no input parameters.

That's it, the source object is ready. Now, in fact, remains the code of the binder.

So, my simple Binder has only four main fields:
Binder class fields
A link to the control and the name of its property that is bound
 Control _targetControl; /// <summary> /// Get; set-once. ///  Control (),    -. /// </summary> public Control TargetControl { get { return this._targetControl; } set { if (this._targetControl != null) { /* do nothing */ } else { this._targetControl = value; } } } string _targetControlProperty; /// <summary> /// Get; set-once. ///    Control'a, ///    -. /// </summary> public string TargetControlProperty { get { return this._targetControlProperty; } set { if (this._targetControlProperty != null) { /* do nothing */ } else { this._targetControlProperty = value; } } } 


Link to the source object and the name of its property
 /// <summary> /// ,      Control. /// </summary> object _dataSourceObject; /// <summary> /// Get; set-once. ///    -, ///     Control. /// </summary> public Object DataSourceObject { get { return this._dataSourceObject; } set { if (this._dataSourceObject != null) { /* do nothing */ } else { this._dataSourceObject = value; } } } string _dataSourceObjectProperty; /// <summary> /// Get; set-once. ///   , ///    Control(). /// </summary> public string DataSourceObjectProperty { get { return this._dataSourceObjectProperty; } set { if (this._dataSourceObjectProperty != null) { /* do nothing */ } else { this._dataSourceObjectProperty = value; } } } 



Go ahead. Constructor.

Binder Designer
 public SimpleBinder( Control targetControl, string targetControlProperty, object dataSourceObject, string dataSourceProperty, string dataSourcePropertyChanged = "") { // safety checks CheckIfPropertyExists(targetControl, targetControlProperty); CheckIfPropertyExists(dataSourceObject, dataSourceProperty); // end safety this._targetControl = targetControl; this._targetControlProperty = targetControlProperty; this._dataSourceObject = dataSourceObject; this._dataSourceObjectProperty = dataSourceProperty; if (dataSourcePropertyChanged == String.Empty) { this.Binding(); } else { CheckIfEventExists(dataSourceObject, dataSourcePropertyChanged); this.Binding(dataSourcePropertyChanged, null); } } 


Notice that in the constructor there are four required parameters (correspond to the above class fields) and one free one. The free parameter is the name of the public event in the source object, which is responsible for notifying that the value of the specified property has changed. I already resulted the code of this class above. Once again, any object that claims to be a source for control must take care of the presence of such an event ... this is not a problem of the Binder itself.

And since the name of the event is an optional parameter, we need such a piece of code:

 if (dataSourcePropertyChanged == String.Empty) { this.Binding(); } else { CheckIfEventExists(dataSourceObject, dataSourcePropertyChanged); this.Binding(dataSourcePropertyChanged, null); } 

If the event is not specified, therefore, automatic updating is not required. Then the .Binding () method works without parameters.

this.Binding ()
 private void Binding() { this.Binding_SetValueToControl(this._targetControlProperty, this.DataSourceObjectProperty); } 


As you can see from the code, .Binding (), in turn, calls the private method .Binding_SetValueToControl () ... here it is:

.Binding_SetValueToControl ()
 private void Binding_SetValueToControl( string targetControlProperty, string dataSourceProperty) { this.TargetControl.GetType() .GetProperty(targetControlProperty) //     .SetValue(this.TargetControl, this.DataSourceObject.GetType() .GetProperty(dataSourceProperty) //     .GetValue(this.DataSourceObject) ); } 


Here are the very mechanisms of reflection. The context of this article does not include a detailed analysis of the methods .GetProperty () and others, but in principle everything is intuitively clear. Just for a glance, I’ll note that this is why we, in the constructor, required a string with the name of the control and source object properties - this was due to the System.Reflection device.

The last question remains - how does our binder tie the event, if it is specified in the constructor?

That's how:
 private void Binding_DataSourcePropertyChangedEvent( string dataSourcePropertyChanged, Delegate propertyChangedEventHandler = null ) { if (propertyChangedEventHandler != null) { this.DataSourceObject.GetType() .GetEvent(dataSourcePropertyChanged) .AddEventHandler(this.DataSourceObject, propertyChangedEventHandler); } else { SimpleBinder RefToThis = this; this.DataSourceObject.GetType() .GetEvent(dataSourcePropertyChanged) .AddEventHandler(this.DataSourceObject, new Action( () => { RefToThis.UpdateControl(RefToThis.GetDataSourcePropertyValue()); } )); } } 


Using all the same mechanisms of reflection, .AddEventHandler () connects the Bindov event handler to the public-event that originally existed in the class of the source object (and started in its set-accessor!). Here: 1) a new delegate of the Action type is created and 2) a method formed on the basis of a lambda expression (what it is and how to use it is not in this article) is passed to it. In principle, everything.

Now how to use it:

 SourceObj = new SomeClass("Text?"); // -. "Text?" -     . SimpleBinder txtBoxBinder = new SimpleBinder(this.label1, "Text", // Control   ,    SourceObj, "Property", //      "PropertyIsChanged"); //  -. 

That's all.

Source: https://habr.com/ru/post/269627/


All Articles