如何从自定义列表框向动态ObservableCollection Tabitem添加按钮,TexBlock等等

时间:2014-01-05 11:34:25

标签: c# wpf mvvm observablecollection tabcontrol

我有两个网格,

  1. 使用在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>
    
  2. 显示名称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>
    
  3. 在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的树视图。 enter image description here

    我正在尝试使用VMMainWindowTabControl中的SelectedIndex属性而没有成功将元素添加到TabItem。

    VTabItem.xaml只是每个TabItem上显示的画布项。

    创建新标签并关闭此标签,非常感谢Nishant Rana提供的这两个教程:Creating dynamic TabItem in WPFAdding a Close Context Menu to TabItem in WPF

    非常感谢您的帮助。 问候和新年快乐! :d

2 个答案:

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

感谢所有人的帮助! 问候。