开发者

multithreaded view models

开发者 https://www.devze.com 2023-01-20 11:32 出处:网络
i have a NavigationWindow that implements a wizard functionality, and a set of Page objects that represent the steps.

i have a NavigationWindow that implements a wizard functionality, and a set of Page objects that represent the steps.

each Page uses a separate view model.

some of these view models spawn worker threads from their constructors. i terminate these threads when the view models are disposed of (they implement IDisposable).

furthermore, i assign these view models to the Pages' DataContext in the Pages' constructors, and dispose of the DataContext on the Unloaded event. i do this because i need to stop the worker threads.

all of this works fine as long as i do not want to navigate back in the wizard. but if i do, the page, since it has been unloaded before, does not have a DataContext anymore and does not show anything.

so, to fix that i need not dispose of the DataContext on Unloaded, and instead instruct the view model to s开发者_开发问答tart/stop its thread(s) when its owning window gets loaded/unloaded. i figure for that i need to introduce a couple of methods (say, Start() and Stop()) on the view model that would do that. and call these methods from the Pages' Initialized and Unloaded handlers.

but this is ugly. it is too complicated, the pages need to know to start/stop threads, otherwise it won't work. so i am looking for the right MVVM way to accomplish this.

please help konstantin


It sounds like the issue is that the view model is dependent on the view's lifecycle - this automatically implies that the view will be notifying the view model of state transitions. The goal is to find the best representation of those changes.

The first step is to re-frame the interaction: Start() and Stop() are imperative concepts which, I agree, do feel heavy. Instead, let's think about what we're doing as a state machine. I am going to assume your threads are doing some sort of listening, so our states might be Listening, Idle, and Complete. They would respectively correspond to running threads, paused threads, and threads ready to be terminated.

A solid way to represent states is an enumeration:

public enum ListenerState
{
    Idle,

    Listening,

    Complete
}

You would declare a property of this type on your view model:

public class ListenerModel : ViewModel
{
    private ListenerState _state;

    public ListenerState State
    {
        get { return _state; }
        set
        {
            _state = value;

            RaisePropertyChanged("State");
        }
    }
}

Then, you would listen for changes in state and update the thread to match:

protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
    if(e.PropertyName == "State")
    {
        // Manipulate thread for current state
    }
}

Now, the view just has to notify the view model of lifecycle events (something the view model couldn't know any other way besides from the view):

private void OnLoaded(object sender, RoutedEventArgs e)
{
    ((ListenerModel) this.DataContext).State = ListenerState.Listening;
}

If you want to completely decouple the view from the view model, you can create a dependency property in your control for the state:

public static readonly DependencyProperty ListenerStateProperty =
    DependencyProperty.Register("ListenerState", typeof(ListenerState), typeof(YourControl), null);

public ListenerState ListenerState
{
    get { return (ListenerState) GetValue(ListenerStateProperty); }
    set { SetValue(ListenerStateProperty, value); }
}

Then, set that property in the Loaded handler instead of referencing the view model:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    this.ListenerState = ListenerState.Listening;
}

Finally, you would bind the property to the view model's property in the markup:

<local:YourControl ListenerState="{Binding State, Mode=TwoWay}" />
0

精彩评论

暂无评论...
验证码 换一张
取 消