I'm facing an issue where I need to be able to actually focus a TabItem
tab in a TabControl
like I would press the TAB
key. I know I could use SendKeys.SendWait("{TAB}");
in order to achieve that but I would prefer a more robust solution.
The tab items are bound to an ItemSource
of an ObservableCollection
. That collection contains a list of view models later bound to the tab items title, icon, content and more. The collection is filled and emptied at runtime, I don't have a static collection.
I've made a sample project to illustrate the issue (see code below).
Current behavior after I added a tab item to the collection looks like this:
After pressing the "Add Tab" button on the left a new tab is added to the ObservableCollection
which is then reflected in a new tab item in the tab control.
I try to focus that new tab with:
- Set the
SelectedItem
of theTabControl
after a new item is added (see code below) Keyboard.Focus((UIElement)sender)
in the tab item loaded event (see code below)- by setting
FocusManager.FocusedElement="{Binding ElementName=tabHeader}"
in XAML (see code below)
None of those attempts seems to work.
Desired behavior would be that the new added tab item actually get focused (like I would press the TAB
key) which would look like this:
To create this image I pressed TAB
after I added a new tab item.
I couldn't find any working solution here or here and also Microsoft's documentation regarding Focus didn't shed enough light.
Any hint is appreciated.
To reproduce the screenshots above here is my sample project:
MainWindow.xaml
<Window x:Class="WpfAppTabControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppTabControl"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainViewViewModel}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Grid.Row="2" Margin="50,50,50,50" Command="{Binding AddTabCommand}">Add Tab</Button>
<GridSplitter Grid.Column="1" Grid.Row="2" ResizeDirection="Columns" ResizeBehavior="PreviousAndNext" HorizontalAlignment="Stretch"/>
<TabControl Grid.Column="2"
Grid.Row="2"
ItemsSource="{Binding ItemsToDisplay}"
SelectedItem="{Binding ActiveTabItem}"
SelectionChanged="OnSelectionChanged"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
FocusManager.FocusedElement="{Binding ElementName=tabHeader}"
FocusManager.IsFocusScope="True">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<EventSetter Event="Loaded" Handler="OnTabItemLoaded"/>
<Setter Property="IsSelected" Value="True"/>
<Setter Property="Focusable" Value="True"/>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<DockPanel x:Name="tabHeader" Margin="-5,-1,-5,0" Focusable="True">
<Button x:Name="closeButton"
Width="16"
Height="16"
Margin="20, 10, 10, 10"
Command="{Binding CloseTabCommand}"
CommandParameter="{Binding Title}"
BorderBrush="Transparent"
DockPanel.Dock="Right"
BorderThickness="0">
<Image x:Name="tabIcon" Source="/close.png" Height="16" Margin="0,0,0,0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
<TextBlock Margin="5, 10, 10, 10" VerticalAlignment="Center" Text="{Binding Title}"/>
</DockPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Content" Value="{Binding Content}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfAppTabControl
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewViewModel();
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var viewModel = (MainViewViewModel)DataContext;
viewModel.NotifySelectedItemChanged(e.AddedItems);
}
private void OnTabItemLoaded(object sender, RoutedEventArgs e)
{
Keyboard.Focus((UIElement)sender);
}
}
}
ItemViewModel.cs
using Prism.Mvvm;
using System;
using System.Windows.Input;
namespace WpfAppTabControl
{
public class ItemViewModel : BindableBase, IDisposable
{
private string title;
private string content;
public ItemViewModel(ICommand closeTabCommand)
{
CloseTabCommand = closeTabCommand ?? throw new ArgumentNullException(nameof(closeTabCommand));
}
public string Title
{
get => title;
set
{
if (title != value)
{
title = value;
RaisePropertyChanged(title);
}
}
}
public string Content
{
get => content;
set
{
if (content != value)
{
content = value;
RaisePropertyChanged(content);
}
}
}
public ICommand CloseTabCommand
{
get;
private set;
}
public void Dispose()
{
// disposing stuff
}
}
}
MainViewViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
namespace WpfAppTabControl
{
public class MainViewViewModel : BindableBase
{
private readonly ObservableCollection<ItemViewModel> itemsToDisplay;
private ItemViewModel activeTabItem;
public MainViewViewModel()
{
itemsToDisplay = new ObservableCollection<ItemViewModel>();
AddTabCommand = new DelegateCommand(AddTab);
}
public IEnumerable<ItemViewModel> ItemsToDisplay => itemsToDisplay;
public ICommand AddTabCommand
{
get;
private set;
}
public ItemViewModel ActiveTabItem
{
get => activeTabItem;
set
{
if (activeTabItem != value)
{
activeTabItem = value;
RaisePropertyChanged();
}
}
}
private void AddTab()
{
var tabCountString = (itemsToDisplay.Count + 1).ToString();
var itemViewModel1 = new ItemViewModel(new DelegateCommand<string>(title => { CloseTab(title); }))
{
Title = "NewTab" + tabCountString,
Content = "Content of " + "NewTab" + tabCountString,
};
itemsToDisplay.Add(itemViewModel1);
ActiveTabItem = itemViewModel1;
}
private void CloseTab(string title)
{
if (string.IsNullOrEmpty(title))
{
return;
}
var tabToClose = itemsToDisplay.FirstOrDefault(viewModel => viewModel.Title == title);
if (tabToClose != null)
{
itemsToDisplay.Remove(tabToClose);
}
}
public void NotifySelectedItemChanged(IList addedItems)
{
if (addedItems.Count > 0)
{
// stuff to do
}
}
}
}