【问题标题】:How do I switch between views from another thread?如何在另一个线程的视图之间切换?
【发布时间】:2011-09-20 08:46:57
【问题描述】:

我正在尝试使用 MVVM,所以我有:

主窗口:

<Window x:Class="tbtest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TabControl ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True" Grid.Row="0"/>
        <Button Command="{Binding AddNewTab}" Grid.Row="1">Add new tab</Button>
    </Grid>
</Window>

和 ViewModel:

public class VM
{
    public VM()
    {
        Tabs = new ObservableCollection<Object>();
        AddNewTab = new DelegateCommand<object>(ExecAddNewTab);
    }

    private void ExecAddNewTab(object obj)
    {
        var tab = new SomeTab();
        Tabs.Add(tab.View);
        ThreadPool.QueueUserWorkItem(item => tab.Activate());
    }

    public ObservableCollection<Object> Tabs { get; private set; }
    public DelegateCommand<object> AddNewTab { get; private set; }
}

每个选项卡都是单独的 ViewModel 和 View:

public class SomeTab : ITab, INotifyPropertyChanged
{
    public SomeTab()
    {
        View = new SomeTabView1 {DataContext = this};
    }

    public string Header { get { return "Header"; } }
    private object _view;
    public object View
    {
        get { return _view; }
        private set
        {
            _view = value;
            PropertyChanged(this, new PropertyChangedEventArgs("View"));
        }
    }

    public void Activate()
    {
        Thread.Sleep(2000);

        // Some work

        Application.Current.Dispatcher.BeginInvoke(new Action<SomeTab>(vm =>
                                                                   {
// switching views, how can it be done?
                                                                          vm.View = new SomeTabView2 { DataContext = this };

                                                                   }), this);
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };
}

SomeTabView1SomeTabView2 是带有 TextBlock 'View1' 和 'View2' 的简单 WPF UserControl。

我需要的是在激活方法中(在单独的线程中运行)将 View1 切换到 View2。
我提供的这段代码不起作用。

我该怎么做才能得到满足?谢谢。

【问题讨论】:

    标签: wpf mvvm view


    【解决方案1】:

    您的 ViewModel 不应该与 View 一起使用

    相反,让您的MainViewModel 包含一个SelectedTab 属性并将其绑定到您的TabControl,然后切换选项卡您只需设置SelectedTab。使用DataTemplates 告诉 WPF 如何绘制每个 ViewModel

    主窗口 XAML

    <Window x:Class="tbtest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525"
            xmlns:local=clr-namespace:MyNamespace>
    
        <Window.Resources>
            <DataTemplate DataType="{x:Type local:SomeTabViewModel1}">
                <local:SomeTabView1 /> 
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:SomeTabViewModel2}">
                <local:SomeTabView2 /> 
            </DataTemplate>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:SomeTabViewModel3}">
                <local:SomeTabView3 /> 
            </DataTemplate>
        </Window.Resources>
    
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}"
                        IsSynchronizedWithCurrentItem="True" Grid.Row="0"/>
            <Button Command="{Binding AddNewTab}" Grid.Row="1">Add new tab</Button>
        </Grid>
    </Window>
    

    主窗口视图模型

    public class VM
    {
        public VM()
        {
            Tabs = new ObservableCollection<Object>();
            AddNewTab = new DelegateCommand<object>(ExecAddNewTab);
        }
    
        private void ExecAddNewTab(object obj)
        {
            var tab = new SomeTab();
            Tabs.Add(tab);
            SelectedTab = tab;
    
            // This should only run whatever code is needed to initialize the
            // ViewModel. It should have nothing to do with views
            ((ITab)tab).Activate();
        }
    
        public object SelectedTab { get; private set; }
        public ObservableCollection<Object> Tabs { get; private set; }
        public DelegateCommand<object> AddNewTab { get; private set; }
    }
    

    【讨论】:

    • 我不需要切换标签。我只需要在一个选项卡内切换视图。我需要:创建新标签页,在此标签页上显示“正在加载...”消息,在后台加载数据,在同一标签页中显示加载的数据(数据网格、文本框等)
    • 在这种情况下,您的TabViewModel 应该有一个IsLoading 属性,当它被激活时它被设置为true,并且加载消息和标签内容的可见性应该绑定到IsLoading .您的 ViewModel 永远不必处理视图
    • 谢谢。让我们把这个例子复杂化。 MainWindowView 在一个程序集中,每个 TabViewModel 在其他程序集中。现在我有 10 个不同的TabViewModels。我应该在每个(!)TabView1TabView2 中写相同的代码(绑定到 IsLoaded 属性并使用可见性)吗?是否可以创建一个公共视图IsLoadingView 并显示它,然后加载内容,将其切换到TabViewXXX 视图之一?
    • 我该如何处理DataTemplates(在您的示例中为Window.Resources)? MainWindowView 在一个程序集中,而每个 TabViewModelTabViewXXX 在其他程序集中?我需要写这段代码的地方:&lt;DataTemplate DataType="{x:Type local:SomeTabViewModel1}"&gt; &lt;local:SomeTabView1 /&gt; &lt;/DataTemplate&gt;
    【解决方案2】:

    上面的问题是由于您的代码“假设”它将选择\聚焦新创建的Tab项目,只需使用IsSynchronizedWithCurrentItem

    您需要通过CollectionView 支持此字段。此外,您应该使用 CollectionView 本身添加新标签。

    例如

    假设您有一个名为TabsViewListCollectionView,其源集合为Tabs 可观察集合...

    TabControl.ItemsSource 绑定到TabsView 然后而不是...

       var tab = new SomeTab();
       Tabs.Add(tab.View); 
    

    使用这个...

       var tabUi = this.TabsView.AddNew() as SomeTab;
       this.TabsView.CommitNew();
       tabUi.Activate();
    

    如果这有帮助,请告诉我......

    【讨论】:

    • 对不起,我不明白。我不需要选择/聚焦新创建的选项卡项。我需要,那个标签内容会改变。首先 - 我必须在选项卡视图中显示“正在加载...”,当所有数据都准备好显示时,我需要将视图更改为 DagaGrids、TextBoxes 等...
    猜你喜欢
    • 2021-04-20
    • 2011-04-13
    • 1970-01-01
    • 2014-01-31
    • 1970-01-01
    • 1970-01-01
    • 2022-06-23
    • 1970-01-01
    • 2021-06-05
    相关资源
    最近更新 更多