多线程视图模型

时间:2010-10-13 15:07:57

标签: wpf mvvm

我有一个实现向导功能的NavigationWindow,以及一组代表这些步骤的Page对象。

每个页面使用单独的视图模型。

其中一些视图模型从其构造函数中生成工作线程。我在处理视图模型时终止这些线程(它们实现了IDisposable)。

此外,我将这些视图模型分配给Pages的构造函数中的Pages的DataContext,并在Unloaded事件上处理DataContext。我这样做是因为我需要停止工作线程。

只要我不想在向导中导航,所有这一切都可以正常工作。但是,如果我这样做,页面,因为它之前已被卸载,不再有DataContext,也没有显示任何内容。

所以,要修复我不需要在Unloaded上处理DataContext,而是指示视图模型在其拥有窗口加载/卸载时启动/停止其线程。我认为我需要在视图模型上引入一些方法(比如Start()和Stop())。并从Pages的Initialized和Unloaded处理程序中调用这些方法。

但这很难看。它太复杂了,页面需要知道启动/停止线程,否则它将无法工作。所以我正在寻找合适的MVVM方法来实现这一目标。

请帮忙 康斯坦丁

1 个答案:

答案 0 :(得分:1)

听起来问题是视图模型依赖于视图的生命周期 - 这自动意味着视图将通知视图模型的状态转换。目标是找到这些变化的最佳表示。

第一步是重新构建交互:Start()Stop()是必要的概念,我同意这些概念确实很重要。相反,让我们考虑一下我们作为状态机所做的事情。我将假设您的线程正在进行某种倾听,因此我们的状态可能是ListeningIdleComplete。它们分别对应于正在运行的线程,暂停的线程和准备终止的线程。

表示状态的一种可靠方法是枚举:

public enum ListenerState
{
    Idle,

    Listening,

    Complete
}

您可以在视图模型上声明此类型的属性:

public class ListenerModel : ViewModel
{
    private ListenerState _state;

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

            RaisePropertyChanged("State");
        }
    }
}

然后,您将监听状态的更改并更新线程以匹配:

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

现在,视图只需要通知视图模型生命周期事件(视图模型除了视图之外无法知道任何其他方式):

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

如果要将视图与视图模型完全分离,可以在控件中为状态创建依赖项属性:

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); }
}

然后,在Loaded处理程序中设置该属性,而不是引用视图模型:

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

最后,您可以将属性绑定到标记中的视图模型属性:

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