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....
精彩评论