TabControl com o botão Add New Tab (+)

Qual é a maneira correta de adicionar uma guia de botão ‘+’ no final de todos os itens de guia na faixa de guia de um controle de guia no WPF?

  1. Deve funcionar corretamente com várias linhas de header de guia.
  2. Deve estar no final de todos os itens da guia
  3. O ciclismo de tabulação deve funcionar corretamente ( Alt + Tab ), ou seja, a guia + deve ser ignorada.
  4. Eu não deveria ter que modificar a coleção de fonts a qual estou ligando. Ou seja, o controle deve ser reutilizável.
  5. A solução deve funcionar com o MVVM

Digite a descrição da imagem aqui

insira a descrição da imagem aqui

Para ser mais preciso, o botão deve aparecer exatamente como uma última guia adicional e não como um botão separado em algum lugar à direita de todas as linhas de faixa de guias.

Eu estou apenas procurando a abordagem geral para fazer isso.

Google lança muitos exemplos, mas se você cavar um pouco profundo nenhum deles satisfaz todos os cinco pontos acima.

Uma solução quase completa usando o 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 

Está quase completo, porque o ciclo de tabulação não pula a aba ‘+’, e mostra conteúdo vazio (o que não é exatamente ótimo, mas eu posso viver com ele até que uma solução melhor apareça …).

Eu usei uma modificação do modelo de controle de guia e binding para o comando AddNewItemCommand no meu modelo de exibição. XAML :

                                                                         

Código no modelo de visualização relevante se parece com isto:

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

Preste atenção: eu embrulhei o TabPanel pelo StackPanel para virar o botão “+” junto com o TabPanel em relação ao valor da propriedade ” TabStripPlacement “. Sem inheritance e sem code-behind na sua opinião.

Acredito ter uma solução completa, comecei com a solução da NVM para criar meu modelo. Em seguida, referenciei o código-fonte DataGrid para criar um TabControl estendido capaz de adicionar e remover itens.

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

                 

Defina o ControlTemplate do TabControl assim:

    

A linha superior na grade seria o TabPanel, mas você poderia colocar isso em um StackPanel com um botão após o TabPanel e estilizar o botão para se parecer com uma guia.

Agora, o botão criaria um novo TabItem (talvez você criasse um personalizado) e o adicionaria ao ObservableCollection de Tabs que você tem como Itemssource para o seu TabControl.

2 e 3) Ele deve sempre aparecer no final, e não é uma tabulação, portanto, espero que não faça parte do ciclo de tabulação

4) Bem, o seu TabControl deve usar um ObservableCollection de TabItems como Itemssource para ser notificado quando um novo é adicionado / removido

Algum código:

O arquivo newTabButton usercontrol .cs

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

E a janela principal:

 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; } } 

Agora, precisaríamos encontrar uma maneira de deixá-lo sempre aparecer no canto inferior direito e também adicionar o evento e o estilo para ele (o sinal de mais está lá como um espaço reservado).

Isso provavelmente seria melhor como um comentário sobre a solução da própria @ NVM; mas eu não tenho o representante para comentar ainda …

Se você está tentando usar a solução aceita e não obtendo o comando add para acionar, provavelmente você não tem um usercontrol chamado “parentUserControl”.

Você pode alterar a declaração TabControl do @ NVM da seguinte forma para que funcione:

  

Obviamente, não é um bom nome para dar um controle de tabulação :); mas eu acho que @NVM tinha o contexto de dados ligado à sua tree visual para um elemento para combinar com o nome.

Observe que, pessoalmente, preferi usar uma binding relativa alterando o seguinte:

  

Para isso:

  

Além da resposta da NVM. Eu não uso tantos modelos e seletores para NewItemPlaceholder. Solução mais fácil sem conteúdo vazio:

     

Ctrl + Tab eu quis desabilitar. Não é tão fácil, você deve se inscrever em KeyDown no elemento pai, ou seja, janela (Ctrl + Shift + Tab também manipulado corretamente):

  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); } 
    Intereting Posts