Root Object Passed by Ref to Control not reflecting Changes

Root Object Passed by Ref to Control not reflecting Changes

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


Phlar posted on Friday, March 19, 2010

Hi All,

I have a WinForm (yes we are still using WinForms) with a split container.  Obviously a TreeView and a WinPart control.  When the WinForm is loaded we retrieve a Root object that contains Child and GrandChild object(s).

When an item in the TreeView is selected we construct a WinPart control and perform the usual DataBindings.  We pass the Root object by reference to the WinPart control so that we can perform the Bind and Rebind methods as required.

When the ApplyEdit() and Save methods are invoked in the control and I evaluate the Root object in the WinForm it still indicates that it is Dirty but the Root object in the WinPart control is not.

Any ideas?  Here are some code fragments:

WinForm Code:

_rootObject = RootObject.GetObject();

...

AddWinPart(new WinPartControl(ref _rootObject, _selectedChildObject)); 

 

WinPart Code:

public WinPartControl(ref RootObject rootObjectItem, ChildObject childObjectItem){

   _rootObject = rootObjectItem;

   _childObject = childObjectItem;

}

private void RebindUI() {

.....

    _rootObject = _rootObject.Save();

....

}

The Saved() event in the WinForm is invoked so I know it's working on the correct object.

Thank you.

rsbaker0 replied on Friday, March 19, 2010

If I'm following you correctly,  the root object is being saved in your WinPartControl, but you're holding a reference to it your WinForm also.

The WinPart member _rootObject gets updated when assign the result of the Save() operation to it, I don't see how this could update the original copy of the reference that was supplied when the control was created.

You passed it by reference, but that just allows the original call to change the value. If you don't assign anything to the ref parameter during the actual execution of the procedure, then the ref keyword didn't buy you anything. Your opportunity to change the value expired at the end of your WinPartControl constructor.

(Coming from a long C++ background, I think I see exactly what you are trying to do, effectively having a pointer to the original reference and keeping them in sync so that when you update one the original is updated, but I don't know how to directly do that in C# without wrapping the original reference with another object)

RockfordLhotka replied on Friday, March 19, 2010

Prior to CSLA 4 you can't have two Windows Forms UI elements binding to the same object. You can only bind an object to one form (one bindingsource).

In CSLA 4 this is changed so you can bind an object to multiple bindingsource objects. It still won't be savable until it is unbound from all forms of course, because that's the only time the edit level will get down to 0.

Phlar replied on Monday, March 22, 2010

RockfordLhotka

Prior to CSLA 4 you can't have two Windows Forms UI elements binding to the same object. You can only bind an object to one form (one bindingsource).

In CSLA 4 this is changed so you can bind an object to multiple bindingsource objects. It still won't be savable until it is unbound from all forms of course, because that's the only time the edit level will get down to 0.

 

Thanks for the update Rocky.  That's exactly the behaviour I wanted to utilize in 3.8.0.  Instead I created my root object in the WinForm and passed the details to the WinPart control to handle all additional functionality since I couldn't bind it at both levels.

JonnyBee replied on Monday, March 22, 2010

Hi all,

In our apps we use multiple bindingsources to the same object/instance in different forms and user controls but follow very strict coding guidelines to keep DataBinding working OK. You do need a good understanding of how DataBinding works under the "hood" to make it work (and keep it work) .

We are mainly using Csla for N2 (.Net 2.0) in our apps.

You must adhere to the following:

Save: 

In our apps -  the Window Forms/User Controls are only "views" and the UI App has a module handler that know which forms are bound to the BO. And the module handler does all the unbind/save/rebind flow in a generic implementation.

Phlar replied on Monday, March 22, 2010

Yes, I've done my fair share of C/C++ programming and had thought passing an item by ref actually passed the memory address pointer.

What we would like to do is to allow the user to select various elements in the Treeview Node which displays the corresponding Winpart control in the second panel.  They can then modify the details and move on to the next item they wish to modify.  Once all modifications have been completed a final save call is made.

To illustrate the example, take a typical CRM application with Client Name, Addresses, Contact Details, Notes.  Obviously the Customer is the root object.  Addresses, Contacts and Notes are all children.  When the user selects a particular address or contact details or note we would create the corresponding WinPart and load it in the second panel.  They can add/remove/update any one of these details until they are completed and then select Save.

RockfordLhotka replied on Monday, March 22, 2010

All reference types (object types) in .NET are aways passed by reference. Which basically means you are passing around a pointer in C++ terms, though you can't actually use a reference like a pointer of course.

var x = new Customer();

In this case 'x' is basically a pointer (reference) to an instance of Customer. If you pass x to a method, or assign it to another field, no more instances are created - you are just passing around a reference to the same instance.

So what you want to do is easy enough - except that data binding complicates matters.

As soon as a Windows Form or bindingsource get a reference to an object data binding interacts with that object. In existing versions of CSLA having more than one binding target interact with your object doesn't work - they conflict. In CSLA 4 the objects are smart enough to deal with multiple binding targets - though there are all sorts of caveats around the developer fully understanding what that means - how data binding will act, how the object will act and so forth.

This is all almost infinitely easier in Silverlight or WPF, where data binding isn't so heavy-handed in most cases. I debated whether to even address this issue for Windows Forms given its legacy status, but so many people still use Windows Forms it seemed worth adding this one last feature Smile

Phlar replied on Tuesday, March 23, 2010

Hi Rocky,

I am not sure if I am following your first statement correctly.  I understand that all non primitive types in .NET are passed by reference.  However what I am somewhat confused about is why an object passed into a method, assigned to a variable, and then modified within the method is not reflected by the calling code.  For example I have constructed a simple test:

public class StandardClass{

    private string _testString;

    public void AssignedString(ref string StringFromWinForm){

        _testString = string.Concat(StringFromWinForm, "1");

        //StringFromWinForm= string.Concat(StringFromWinForm, "1");

    }

}

//WinForm Code

private void _button1_Click(...){

    StandardClass class1 = new StandardClass();

    string _localString = "Hello World ";

    class1.AssignedString(ref _localString);

    MessageBox.Show(_localString);

}

Running this code produces the result "Hello World " and not "Hello World 1" as I had hoped.

This is one of two errors I am encountering.  The other is databinding and the EditLevels which I am hoping this issue will address the databinding issue.

As I see it, I have two options:

  1. Create a protected internal getter and retrieve the root object whenever the object has been saved or the control has been closed and populated with another control.
  2. Implement Event handlers to invoke and refresh the root object in the WinForm

Am I missing something obvious here (as I think I have been staring at this problem too long and over-complicating things).

 

Copyright (c) Marimer LLC