Problem with user control databound to a ReadOnly CSLA BO property

Problem with user control databound to a ReadOnly CSLA BO property

Old forum URL: forums.lhotka.net/forums/t/11621.aspx


Troncho posted on Wednesday, October 03, 2012

Hi everybody. I've written a winform helper user control to get access to lists associated with the the entity bound to the control and to open the corresponding edit form related to the entity bound to the user control.

Everything works great with read / write BO properties. Now, I've come accross an issue with read only properties.

I have a BusinessBase normal inherited class (ImpTax) with normal BO properties like:

private static PropertyInfo<ImpTaxCollectorInfoBase> TaxCollectorObjProperty = RegisterProperty<ImpTaxCollectorInfoBase>(c => c.TaxCollectorObj);

public ImpTaxCollectorInfoBase TaxCollectorObj
{
  get { return GetProperty(TaxCollectorObjProperty); }
  set { SetProperty(TaxCollectorObjProperty, value); }
}

ImpTaxCollectorInfoBase has a couple of properties (Guid Id, string Description) and is a ReadOnlyBase class.

I've implemented the CanWrite method on this class:

public override bool CanWriteProperty(string propertyName)
{
	if (InternalUse)
		return false;
	else
		return base.CanWriteProperty(propertyName);
}

So under certain circumstances, all object's properties from an instance of this class can be read only.

I've then written the custom control. It has a browse button (for bringing up all the records of the type being shown) and a LinkLabel (for opening an edit form associated with the BO property being displayed).

[System.ComponentModel.DefaultBindingProperty("InfoObj")]
[DefaultProperty("ControlText")]
public partial class UCNrgHelpBase : UserControl
{
	#region Business...

	protected IReadOnlyInfo infoObj;
	/// <summary>
	/// Default Binding Property...
	/// </summary>
	public virtual IReadOnlyInfo InfoObj
	{
		get
		{
			return infoObj;
		}
		set
		{
			SetInfoObj(value);
		}
	}
 
	protected void SetInfoObj(IReadOnlyInfo obj)
	{
		if (infoObj != obj)
		{
			infoObj = obj;

			if (obj == null)
				InfoLinkLabel.Text = string.Empty;
			else
				InfoLinkLabel.Text = infoObj.Info;
		}
	}
 
	[Browsable(true)]
	[EditorBrowsable]
	[Description("What this control represents...")]
	public string ControlText
	{
		get { return InfoLabel.Text; }
		set { InfoLabel.Text = value; }
	}
 
	bool _readOnly = false;
	[Browsable(true)]
	[EditorBrowsable]
	public virtual bool ReadOnly
	{
		get
		{
			return _readOnly;
		}
		set
		{
			_readOnly = value;
			InfoBrowseButton.Enabled = !_readOnly;
			InfoBrowseButton.StartColor = _readOnly ? System.Drawing.Color.Gray : System.Drawing.Color.Chocolate;

		}
	}
}

Then on the the winform, I use a BindingSource, an ErrorProvider, a BindingSourceRefresh and a ReadWriteAuthorization helper controls. And I bind the BindingSource.TaxCollectorObj property to InfoObj property on the user control. I use "Data Source Update Mode == OnValidation". And the user control "ApplyAuthorization" is set to true.

So, when ImpTax.InternalUse == false, all public properties are read/write and everything works well.

But, when ImpTax.InternalUse == true, all public properties become read only and here my problem comes to be.

I've noticed that every time focus leaves the user control (UCNrgHelpBase), just by tabbing or by clicking on the LinkLabel within it (which opens a new form), the BusinessBase Tax class bound property (TaxCollectorObj), executes its setter which in turn checks the CanWrite(..) and fails cause its a read only property. This happens even thoung the bound value never changed. In fact, I've debugged the setter and the [value] it receives == the GetProperty(TaxCollectorObjProperty). So the property never changed but the setter is getting called.

I've also checked it with the property TaxCollectorObj being read write and the setter gets always exectued, every time the user control looses focus.

I've found a non elegant solution: inside TaxCollectorObj's setter I can check

   if (GetProperty(TaxCollectorObjProperty) == value)

       return;

I truly think there must be a cause why this is happening. I must be missing something here and that must be the cause of this setter executing every time I exit the user control no matter its bound property didnt ever change.

Every suggestion is very appreciated.

Thanx in advance Big Smile

 

RockfordLhotka replied on Thursday, October 04, 2012

Windows Forms data binding works in very specific ways.

If the user changes a value in a UI control and tabs out of that control, data binding will put that value into the object's property. If the property setter throws an exception data binding won't work right.

A ReadOnlyBase object doesn't have the necessary features to allow for a public setter, so you can't use that.

A BusinessBase object does allow for a public setter, and it also supports authorization rules on the setter to block the property from being changed. In that case it throws an exception and will confuse data binding.

To prevent data binding from being confused, you should make sure to change the UI control to read-only if the user isn't allowed to change the property. That way the user can't change the value in the UI control, so data binding will never try to set the property, so the exception won't happen.

The Csla.Windows namespace has helper controls for Windows Forms to make this work. The Windows Forms app in ProjectTracker demonstrates how to do this.

Troncho replied on Thursday, October 04, 2012

Hi Rocky, thank you very much for your answer. The problem still stands.

I've added a ReadOnly Property to the user control I wrote which is working (apparently) very well in combination with your CSLA Windows Helper controls: ReadWriteAuthorization and BindingSourceRefresh. Heres is the users control code for the ReadOnly property:

bool _readOnly = false;
[Browsable(true)]
[EditorBrowsable]
public virtual bool ReadOnly
{
	get
	{
		return _readOnly;
	}
	set
	{
		_readOnly = value;
		InfoBrowseButton.Enabled = !_readOnly;
		InfoBrowseButton.StartColor = _readOnly ? System.Drawing.Color.Gray : System.Drawing.Color.Chocolate;
	}
}
 

 Then, on the form, when I drop this user control, I let its CSLA.Windows.ReadWriteAuthorization extender property ApplyAuthorization == true. So, when the code runs, the BusinessBase.CanWrite(..) executes and checks if the property bound to my user control can be written. If not, it "sets" the user control ReadOnly property and the code above executes just fine. The inner button (InfoBrowseButton) gets disabled. But this user control also has a "LinkLabel", and you can TAB into this LinkLabel or click on it to open the Edit Form associated with the databound ReadOnlyBase info associated with it (just as your Project Example does with LinkLabels on the resources and projects grids).

The problem arises when this user control loses focus, the Validating event gets executed and afterwards its bound property setter gets executed. And the setter in this case fails cause the property cannot be written to.

Here is where I'm getting confused. When I compare the "value" received by the property's setter and the GetProperty(objProperty) they are equal (the references are equal as they are class instances of a BO derived class.

I must be missing something cause the setter here should never be called to as neither the actual property value nor the user control value was ever changed.

Am I adding the ReadOnly user control property in a correct manner? Or do you suggest any other way of avoiding the setter being called?

Or am I missing something that makes the setter always being called every time the user control loses its focus?

What I dont understand is why the setter always gets called upon the user control losing focus (no matter if I modify its value or not and no matter if its ReadOnly Property is true or false). Just by TABBING through it causes the bound property setter being called.

I can compare the setter's "value" property with GetProperty(objProperty), but is seems like cheating. The problem must be somewhere before the setter gets called.

If there is a better way of writing a databinding user control that avoids this issue when being ReadOnly, I'll really appreciate you point me in the right direction.

For all other things, I'm using the CSLA.Windows extenders (ReadWriteAuthorization and BindingSourceRefresh) in combination with the winform ErrorProvider in almost every editable form and it's working really well.

The results so far are OUTSTANDING!!!

Rocky, thanks again Big Smile

 

Troncho replied on Saturday, October 06, 2012

Hi Rocky I addede a "public virtual bool ReadOnly" property to my user control. I explain in more detail in my main answer. Still the setter gets called. I must be missing something or must be declaring something wrong on my user control. May be you have some tip on this issue.

Thanks again,

Troncho

Troncho replied on Saturday, October 06, 2012

Now I've added this code to my user control's ReadOnly setter:

public virtual bool ReadOnly
{
	get
	{
		return _readOnly;
	}
	set
	{
		if (_readOnly != value)
		{
			_readOnly = value;
			InfoBrowseButton.Enabled = !_readOnly;
			InfoBrowseButton.StartColor = _readOnly ? 
                            System.Drawing.Color.Gray :
                            System.Drawing.Color.Chocolate;
			foreach (Binding binding in this.DataBindings)
			{
				binding.DataSourceUpdateMode = _readOnly ? 
                                   DataSourceUpdateMode.Never : 
                                   DataSourceUpdateMode.OnValidation;
			}
		}
     }
}

When the user control is ReadOnly, this effectively prevents the bound source to get updated.

Still the question remains on why does the bound source property setter gets called just by tabbing in the user control. May be its a winform bug, or I may still be missing something getting called which I'm not a aware of...

In order to cross check if any property gets updated, I added an event handler to the form where my user control is:

private void impTipoBaseBindingSource_CurrentItemChanged(object sender, EventArgs e)
{
	MessageBox.Show("CurrentItemChanged");
}

When I tab out of my user control, the BO setter gets called and this "CurrentItemChanged" event never gets called.

Could the problem be in the way I'm declaring my user control as being Data Bound?

Does this way of declaring a Single Property Data Bound User Control causes the bound property always being refreshed each time the control loses focus no matter what?

[System.ComponentModel.DefaultBindingProperty("InfoObj")]
public partial class UCNrgHelpBase : UserControl
{
...

}

If anybody has had any similar problems or experience with winform user controls, any advice is appreciated.

Thanks again,

Troncho

Copyright (c) Marimer LLC