Chaining Up Events Through Abstractions C# WPF MVVM Conventions (What I'm doing works -- is it "correct"?)

StackOverflow https://stackoverflow.com/questions/15256691

Question

A conventions conundrum:

For simplicity's sake, let's say I have a View called FruitView with a label that says "Choose a fruit:" and choices such as apple, orange, pear, etc. in a ListBox.

Each item is an instance of a class (Apple.cs, Orange.cs, Pear.cs, etc.).

The ViewModel keeps track of the selected fruit with a binded property:

public Fruit CurrentFruit { get; set; }

"Fruit" itself is an interface that each of Apple, Orange, Pear, etc. derive from.

Now, another step, each of Apple, Orange, Pear, etc. Apple.cs depend on static classes called AppleManager, OrangeManager, PearManager, etc. which have methods and events. These managers are significantly different, and do not derive from any common base or interface.

For example, AppleManager might have:

public delegate void ColorChangedEventHandler(string color);

public static event ColorChangedEventHandler ColorChanged;

private void RaiseColorChanged(string color)
{
    if (ColorChanged != null)
    {
        ColorChanged(color);
    }
}

When "apple" is selected in the view, the rest of the View's buttons, images, etc. need to react to events that originate in AppleManager; when "orange" is selected, the view needs to react to events in OrangeManager.

So, the question is, what's the proper pathway for hooking up the View's reaction methods to the manager of CurrentFruit in the ViewModel?

I mean, it's possible to go:

private void FruitListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    // pseudocode: if apple selected, AppleManager.ColorChanged += new ColorChangedEventHandler(CurrentFruit_ColorChanged);
}

private void CurrentFruit_ColorChanged(string color)
{
    FruitColorTextBox.Text = color;
}

The reason this is unacceptable is because it breaks the design pattern, and means I need to handle for every kind of fruit.

in reality, instead of fruit options, I have many medical devices, and I want to use the interface as, well, as an interface, rather than adding extra logic.

My current solution was to add events to the interface VCONF, like so:

public interface VCONF
{
    public static event CallIncomingEventHandler CallIncoming;
    public static event EventArgs CallStarted;
    public static event EventArgs CallEnded;
}

Now VCONF1, VCONF2, VCONF3, etc. all derive from VCONF, and they implements these events with has methods to raise them.

This means that I have to write those same event lines in each VCONF1, etc., but that is the same as having multiple classes inherit INotifyPropertyChanged--that's why I set it up this way). It also means having those same events and raise methods written in VCONF1Manager--a chain of raise event, above layer raises event, above layer raises event, and so on, until the View can react to it. VCONF1Manager.CallIncoming -> VCONF1.CallIncoming -> View reacts

VCONF1 subscribes to the events in VCONF1Manager, VCONF2 subscribes to the events in VCONF2Manager, etc.

Other VCONF? might not even have a manager, just having their own way to determine when it's time to raise these events.

The View subscribes to the events of the current VCONF when it is selected:

private void VCONF_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    (this.DataContext as VCONFViewModel).CurrentVCONF = (sender as ListBox).SelectedValue as VCONF;

    (((sender as ListBox).SelectedValue as VCONF).Device as VCONF).CallIncoming += new CallIncomingEventHandler(CurrentVCONF_CallIncoming);
    // same for other events
    ...
}

So, what I'm really asking here is, what's the or your preferred/conventional way for chaining events through layers/abstractions?

What I'm doing works, but I can't help feeling like doubling up and chaining events in a row isn't the best way to do it.

Thanks in advance.

Was it helpful?

Solution 2

As mentioned in my comment, I reworked the events pathway, following Microsoft's .NET standard conventions.

I'm not using the ViewModel for this pathway anymore, the ViewModel is only to hold objects that the view uses directly and displays, like the collection of devices (or fruit, or whatever).

The View talks to the current device based on an interface, and that interface has whatever events the device will raise to the view there, with standard EventHandlers, no data EventArgs.

Each device implements those events, and raises them when it's internal logic determines to do so (depending on the device manager classes).

Also interfaced and implemented are properties to contain the data I was trying to pass around earlier through the events.

The view hooks up to the current device's events as soon as it's selected, and when those events are raised, the View accesses those values through the properties as "sender.IncomingCallerID" or "sender.CallStatus", etc.

So it basically follows how Microsoft set up SelectionChanged on a ListBox, then you access the SelectedValue property from sender.

OTHER TIPS

Your view should bind to properties of your view model, you shouldn't have view event handlers in your view model. So instead of the CurrentFruit_ColorChanged event handler, your view should bind to the Color property of its DataContext, so essentially, fruit.Color. You then react appropriately in the view model based on the property changing rather than the event that caused the change.

If your view needs to look different for different types of fruits, you should utilize data templating and have the view automatically configure itself when the current fruit changes. I.e.:

<Window.Resources>
    <DataTemplate DataType="{x:Type Apple}">
        <!-- ... view for Apples ... -->
    </DataTemplate>
    <DataTemplate DataType="{x:Type Orange}">
        <!-- ... view for Oranges ... -->
    </DataTemplate>
    <DataTemplate DataeType="{x:Type Pear}">
        <!-- ... view for Pears ... -->
    </DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding Path=CurrentFruit}"/>

Your fruit-specific views in the above example would connect to properties of their DataType. Your manager concept may (should) go away when the view is connecting to viewmodel properties instead of the viewmodel trying to subscribe to view events.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top