开发者

How do I switch between views from another thread?

I'm trying to use MVVM, so I have:

MainWindow:

<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>

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

Each tab is separate ViewModel with 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 { };
}

SomeTabView1 And SomeTabView2 are simple WPF UserControl with TextBlock 'View1' and 'View2'.

What I need is in Activate method (that is running in separate thread) switch View1 to View2.

This code I provided doesn't work.

What sould I do to get desired? Thanks.


Your ViewModels should not work with Views

Instead, have your MainViewModel contain a SelectedTab property and bind it to your TabControl, then to switch tabs you only set the SelectedTab. Use DataTemplates to tell WPF how to draw each ViewModel

MainWindow 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>

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


The problem above is due to the fact that your code is "assuming" that it will select \ focus the newly created Tab item by simply using IsSynchronizedWithCurrentItem.

You need to back this field by a CollectionView. Also you should add new tab using this CollectionView itself.

E.g.

Assume you have a ListCollectionView called TabsView with source collection as Tabs observable collection...

Bind TabControl.ItemsSource to TabsView and then instead of ...

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

Use this...

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

Let me know if this helps....

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜