views:

369

answers:

1

Overview

I am using CompositeWPF to create an app using C#. This really should make a difference to the answer as my problem would exist outside of Prism.

I have an ItemsControl which is bound to an ObservableCollection containing my ViewModels. This is working fine. In my DataTemplate I have my required controls bound in XAML. One of these controls is a button called "Configure." When clicked the button issues a DelegateCommand which passes the ViewModel as a parameter.

The DelegateCommand opens a modal dialog showing the VM looks as follows.

 private void ShowDialog(object obj)
        {

            ComPortPropertiesPresenter presenter = _container.Resolve<ComPortPropertiesPresenter>();
            ComPortViewModel vm = obj as ComPortViewModel;
            presenter.ComPort = vm.Clone() as ComPortViewModel;

            DialogOptions options = new DialogOptions();
            options.DialogTitle = presenter.View.DisplayName;
            options.HideOkCancelButtons = true;

            bool? result = DialogWorkspace.ShowView(presenter.View, options, () =>
            {
                return true;
            });

            if (result == true)
            {
                // TODO: There must be a better way of doing this.  The problem is if you bind the object the
                // automated bindings cause a realtime update.  If you clone the object and then set it to become obj then
                // the bindings do not update.   Need to investigate how an observablecollection triggers an update.
                int position = ComPorts.IndexOf(obj as ComPortViewModel);
                ComPorts.Remove(obj as ComPortViewModel);
                ComPorts.Insert(position, presenter.ComPort);


            }

My original code passed the obj to the presenter without a clone. The problem with that was that the value was being changed in real time. If you made a change and then hit cancel the dialog would be closed although because of the bindings the changes would have already happened.

I decided to clone the vm and pass that to the view. If the dialog result was then true I tried copying the cloned vm over the original. EG: obj = presenter.ComPort;

This didn't then update the values on the original view which contained the button.

I then tried things like... obj.BaudRate = presenter.ComPort.BaudRate which worked although my fear is it is long winded and if the vm was ever extended things could get missed.

My final solution was to remove the original vm and add the new one in the location of the original. This works 100% although I feel it is somewhat clumsy. Am I being overly critical or is there a better way?

My guess is that the ObservableCollection fires an INotify event when something is added/removed. The same in my vm when a property is updated it works because I raise an event.

So could the problem be that if you overwrite something in an ObservableCollection no event is raised?

There must be some clever cloggs out there who knows.

+2  A: 

The thing you need to understand here is one of pointers and not of WPF.

Your "obj" variable is simply the stored address of an object in memory. In your example, there are 2 references to this object. The first is your obj variable, the second is your ObservableCollection.

Let's say it like this. Let's name your original object "A" and your new object "B". Currently your references look like this:

obj => A
ObservableCollection => A
presenter.ComPort => B

When you say obj = presenter.ComPort, you are really saying "I no longer want to reference the object 'obj' was pointing to... now I want to reference the object 'presenter.ComPort' is pointing to". Now everything looks like this:

obj => B
ObservableCollection => A
presenter.ComPort => B

Notice you've done nothing to the ObservableCollection. This addresses your question "if you overwrite something... no event is raised"... you've not "overwritten" anything.

This is why you've had to do the Remove + Add to change the ObservableCollection. When you do this, NOW you've changed something in the ObservableCollection:

obj => B
ObservableCollection => B
presenter.ComPort => B

There is not a way around this really and what you have done is correct. This is the nature of references.

There are ways around this by using "reference references" (passing a reference with the "ref" keyword), but I really like the explicit nature of what you are doing here and I would advise you to keep it.

Anderson Imes
Thank you for taking the time to answer my question so thoroughly. I understand your points and will leave as is. My concern to this approach was if the ObservableCollection had many thousands of objects would there be a performance hit. I guess I shall do some tests and see what the stats say. Thanks again.
m1dst
Having done some research it appears that this option is fine for smaller collections. Larger collections would be better not using the IndexOf method but a custom method to do the same task. See http://dotnetperls.com/array-indexof
m1dst
You are looking at the wrong thing here. You aren't trying to find an object, you are trying to remove and add one. That's a completely different operation and has nothing to do with your problem. You'll find that the use of simple .NET arrays will make this add/remove operation much more expensive.
Anderson Imes
I think maybe I wasn't clear enough. Having solved my problem with the add and remove there was 1 line just above which used the IndexOf to find the index of the object so I could reinsert the new object in its place.I have another place in my program where I would like to do something similar but that ObservableCollection could have many thousand objects. Using that same technique where I searched, removed then inserted made me think there could be a big overhead. I wasn't suggesting moving away from an ObservableCollection, just writing a simpler search.
m1dst