Winform UserControls seem to always trigger validation and call all their bound BO properties setters. Is this the normal behaviour?

Winform UserControls seem to always trigger validation and call all their bound BO properties setters. Is this the normal behaviour?

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


Troncho posted on Sunday, October 07, 2012

Has anybody come across this same behaviour? Is this the way it should work? Or am I missing something?

Just to prove my point, now I wrote a very simple UserControl with a Label and a TextBox, both private. And a unique binding property "TestName"

[DefaultBindingProperty("TestName")]
[DefaultProperty("TestName")]
public partial class TestUC : UserControl
{
	public TestUC()
	{
		InitializeComponent();
	}
	private System.Windows.Forms.Label TestNameLabel;
	private System.Windows.Forms.TextBox TestNameTextBox;
public string TestName { get { return this.TestNameTextBox.Text; } set { this.TestNameTextBox.Text = value; } } }

Then, I drop this UserControl on a form (testUC1) and bind its bindable property(TestName) to a BO (derived from BusinessBase) property (Alias). I also SetApplyAuthorization(..):

this.readWriteAuthorization1.SetApplyAuthorization(this.testUC1, true);
this.testUC1.DataBindings.Add(new System.Windows.Forms.Binding("TestName"this.impTaxBaseBindingSource, "Alias"true));

Then, every time testUC1 loses focus, the bound BO property (Alias) setter ALWAYS GETS CALLED! Just by tabbing testUC1 in and out, when it loses focus, the BO property setter gets called.

It seems that somewhere on the way, the UserControls private method:

  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.ValidateThroughAncestor(System.Windows.Forms.Control ancestorControl = {System.Windows.Forms.SplitContainer}, bool preventFocusChangeOnError = true) + 0x105 bytes 

gets called, which eventually triggers all the validation events associated with the user control which eventually trigger:

  System.dll!System.ComponentModel.ReflectPropertyDescriptor.SetValue(object component = {NrgNet.Library.Base.ImpTaxBase}, object value = "somevalue") + 0x11b bytes 

which calls the BO property setter...

Here is the Call Stack list:

> NrgNet.Library.Base.dll!NrgNet.Library.Base.ImpTaxBase.Alias.set(string value = "somevalue") Line 68 C#
  [Native to Managed Transition] 
  System.dll!System.SecurityUtils.MethodInfoInvoke(System.Reflection.MethodInfo method, object target, object[] args) + 0x65 bytes 
  System.dll!System.ComponentModel.ReflectPropertyDescriptor.SetValue(object component = {NrgNet.Library.Base.ImpTaxBase}, object value = "somevalue") + 0x11b bytes 
  System.Windows.Forms.dll!System.Windows.Forms.BindToObject.SetValue(object value) + 0x63 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.Binding.PullData(bool reformat, bool force) + 0x159 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.Binding.Target_Validate(object sender, System.ComponentModel.CancelEventArgs e = {System.ComponentModel.CancelEventArgs}) + 0x1a bytes 
  System.Windows.Forms.dll!System.Windows.Forms.Control.OnValidating(System.ComponentModel.CancelEventArgs e) + 0x88 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.Control.NotifyValidating() + 0x27 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.Control.PerformControlValidation(bool bulkValidation = false) + 0x34 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.ValidateThroughAncestor(System.Windows.Forms.Control ancestorControl = {System.Windows.Forms.SplitContainer}, bool preventFocusChangeOnError = true) + 0x105 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.EnterValidation(System.Windows.Forms.Control enterControl) + 0x7c bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.UpdateFocusedControl() + 0x8a bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.AssignActiveControlInternal(System.Windows.Forms.Control value = {System.Windows.Forms.SplitContainer}) + 0x60 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.ActivateControlInternal(System.Windows.Forms.Control control, bool originator = false) + 0x48 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.ActivateControlInternal(System.Windows.Forms.Control control = {NrgNet.WinForms.Base.UCTreePart<NrgNet.Library.Base.GenMenuListBase,NrgNet.Library.Base.GenMenuInfoBase>}, bool originator = false) + 0xae bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.SetActiveControlInternal(System.Windows.Forms.Control value = {NrgNet.WinForms.Base.UCTreePart<NrgNet.Library.Base.GenMenuListBase,NrgNet.Library.Base.GenMenuInfoBase>}) + 0x73 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.SetActiveControl(System.Windows.Forms.Control ctl) + 0x33 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.ActiveControl.set(System.Windows.Forms.Control value) + 0x5 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.SplitContainer.SelectNextActiveControl(System.Windows.Forms.Control ctl = {NrgNet.WinForms.Base.UCTreePart<NrgNet.Library.Base.GenMenuListBase,NrgNet.Library.Base.GenMenuInfoBase>}, bool forward = true, bool tabStopOnly = true, bool nested = true, bool wrap = false) + 0x70 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.SplitContainer.SelectNextControlInContainer(System.Windows.Forms.Control ctl, bool forward, bool tabStopOnly, bool nested, bool wrap) + 0xdc bytes 
  System.Windows.Forms.dll!System.Windows.Forms.SplitContainer.Select(bool directed, bool forward) + 0x96 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.SplitContainer.SelectNextControlInContainer(System.Windows.Forms.Control ctl, bool forward, bool tabStopOnly, bool nested, bool wrap) + 0x192 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.SplitContainer.Select(bool directed, bool forward) + 0x96 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.Control.SelectNextControl(System.Windows.Forms.Control ctl, bool forward, bool tabStopOnly, bool nested, bool wrap) + 0x7e bytes 
  System.Windows.Forms.dll!System.Windows.Forms.Form.ProcessTabKey(bool forward = true) + 0x24 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.ProcessDialogKey(System.Windows.Forms.Keys keyData = LButton | Back) + 0x71 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.Form.ProcessDialogKey(System.Windows.Forms.Keys keyData) + 0x29 bytes 
  System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.ProcessDialogKey(System.Windows.Forms.Keys keyData) + 0x2b bytes 


Thank you all,

Troncho

 

JonnyBee replied on Sunday, October 07, 2012

Hi,

I suggest you read thoroughly the DataBindingFAQ.
This document goes thorough what actually triggers validation and how databinding in Windows Forms work.

(There's a similar DataGridViewFAQ too that is valuable for databinding and datagridview.)

When updatemode is set to OnValidation the value is written back to the object.

By default, simple binding updates a bound data source property value as part of Control validation.
Control validation occurs when a Control loses focus AND whenever the user tabs into a control has CausesValidation set to true.
That may be the first, second, third or whenever the next control has CausesValidation set to true.
By default most input controls, forms and usercontrols has CausesValidation = true.

My preference is to NEVER to this type of databinding.  I my view a usercontrol is similar to a Form and should have it's own binding sources and readwriteauthorization/errorproviders. The reason being that you can only ever have one input control on the UserControl in this style. The ReadWriteAuthorization and ErrorProvider will act on the UserControl and NOT on the input controls and this would confuse the user.

You might get away with making the input control publicly visible but the tooling support for setting up databinding does not expose these properties (IIRC) so it would have to be defined manually.

Troncho replied on Wednesday, October 31, 2012

Thanks for the links Jonny. I'll go through them to get a deeper understanding of the associated behaviours.

Let me tell you exactly what I'm doing:

. I'm using special ReadOnlyBase derived objects as properties of an normal BusinessBase object and exposing them as read only to the UI.

. My first approach was to use 2 separete controls on each UI form associated with each of these special ReadOnlyBase properties, while editing the main BusinessBase object that contained them.

. The first control was a LinkLabel, exposing some text asociated with the ReadOnlyBase control (I actually bind the ReadOnlyBase object directy and works just fine, cause the info I want to expose gets returned by the ReadOnlyBase.ToString() which automatically gets called). It doesn't matter if the underlaying bound property is writeable or not, the LinkLabel never calls the bound property setter.

. The second control is a button, which is only enabled when the 1st control (textbox) bound property is writable. When clicked, it brings up a SelectForm, allows the user to select a diferent r/o object from the list and returns the newly selected item. Here I manually update BusinessBase property with the newly obtained ReadOnlyProperty and everything works fine.

. My second, more sophisticated approach, was to write special User Controls that could encapsulate both the info I wanted to expose to the UI and the button allowing the user to select a different ReadOnlyBase property during the BusinessBase object editing. I bound directy the RO property to a unique new bindable property I expose on the control.

. Everything went fine till I bumped across cases where the bound ReadOnlyBase property was actually ReadOnly (CanWriteProperty(..) == false) to the UI during the main BO editing process. There I found that no matter what, upon exiting the UserControl, the winform validation process ALWAYS triggered this property SETTER causing the exception in cases the property is actually not writable.

. My first thought, this time, was to check inside the setter if the property had actually changed:

if (ReadProperty(..) == value)

return;

... but I find this to be NOT elegant at all (more or less like doing something wrong and then having to check upon it).

So, as a result, I poped the issue on the forum.

All I want to do is bind ReadOnlyBase derived properties during main BO derived objects editing. Is there any simple workaround or suggestion around User Controls the way I'm trying to build them?

I think its not practical to add 1 extra BindingSource for each of these special UC on each edit form (I can have more than 10 of these special UCs on one BO edit form).

Once again, Jonny, thank you very much for your ideas and support Big Smile

Troncho

JonnyBee replied on Friday, November 02, 2012

Have you tried to specify DataSourceUpdateMode.Never on the DataBinding? 

http://msdn.microsoft.com/en-us/library/system.windows.forms.datasourceupdatemode.aspx

Troncho replied on Friday, November 02, 2012

Thanks for the advice. I'll give it a try and get back to you with the results.

Troncho,

Troncho replied on Friday, November 09, 2012

Hi Jonny. I've followed your advice. I'm setting DataSourceUpdateMode.Never when the control is ReadOnly and to DataSourceUpdateMode.OnPropertyChanged when not.

Its working perfectly well!!!

Thanks! Again!

Troncho Big Smile

Copyright (c) Marimer LLC