TabControl с кнопкой Добавить новую вкладку (+)

Каков правильный способ добавления вкладки кнопок «+» в конце всех элементов табуляции в полосе вкладок элемента управления вкладкой в ​​WPF?

  1. Он должен работать правильно с несколькими строками заголовка вкладок.
  2. Это должно быть в конце всех элементов табуляции
  3. Включение табуляции должно работать корректно ( Alt + Tab ), т. + Вкладка + должна быть пропущена.
  4. Мне не нужно изменять исходную коллекцию, к которой я привязываюсь. То есть, контроль должен быть повторно использован.
  5. Решение должно работать с MVVM

Введите описание изображения здесь

введите описание изображения здесь

Чтобы быть более точным, кнопка должна отображаться точно как дополнительная последняя вкладка, а не как отдельная кнопка где-то справа от всех строк полосы вкладок.

Я просто ищу общий подход к этому.

Google предлагает множество примеров, но если вы копаете немного глубоко, ни один из них не удовлетворяет всем вышеперечисленным пяти пунктам.

    Почти полное решение с использованием IEditableCollectionView:

     ObservableCollection _items; public ObservableCollection Items { get { if (_items == null) { _items = new ObservableCollection(); var itemsView = (IEditableCollectionView)CollectionViewSource.GetDefaultView(_items); itemsView.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtEnd; } return _items; } } private DelegateCommand _newCommand; public DelegateCommand NewCommand { get { if (_newCommand == null) { _newCommand = new DelegateCommand(New_Execute); } return _newCommand; } } private void New_Execute(object parameter) { Items.Add(new ItemVM()); } 
                    
     public class TemplateSelector : DataTemplateSelector { public DataTemplate ItemTemplate { get; set; } public DataTemplate NewButtonTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item == CollectionView.NewItemPlaceholder) { return NewButtonTemplate; } else { return ItemTemplate; } } } Enter code here 

    Он почти завершен, потому что цикл табуляции не пропускает вкладку «+» и покажет пустое содержимое (что не очень хорошо, но я могу жить с ним, пока не появится лучшее решение …).

    Я использовал модификацию шаблона управления вкладкой и привязку к команде AddNewItemCommand в моей модели представления. XAML :

                                                                             

    Код в соответствующей модели представления выглядит следующим образом:

     public ICommand AddNewItemCommand { get { return new DelegateCommand((param) => { MyItemSource.Add(CreateMyValueViewModel()); }, (param) => MyItemSource != null); } } 

    Обратите внимание: я упаковал TabPanel с помощью StackPanel, чтобы перевернуть кнопку «+» вместе с TabPanel относительно значения свойства « TabStripPlacement ». На ваш взгляд, без наследования и без кода .

    Я считаю, что придумал полное решение, я начал с решения NVM по созданию моего шаблона. И затем ссылается на исходный код DataGrid, чтобы придумать расширенный TabControl, способный добавлять и удалять элементы.

    ExtendedTabControl.cs

     public class ExtendedTabControl : TabControl { public static readonly DependencyProperty CanUserAddTabsProperty = DependencyProperty.Register("CanUserAddTabs", typeof(bool), typeof(ExtendedTabControl), new PropertyMetadata(false, OnCanUserAddTabsChanged, OnCoerceCanUserAddTabs)); public bool CanUserAddTabs { get { return (bool)GetValue(CanUserAddTabsProperty); } set { SetValue(CanUserAddTabsProperty, value); } } public static readonly DependencyProperty CanUserDeleteTabsProperty = DependencyProperty.Register("CanUserDeleteTabs", typeof(bool), typeof(ExtendedTabControl), new PropertyMetadata(true, OnCanUserDeleteTabsChanged, OnCoerceCanUserDeleteTabs)); public bool CanUserDeleteTabs { get { return (bool)GetValue(CanUserDeleteTabsProperty); } set { SetValue(CanUserDeleteTabsProperty, value); } } public static RoutedUICommand DeleteCommand { get { return ApplicationCommands.Delete; } } public static readonly DependencyProperty NewTabCommandProperty = DependencyProperty.Register("NewTabCommand", typeof(ICommand), typeof(ExtendedTabControl)); public ICommand NewTabCommand { get { return (ICommand)GetValue(NewTabCommandProperty); } set { SetValue(NewTabCommandProperty, value); } } private IEditableCollectionView EditableItems { get { return (IEditableCollectionView)Items; } } private bool ItemIsSelected { get { if (this.SelectedItem != CollectionView.NewItemPlaceholder) return true; return false; } } private static void OnCanExecuteDelete(object sender, CanExecuteRoutedEventArgs e) { ((ExtendedTabControl)sender).OnCanExecuteDelete(e); } private static void OnCanUserAddTabsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((ExtendedTabControl)d).UpdateNewItemPlaceholder(); } private static void OnCanUserDeleteTabsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // The Delete command needs to have CanExecute run. CommandManager.InvalidateRequerySuggested(); } private static object OnCoerceCanUserAddTabs(DependencyObject d, object baseValue) { return ((ExtendedTabControl)d).OnCoerceCanUserAddOrDeleteTabs((bool)baseValue, true); } private static object OnCoerceCanUserDeleteTabs(DependencyObject d, object baseValue) { return ((ExtendedTabControl)d).OnCoerceCanUserAddOrDeleteTabs((bool)baseValue, false); } private static void OnExecutedDelete(object sender, ExecutedRoutedEventArgs e) { ((ExtendedTabControl)sender).OnExecutedDelete(e); } private static void OnSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (e.NewValue == CollectionView.NewItemPlaceholder) { var tc = (ExtendedTabControl)d; tc.Items.MoveCurrentTo(e.OldValue); tc.Items.Refresh(); } } static ExtendedTabControl() { Type ownerType = typeof(ExtendedTabControl); DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(typeof(ExtendedTabControl))); SelectedItemProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(OnSelectionChanged)); CommandManager.RegisterClassCommandBinding(ownerType, new CommandBinding(DeleteCommand, new ExecutedRoutedEventHandler(OnExecutedDelete), new CanExecuteRoutedEventHandler(OnCanExecuteDelete))); } protected virtual void OnCanExecuteDelete(CanExecuteRoutedEventArgs e) { // User is allowed to delete and there is a selection. e.CanExecute = CanUserDeleteTabs && ItemIsSelected; e.Handled = true; } protected virtual void OnExecutedDelete(ExecutedRoutedEventArgs e) { if (ItemIsSelected) { int indexToSelect = -1; object currentItem = e.Parameter ?? this.SelectedItem; if (currentItem == this.SelectedItem) indexToSelect = Math.Max(this.Items.IndexOf(currentItem) - 1, 0); if (currentItem != CollectionView.NewItemPlaceholder) EditableItems.Remove(currentItem); if (indexToSelect != -1) { // This should focus the row and bring it into view. SetCurrentValue(SelectedItemProperty, this.Items[indexToSelect]); } } e.Handled = true; } protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { base.OnItemsSourceChanged(oldValue, newValue); CoerceValue(CanUserAddTabsProperty); CoerceValue(CanUserDeleteTabsProperty); UpdateNewItemPlaceholder(); } protected override void OnSelectionChanged(SelectionChangedEventArgs e) { if (Keyboard.FocusedElement is TextBox) Keyboard.FocusedElement.RaiseEvent(new RoutedEventArgs(LostFocusEvent)); base.OnSelectionChanged(e); } private bool OnCoerceCanUserAddOrDeleteTabs(bool baseValue, bool canUserAddTabsProperty) { // Only when the base value is true do we need to validate // that the user can actually add or delete rows. if (baseValue) { if (!this.IsEnabled) { // Disabled TabControls cannot be modified. return false; } else { if ((canUserAddTabsProperty && !this.EditableItems.CanAddNew) || (!canUserAddTabsProperty && !this.EditableItems.CanRemove)) { // The collection view does not allow the add or delete action. return false; } } } return baseValue; } private void UpdateNewItemPlaceholder() { var editableItems = EditableItems; if (CanUserAddTabs) { // NewItemPlaceholderPosition isn't a DP but we want to default to AtEnd instead of None // (can only be done when canUserAddRows becomes true). This may override the users intent // to make it None, however they can work around this by resetting it to None after making // a change which results in canUserAddRows becoming true. if (editableItems.NewItemPlaceholderPosition == NewItemPlaceholderPosition.None) editableItems.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtEnd; } else { if (editableItems.NewItemPlaceholderPosition != NewItemPlaceholderPosition.None) editableItems.NewItemPlaceholderPosition = NewItemPlaceholderPosition.None; } // Make sure the newItemPlaceholderRow reflects the correct visiblity TabItem newItemPlaceholderTab = (TabItem)ItemContainerGenerator.ContainerFromItem(CollectionView.NewItemPlaceholder); if (newItemPlaceholderTab != null) newItemPlaceholderTab.CoerceValue(VisibilityProperty); } } 

    CustomStyleSelector.cs

     internal class CustomStyleSelector : StyleSelector { public Style NewItemStyle { get; set; } public override Style SelectStyle(object item, DependencyObject container) { if (item == CollectionView.NewItemPlaceholder) return NewItemStyle; else return Application.Current.FindResource(typeof(TabItem)) as Style; } } 

    TemplateSelector.cs

     internal class TemplateSelector : DataTemplateSelector { public DataTemplate ItemTemplate { get; set; } public DataTemplate NewItemTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item == CollectionView.NewItemPlaceholder) return NewItemTemplate; else return ItemTemplate; } } 

    Generic.xaml

                     

    Определите контрольную таблицу TabControl следующим образом:

        

    Верхняя строка в сетке будет TabPanel, но вы можете поместить ее в StackPanel с помощью кнопки, следующей за TabPanel, и нарисуйте кнопку, чтобы она выглядела как вкладка.

    Теперь кнопка создаст новый TabItem (возможно, ваш пользовательский) и добавит его в ObservableCollection из вкладок, которые у вас есть как Itemssource для вашего TabControl.

    2 и 3). Он должен всегда появляться в конце, и это не вкладка, так что, надеюсь, не будет частью цикла табуляции

    4) Ну, ваш TabControl должен использовать ObservableCollection TabItems в качестве Itemssource для уведомления, когда новый добавляется / удаляется

    Некоторый код:

    Файл usercontrol .cs NewTabButton

     public partial class NewTabButton : TabItem { public NewTabButton() { InitializeComponent(); Header = "+"; } } 

    И главное окно:

     public partial class Window1 : Window { public ObservableCollection Tabs { get; set; } public Window1() { InitializeComponent(); Tabs = new ObservableCollection(); for (int i = 0; i < 20; i++) { TabItem tab = new TabItem(); tab.Header = "TabNumber" + i.ToString(); Tabs.Add(tab); } Tabs.Add(new NewTabButton()); theTabs.ItemsSource = Tabs; } } 

    Теперь нам нужно найти способ, чтобы он всегда отображался справа внизу, а также добавлял к нему событие и стиль (знак плюса есть в качестве заполнителя).

    Вероятно, это будет лучше в качестве комментария к собственному решению @ NVM; но у меня нет комментариев, чтобы прокомментировать …

    Если вы пытаетесь использовать принятое решение и не получаете команду add для запуска, то, вероятно, у вас нет пользовательского контроля с именем «parentUserControl».

    Вы можете изменить декларацию TabControl от NVM следующим образом, чтобы она работала:

      

    Очевидно, что это не хорошее имя для управления вкладками :); но я предполагаю, что @NVM имел контекст данных, подключенный к его визуальному дереву, к элементу, соответствующему имени.

    Обратите внимание, что лично я предпочел использовать относительную привязку, изменив следующее:

      

    К этому:

      

    В дополнение к ответу NVM. Я не использую так много шаблонов и селекторов для NewItemPlaceholder. Простое решение без пустого содержимого:

         

    Ctrl + Tab Я решил отключить. Это не так просто, вы должны подписаться на KeyDown на родительском элементе, то есть Window (Ctrl + Shift + Tab также обрабатывается правильно):

      public View() { InitializeComponent(); AddHandler(Keyboard.PreviewKeyDownEvent, (KeyEventHandler)controlKeyDownEvent); } private void controlKeyDownEvent(object sender, KeyEventArgs e) { e.Handled = e.Key == Key.Tab && Keyboard.Modifiers.HasFlag(ModifierKeys.Control); } 
    Давайте будем гением компьютера.