我有两个网格,
使用在ObservableCollection上绑定的TabControl和将关闭动态TabItem的ContextMenuItem。
<Grid>
<TabControl Name="mainTabControl" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding ObservableCollectionTabItems}" Background="White" Margin="10,0,0,0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" >
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Close" Click="MenuItemCloseTab_Click">
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
显示名称o我的控件的ListBox。
<Grid Background="#FFD61B1B">
<ListBox x:Name="RightListBox" SelectionMode="Single" SelectionChanged="RightListBox_OnSelectionChanged" IsSynchronizedWithCurrentItem="true" Margin="10,0,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Margin="10" Content="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
在MainWindow.xaml的代码隐藏中添加一个新选项卡:
this._vmMainWindowTabControl.AddTab("ViewOne", "Test");
VMMainWindowTabControl.cs是一个具有以下内容的ViewModel:VMBase - &gt; INotifyPropertyChanged类,VMParentForViews是一个空的ViewModel类,VMViewTypeOne另一个ViewModel,我们在构造函数的TabItem上设置标题。
public class VMMainWindowTabControl :VMBase
{
private VMParentForViews vmParentForViews;
public VMMainWindowTabControl()
{
ObservableCollectionTabItems = new ObservableCollection<VMParentForViews>();
}
public ObservableCollection<VMParentForViews> ObservableCollectionTabItems { get; set; }
///<summary>
/// I'm trying to get controls to Tabitem with SelectedIndex but I have not success.
/// </summary>
//public int SelectedIndex
//{
// get
// {
// ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.ObservableCollectionTabItems);
// return collectionView.CurrentPosition;
// }
// set
// {
// ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.ObservableCollectionTabItems);
// collectionView.MoveCurrentToPosition(value);
// OnPropertyChanged("SelectedIndex");
// }
//}
/// <summary>
/// Adds the tab to the TabControl
/// </summary>
/// <param name="viewType">Type of the view.</param>
/// <param name="header">Header of the TabItem.</param>
public void AddTab(string viewType, string header)
{
if(viewType.Equals("ViewOne"))
{
vmParentForViews = new VMViewTypeOne(header);
this.ObservableCollectionTabItems.Add(vmParentForViews);
}
// Set the new tab to be the current tab
ICollectionView collectionView1 = CollectionViewSource.GetDefaultView(this.ObservableCollectionTabItems);
if (collectionView1 != null)
{
collectionView1.MoveCurrentTo(vmParentForViews);
}
}
/// <summary>
/// Closes the tab item.
/// </summary>
public void CloseTabItem(Object sender)
{
VMParentForViews vmParentForViews = (sender as MenuItem).DataContext as VMParentForViews;
this.ObservableCollectionTabItems.Remove(vmParentForViews);
}
public void AddElement(Object sender)
{
// How I can do this.
}
}
我的问题是当我点击ListBoxItem时,我得到了这个ListBox的SelectedItem。但是现在我不知道如何引用我将添加此控件的相应TabItem。此TabItem保存在ObservableCollection中,但我需要发送者点击该选项卡。也许这是我用Google搜索的其他方式。
这是一张图片,用于解释项目中View和ViewModel的树视图。
我正在尝试使用VMMainWindowTabControl中的SelectedIndex属性而没有成功将元素添加到TabItem。
VTabItem.xaml只是每个TabItem上显示的画布项。
创建新标签并关闭此标签,非常感谢Nishant Rana提供的这两个教程:Creating dynamic TabItem in WPF和Adding a Close Context Menu to TabItem in WPF
非常感谢您的帮助。 问候和新年快乐! :d
答案 0 :(得分:3)
我相信你的MVVM正在变得模糊,你可以通过绑定做更多的事情。
我已经汇总了一个我认为你所追求的例子,使用绑定,模板和RoutedCommands来实现你所说的功能。
就像这样......
我的示例中有3个模型,MyModel1到MyModel3,它们基本上都是
public class MyModel1
{
public string Header { get { return "One"; }}
}
标题为每个模型返回不同的值。
ViewModel也很简单
public class MyViewModel:INotifyPropertyChanged { 私有对象selectedItem;
public MyViewModel()
{
this.AvailableItems = new Collection<Type>() { typeof(MyModel1), typeof(MyModel2), typeof(MyModel3) };
this.Items = new ObservableCollection<object>();
}
public Collection<Type> AvailableItems { get; set; }
public ObservableCollection<object> Items { get; set; }
public void AddItem(Type type)
{
var item = Items.FirstOrDefault(i => i.GetType() == type);
if (item == null)
{
item = Activator.CreateInstance(type);
Items.Add(item);
}
SelectedItem = item;
}
internal void RemoveItem(object item)
{
var itemIndex = this.Items.IndexOf(item);
if (itemIndex > 0)
{
SelectedItem = Items[itemIndex - 1];
}
else if (Items.Count > 1)
{
SelectedItem = Items[itemIndex + 1];
}
Items.Remove(item);
}
public object SelectedItem
{
get { return selectedItem; }
set
{
if (value != selectedItem)
{
selectedItem = value;
OnPropertyChanged();
}
}
}
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
ListBox将绑定到AvailableItems,TabControl将绑定到Items。
有3个UserControl,每个模型一个,它们看起来都像这样
<UserControl x:Class="StackOverflow._20933056.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TextBlock Text="User Control 1" />
</UserControl>
后面的视图代码实例化ViewModel,注册RoutedCommand并处理RoutedCommand的事件。
public partial class MainWindow : Window
{
public static RoutedCommand CloseItemCommand = new RoutedCommand("CloseItem", typeof(MainWindow));
public MainWindow()
{
this.ViewModel = new MyViewModel();
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
private void MyListBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.ViewModel.AddItem(e.AddedItems.OfType<Type>().FirstOrDefault());
}
private void CommandBinding_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
this.ViewModel.RemoveItem(e.Parameter);
}
稍后有关RoutedCommand的更多信息。
乐趣在于Xaml,非常简单
<Window x:Class="StackOverflow._20933056.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:StackOverflow._20933056"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="600" Width="800">
<Window.Resources>
<ContextMenu x:Key="TabContextMenu">
<MenuItem Header="Close" Command="{x:Static this:MainWindow.CloseItemCommand}" CommandParameter="{Binding}" />
</ContextMenu>
<DataTemplate DataType="{x:Type this:MyModel1}">
<this:UserControl1 DataContext="{Binding}" ContextMenu="{StaticResource TabContextMenu}" />
</DataTemplate>
<DataTemplate DataType="{x:Type this:MyModel2}">
<this:UserControl2 DataContext="{Binding}" ContextMenu="{StaticResource TabContextMenu}" />
</DataTemplate>
<DataTemplate DataType="{x:Type this:MyModel3}">
<this:UserControl2 DataContext="{Binding}" ContextMenu="{StaticResource TabContextMenu}" />
</DataTemplate>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Path=Header}" />
<Setter Property="ContextMenu" Value="{StaticResource TabContextMenu}" />
</Style>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{x:Static this:MainWindow.CloseItemCommand}" CanExecute="CommandBinding_OnCanExecute" Executed="CommandBinding_OnExecuted" />
</Window.CommandBindings>
<Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox x:Name="MyListBox" ItemsSource="{Binding Path=AvailableItems}" SelectionChanged="MyListBox_OnSelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type system:Type}"><TextBlock Text="{Binding Path=Name}" /></DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TabControl Grid.Column="1" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
</Grid>
</Grid>
</Window>
正如我之前所说,ListBox绑定到ViewModel的AvailableItems,TabControl绑定到Items。 TabControl还绑定到SelectedItem,它允许从视图模型中控制选定的选项卡。
ListBox.SelectionChanged事件在后面的代码中处理,以调用添加或选择标签项的ViewModel.AddItem方法。
注意:TabControl中的每个选项卡实际上都是Model对象,而不是TabItem控件。定义了DataTemplates以允许TabControl在TabItem的内容中为每个Model正确插入所需的UserControl。
TabItem管理是通过ViewModel中的AddItem和RemoveItem方法实现的。
现在,回到RoutedCommand。
RoutedCommand允许定义一个命令,该命令可以从VisualTree中的某个地方触发,然后在其他地方获取,而接收处理程序则关心它来自何处。
因此,在Xaml中,有一个名为TabContextMenu的ContextMenu资源。该资源通过Style绑定到所有TabItem的ContextMenu。它还绑定了DataTemplates中每个UserControl的ContextMenu。
在ContextMenu中有一个MenuItem,它将触发RoutedCommand,并随之传递当前的DataContext(Model)。
MainWindow有一个CommandBinding接收和处理RoutedCommand。在CommandBindings Executed事件处理程序中,调用ViewModel.RemoveItem方法。
这里的代码几乎是我的示例的完整代码库。此答案中仅缺少MyModel2,MyModel3,UserControl2和UserControl3的实现,可以从MyModel1和UserControl1中推断出它们。您应该能够在新的C#/ WPF项目中重现该示例。
我希望这会有所帮助。
答案 1 :(得分:0)
你的榜样完美无缺!谢谢,你给我更多关于MVVM的观点。
如果我要更新,请从后面的代码更改AvailableItems。我继续这样做但没有成功。
我将它从Collection更改为ObservableCollection:
public ObservableCollection<Type> AvailableItems { get; set; }
构造函数我没有填写它。
public MyViewModel()
{
this.AvailableItems = new ObservableCollection<Type>();
this.Items = new ObservableCollection<object>();
}
现在添加一个标签:
private void AddTab(object sender, RoutedEventArgs e)
{
foreach (ClassFoo item in instanceBoo.methodBoo)
{
Type test = (Type) item.GetType();
this.myViewModel.AvailableItems.Add(test);
}
}
我的列表框是:
<Grid Background="#FFD61B1B">
<ListBox x:Name="RightListBox" ItemsSource="{Binding Path=AvailableItems}" SelectionMode="Single" SelectionChanged="RightListBox_OnSelectionChanged" IsSynchronizedWithCurrentItem="true" Margin="10,0,0,0">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type system:Type}">
<Label Margin="10" Content="{Binding Path=Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
当我运行应用程序时,我在AvailableItems中有数据,但它没有刷新列表框。我尝试将AvailableItems更改为具有IPropertyChanged的本地Collection,但也不起作用。
private Collection<Type> availableItems;
public Collection<Type> AvailableItems
{
get { return availableItems; }
set
{
if (value != availableItems)
{
availableItems = value;
OnPropertyChanged();
}
}
}
我的问题是我必须首先显示一个空白标签。接下来我必须用控件填充它们,这些将是我们的AvailableItems,并在保存之后。
我还有另一个问题,
如果我想从按钮中删除TabItem?
<Button Content="Button" Command="{x:Static mvvmTestStackOverflowAnswer1:MainWindow.CloseItemCommand}" CommandParameter="{Binding}" Grid.Column="1" HorizontalAlignment="Left" Height="46" Margin="478,10,0,0" VerticalAlignment="Top" Width="104"/>
假设ContextMenu发送命令关闭的结构相同。但是itemIndex总是给我一个-1。
internal void RemoveItem(object item)
{
var itemIndex = this.Items.IndexOf(item);
if (itemIndex > 0)
{
SelectedItem = Items[itemIndex - 1];
}
else if (Items.Count > 1)
{
SelectedItem = Items[itemIndex + 1];
}
this.Items.Remove(item);
}
感谢所有人的帮助! 问候。