MahApps.MetroのMetroTabControl
が、TabItem
のキャッシュに対応していました(ちゃんとやるとなるとxamlも長くなるし、正しく書く自信もないのでどうしようかと思っていたところでした^^;
NuGet Gallery | MahApps.Metro 2.4.3
もしくはMetroTabControl
が依存している、ControlzExのTabControlEx
でもいいと思います(未確認)
NuGet Gallery | ControlzEx 4.4.0
TabControlEx
やっていることは↓とほぼ同じなので、ライブラリを使用したくない場合はこれをベースにしてください(ちょっと甘いところがあるので要調整)
c# - Stop TabControl from recreating its children - Stack Overflow
次はフォーカスですが↓はベタ置き専用になっていますので、ItemsSource
と両対応&添付ビヘイビア化しました。
TabControlでのTab切り替え時フォーカス設定
簡単に試した限りは動いていますが、突っ込んだテストはしていません(間違えるほど大したことはしていないので、大丈夫だとは思いますが^^;
xml
1<Window
2 x:Class="Questions318528.MainWindow"
3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5 xmlns:local="clr-namespace:Questions318528"
6 xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
7 Width="800"
8 Height="450">
9 <Grid>
10 <mah:MetroTabControl
11 local:TabItemKeepFocusBehavior.Use="True"
12 ItemsSource="{Binding Tabs}"
13 KeepVisualTreeInMemoryWhenChangingTabs="True">
14 <TabControl.Resources>
15 <DataTemplate DataType="{x:Type local:TabItem1}">
16 <ScrollViewer>
17 <StackPanel>
18 <TextBlock Text="View1" />
19 <TextBox />
20 <Image Source="https://teratail-v2.storage.googleapis.com/uploads/avatars/u13/132786/KnkDDC5A_thumbnail.jpg" />
21 <TextBox />
22 <Image Source="https://teratail-v2.storage.googleapis.com/uploads/avatars/u13/132786/KnkDDC5A_thumbnail.jpg" />
23 <TextBox />
24 </StackPanel>
25 </ScrollViewer>
26 </DataTemplate>
27 <DataTemplate DataType="{x:Type local:TabItem2}">
28 <ScrollViewer>
29 <StackPanel>
30 <TextBlock Text="View2" />
31 <TextBox />
32 <Image Source="https://teratail-v2.storage.googleapis.com/uploads/avatars/u13/132786/KnkDDC5A_thumbnail.jpg" />
33 <TextBox />
34 <Image Source="https://teratail-v2.storage.googleapis.com/uploads/avatars/u13/132786/KnkDDC5A_thumbnail.jpg" />
35 <TextBox />
36 </StackPanel>
37 </ScrollViewer>
38 </DataTemplate>
39 </TabControl.Resources>
40 <TabControl.ItemTemplate>
41 <DataTemplate>
42 <TextBlock Text="header" />
43 </DataTemplate>
44 </TabControl.ItemTemplate>
45 </mah:MetroTabControl>
46 </Grid>
47</Window>
cs
1using System;
2using System.Collections.ObjectModel;
3using System.Windows;
4using System.Windows.Controls;
5using System.Windows.Input;
6using System.Windows.Threading;
7
8namespace Questions318528
9{
10 public class TabItem1 { }
11 public class TabItem2 { }
12
13 public partial class MainWindow : Window
14 {
15 public ObservableCollection<object> Tabs { get; }
16 public MainWindow()
17 {
18 InitializeComponent();
19 DataContext = this;
20
21 Tabs = new()
22 {
23 new TabItem1(),
24 new TabItem2(),
25 "aaa",
26 };
27 }
28 }
29
30 // [TabControlでのTab切り替え時フォーカス設定](https://social.msdn.microsoft.com/Forums/ja-JP/e1f77d05-3358-4d63-8810-165300b22f24/tabcontrol1239112398tab209991242626367123602617812501124571254012459?forum=wpfja)
31 // を参考に添付ビヘイビア化
32 internal class TabItemKeepFocusBehavior
33 {
34 // 内部使用添付プロパティ TabItemに添付 フォーカスがあったエレメント
35 private static readonly DependencyProperty FocusedProperty
36 = DependencyProperty.RegisterAttached("Focused", typeof(IInputElement), typeof(TabItemKeepFocusBehavior),
37 new PropertyMetadata(null));
38 private static IInputElement GetFocused(DependencyObject target) => (IInputElement)target.GetValue(FocusedProperty);
39 private static void SetFocused(DependencyObject target, IInputElement value) => target.SetValue(FocusedProperty, value);
40
41 // 添付ビヘイビア TabControlに添付 フォーカス戻しを使用するかどうか
42 public static readonly DependencyProperty UseProperty
43 = DependencyProperty.RegisterAttached("Use", typeof(bool), typeof(TabItemKeepFocusBehavior),
44 new PropertyMetadata(false, OnUseChanged));
45 public static bool GetUse(DependencyObject target) => (bool)target.GetValue(UseProperty);
46 public static void SetUse(DependencyObject target, bool value) => target.SetValue(UseProperty, value);
47
48 private static void OnUseChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
49 {
50 if (sender is TabControl tabControl)
51 {
52 if ((bool)e.NewValue)
53 {
54 tabControl.PreviewGotKeyboardFocus += TabControl_PreviewGotKeyboardFocus;
55 tabControl.SelectionChanged += TabControl_SelectionChanged;
56 }
57 else
58 {
59 tabControl.PreviewGotKeyboardFocus -= TabControl_PreviewGotKeyboardFocus;
60 tabControl.SelectionChanged -= TabControl_SelectionChanged;
61 }
62 }
63 }
64
65 // フォーカスを得るたびにFocusedにセット
66 private static void TabControl_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
67 {
68 if (sender is TabControl tabControl)
69 {
70 if (e.NewFocus is TabItem) return;
71
72 var tabItem = tabControl.SelectedItem as TabItem ?? tabControl.ItemContainerGenerator.ContainerFromItem(tabControl.SelectedItem) as TabItem;
73 if (tabItem == null) return;
74
75 if (e.NewFocus is IInputElement element)
76 {
77 SetFocused(tabItem, element);
78 }
79 }
80 }
81
82 // 選択が変わったときにFocusedにフォーカス
83 private static void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
84 {
85 if (sender is TabControl tabControl)
86 {
87 var tabItem = tabControl.SelectedItem as TabItem ?? tabControl.ItemContainerGenerator.ContainerFromItem(tabControl.SelectedItem) as TabItem;
88 if (tabItem == null) return;
89
90 if (GetFocused(tabItem) is IInputElement element)
91 {
92 Action action = () => element.Focus();
93 tabControl.Dispatcher.BeginInvoke(action, DispatcherPriority.Input);
94 }
95 }
96 }
97 }
98}
特に指定がないので.NET 5.0で書きました^^
.NET Framework 4.8等では、new()でエラーが出るので型を書いてください。
Target-typed new expressions - C# 9.0 specification proposals | Microsoft Docs
ターゲットからの new 型推論 | ++C++; // 未確認飛行 C