wpf 使用 mvvm 在选项卡项中动态包含关闭按钮

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28298574/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 12:54:19  来源:igfitidea点击:

Include a close button in a tab item dynamically using mvvm

c#wpfmvvmtabcontrol

提问by Ani_1317

I have the a tabcontrol in my MainWindow. The default/first tab of the tabcontrol is the home user control. In the home page I have a button that can add further tabs.

我的 MainWindow 中有一个 tabcontrol。tabcontrol 的默认/第一个选项卡是家庭用户控件。在主页中,我有一个可以添加更多选项卡的按钮。

MainWindow.xaml:

主窗口.xaml:

<Window.Resources>
    <DataTemplate x:Key="ClosableTabItemTemplate"> 
        <Button Content="X" Cursor="Hand" DockPanel.Dock="Right" Focusable="False"
                FontFamily="Courier" FontSize="9" FontWeight="Bold"  Margin="0,1,0,0" Padding="0" 
                VerticalContentAlignment="Bottom" Width="16" Height="16"/>            
    </DataTemplate>
</Window.Resources>

<Grid>
    <TabControl Name="tabMain" ItemsSource="{Binding TabItems,UpdateSourceTrigger=PropertyChanged}" />   
</Grid>

In my view model I have the add functionality where a new tab has been added. I need the close button for all these newly added tabs.

在我的视图模型中,我有添加功能,其中添加了一个新选项卡。我需要所有这些新添加的标签的关闭按钮。

public MainViewModel()
    {
        try
        {
            Home Item2 = new Home();                           
            TabItems.Add(new TabItem() { Header = "Home", Content = Item2 });                             
        }
        catch(Exception ex)
        {
            MessageBox.Show("Exception "+ex);
        }

//Function to add new tabs.
public void AddNewTabs()
{
            ChildWindow childContent = new ChildWindow();
            TabItem item = new TabItem() { Header = "New Tab", Content = childContent};                
            item.MouseDoubleClick += new MouseButtonEventHandler(tab_MouseDoubleClick);
            TabItems.Add(item);
}

Right now new tabs are being added but without the close button. I have tried giving

现在正在添加新标签,但没有关闭按钮。我试过给予

item.HeaderTemplate = FindResource("ClosableTabItemTemplate") as DataTemplate; 

But it shows error.

但它显示错误。

Any help would be appreciated.

任何帮助,将不胜感激。

Thanks in advance.

提前致谢。

回答by James Willock

You could have a look at Dragablzwhich does this and more.

你可以看看Dragablz,它可以做到这一点以及更多。

enter image description here

enter image description here

Disclaimer: this is my library, but it's open source, so enjoy.

免责声明:这是我的图书馆,但它是开源的,所以尽情享受吧。

回答by ELH

You view model must not interact directly with the view in order to respect the Mvvm pattern, meaning that you need to use commands instead of Events, don't use any view related control in your View model logic ..

为了尊重 Mvvm 模式,您的视图模型不得直接与视图交互,这意味着您需要使用命令而不是事件,不要在您的视图模型逻辑中使用任何与视图相关的控件..

here a cleaner way to achieve what you're looking for :

这里有一种更干净的方式来实现您正在寻找的东西:

FirstIn the view use the TabControlContentTemplateand ItemTemplateInstead :

首先在视图中使用TabControlContentTemplateItemTemplate代替:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <Button Content="Add new tab" Command="{Binding AddNewTabCommand}"></Button>
    <TabControl  Grid.Row="1" Name="TabMain" ItemsSource="{Binding TabItems,UpdateSourceTrigger=PropertyChanged}" >
        <TabControl.ItemTemplate>
            <DataTemplate >
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Header}"/>
                    <Button Content="X" Cursor="Hand" DockPanel.Dock="Right" Focusable="False"
            FontFamily="Courier" FontSize="9" FontWeight="Bold"  Margin="0,1,0,0" Padding="0" 
            VerticalContentAlignment="Bottom" Width="16" Height="16" Command="{Binding DataContext.CloseTabCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CommandParameter="{Binding ElementName=TabMain,Path=SelectedItem}"/>
                </StackPanel>
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Content}" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Grid>

Second, In the ViewModel Create a class TabItem that will hold a tab Content and header (customize it as needed), you may want to implement the INotifyPropertyChangedinterface if the class reflect any changes to the view,

其次,在 ViewModel 中创建一个 TabItem 类,该类将保存一个选项卡内容和标题(根据需要自定义),INotifyPropertyChanged如果该类反映了对视图的任何更改,您可能需要实现该接口,

Third, define the commands to add and romove a TabItem from TabItems ObservableCollection,

第三,定义从 TabItems 添加和删除 TabItem 的命令ObservableCollection

here the viewModel Code :

这里的 viewModel 代码:

  public class TabItem
{
    public String Header { get; set; }
    public String Content { get; set; }
}
public class MainViewModel : INotifyPropertyChanged
{
    private ObservableCollection<TabItem> _tabItems;
    public ObservableCollection<TabItem> TabItems
    {
        get
        {
            return _tabItems;
        }

        set
        {
            if (_tabItems == value)
            {
                return;
            }

            _tabItems = value;
            OnPropertyChanged();
        }
    }
    private RelayCommand _addNewTabCommand;
    public RelayCommand AddNewTabCommand
    {
        get
        {
            return _addNewTabCommand
                ?? (_addNewTabCommand = new RelayCommand(
                () =>
                {
                    TabItems.Add(new TabItem()
                    {
                        Header = "NewTab",
                        Content = "NewContent"
                    });
                }));
        }
    }
    private RelayCommand<TabItem> _closeTabCommand;
    public RelayCommand<TabItem> CloseTabCommand
    {
        get
        {
            return _closeTabCommand
                ?? (_closeTabCommand = new RelayCommand<TabItem>(
                (t) =>
                {
                    TabItems.Remove(t);
                }));
        }
    }

    public MainViewModel()
    {
        TabItems = new ObservableCollection<TabItem>()
       {
            new TabItem()
           {
               Header = "Home",
               Content = "Home Content"
           },
           new TabItem()
           {
               Header = "Header1",
               Content = "Content1"
           }
       };

    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

the output :

输出 :

enter image description here

enter image description here

Ps: My MainWindowView DataContextis set to MainWindowViewModel, and tha's why i am using AncestorTypeto find the CloseTabCommand

Ps:我的MainWindow视图DataContext设置为MainWindowViewModel,这就是我AncestorType用来查找CloseTabCommand