Custom change listeners

A user asked.

When I use a viewmodel as a datacontext everything just works. Now I want to add a listener to the viewmodel of my own design, but am stumped.  Do you have any suggestions? How do I hook up custom change listeners?

It all depends upon what you want to do with the notification. If you want to use that notification to update something else, I’d recommend using a Dependent instead of an event.

Updating a dependent

Where Independent has OnGet() and OnSet(), Dependent has only OnGet(). You can't directly set a dependent field. It's value is determined by an update method. You pass the update method to the constructor.

Here is an example dependent field.

private class PersonViewModel
{
    private Person _person;

    private string _fullName;
    private Dependent _depFullName;

    public PersonViewModel(Person person)
    {
        _person = person;
        _depFullName = new Dependent(UpdateFullName);
    }

    public string FullName
    {
        get { _depFullName.OnGet(); return _fullName; }
    }

    private void UpdateFullName()
    {
        _fullName = string.Format("{0}, {1}", _person.LastName, _person.FirstName);
    }
}

The UpdateFullName method is called only when the full name is out-of-date. If you access FullName again without changing the person’s name, the method is not called a second time.

The advantage of Dependent over events is dependency management. If you used events, you would have to know which events to subscribe to. Dependent, on the other hand, discovers its dependencies. This is why you use Update Controls instead of INotifyPropertyChanged in the first place.

Firing an event

But suppose that you need to notify something else for a different reason. Maybe you need to send a message to an external system. In this case, you can hook the Invalidated event of the Dependent. Add the hook to the constructor, like this:

public PersonViewModel(Person person)
{
    _person = person;
    _depFullName = new Dependent(UpdateFullName);
    _depFullName.Invalidated += delegate
    {
        Dispatcher.CurrentDispatcher.BeginInvoke(() =>
        {
            if (FullNameChanged != null)
                FullNameChanged();
        });
    };
}

The Invalidated event is fired whenever the Dependent becomes out-of-date. One of the Independent fields upon which it depends has just changed.

It is very important that your Invalidated handler does not try to synchronously get the updated value. The Independent field may not have its new value, yet. And even if it did, there may be additional changes coming. That is why the above example fires the FullNameChanged event asynchronously.

By the way, the code above is exactly how UpdateControls.XAML implements INotifyPropertyChanged.

Remember that Update Controls replaces imperative events with declarative dependency discovery. If you can avoid firing events, your application will be less brittle. But if you really need events, you can get them.

Comments

Specific example involving collections

I have an

EntityModel {
string name;
double x;
double y;
}

WorldModel {
Entities; // a collection of EntityModel
}

WorldView contains a 3D rendering engine whose scene graph I need to insert into and remove from whenever the underlying Entities collection changes.

How would I achieve this?

Actual code:
https://github.com/timothypratley/Strive.NET/blob/master/Source/Strive/S...
https://github.com/timothypratley/Strive.NET/blob/master/Source/Strive/S...
https://github.com/timothypratley/Strive.NET/blob/master/Source/Strive/S...

Thanks for a great library :)

3D rendering supports

Are you searching for 3D Renderings engine?

Modify the scene graph in the update method

This sounds like the second situation above: you are using a Dependent to effect a change in an external system.

You can have a Dependent in your WorldViewModel called _depSceneGraph. The update method of this Dependent (the method you pass to the constructor) will need to walk the entities. Call it UpdateSceneGraph. So in your WorldViewModel constructor you initialize:

_depSceneGraph = new Dependent(UpdateSceneGraph);

UpdateSceneGraph takes no parameters and returns void. In it, you walk through each EntityModel in WorldModel. Create the representative object in the Direct3D scene graph. To start with, just destroy all objects in the scene graph and rebuild everything from scratch. Once you get this working, you can optimize by using the RecycleBin. I'll post more on that later.

Then you'll need to trigger UpdateSceneGraph. You'll do this by hooking the Invalidated event. Remember that you can't trigger it immediately within the Invalidated event, since that will cause a dependency cycle. Instead, dispatch a call for later. For example:

_depSceneGraph.Invalidated += delegate
{
Dispatcher.CurrentDispatcher.BeginInvoke(() =>
{
_depSceneGraph.OnGet();
});
};

Finally, you'll need to prime the pump. After you hook this event, call OnGet() directly:

_depSceneGraph.OnGet();

That will cause the scene graph to be updated after any change to the Entities collection or any EntityModel object that it contains.

Hope this helps.

Ok I got that part working in

Ok I got that part working in a slightly different way; The scene graph only exists in my View class so in the ViewModel I fire another event that the View listens to and rebuilds the entire scene graph. discard doesn't really do much except allow the dependency to be found. Interested to hear more about RecycleBin. :)


public event EventHandler WorldChanged;
private readonly Dependent _depWorldModel;
void UpdateWorldModel()
{
var discard = WorldModel.Values;
var eh = WorldChanged;
if (eh != null)
eh(this, new EventArgs());
}

...
_depWorldModel = new Dependent(UpdateWorldModel);
_depWorldModel.Invalidated += () =>
Dispatcher.CurrentDispatcher.BeginInvoke((Action)_depWorldModel.OnGet);
_depWorldModel.OnGet();

RecycleBin article posted

I posted an article about the RecycleBin at http://updatecontrols.net/doc/advanced/object-recycling. Let me know if this does the job for you.