TabControl 内の ScrollViewer が勝手にスクロールされる
◆ タブ切り替え時に TabItem の中の要素にフォーカスされる
◆ ScrollViewer の中の要素にフォーカスされると ScrollViewer がスクロールされる
◆ 対策は ダミーのフォーカス可能要素を TabItem の最初においておくのがよさそう
◆ Tab でフォーカス切替時に邪魔ならスキップする処理を入れておく
◆ ScrollViewer の中の要素にフォーカスされると ScrollViewer がスクロールされる
◆ 対策は ダミーのフォーカス可能要素を TabItem の最初においておくのがよさそう
◆ Tab でフォーカス切替時に邪魔ならスキップする処理を入れておく
TabControl でタブを作ってその中に ScrollViewer があると 勝手に下の方までスクロールされていました
原因はタブを切り替えるとそのタブの中の最初の要素にフォーカスが当たるから
ScrollViewer の中にはボタンがあって ボタンにフォーカスが当たることでボタンが見える位置までスクロールされていました
切り替え前の最初に画面を表示したときのタブではスクロールされていません
タブを切り替えると切り替え先のタブ内の ScrollViewer がスクロールされます
ただでさえ 最初から中途半端なところにスクロールあると気持ち悪いのに タブを切り替えるたびにボタンが見える位置までスクロールされて前回の位置が保持されないのは使いづらいです
この Window を開いてタブを切り替えるだけで再現できます
そのタブに ScrollViewer があるかないかもわからないですし どこにあるか構造も統一されていないです
複数あった場合にはフォーカスされたものだけを対象にしないといけないですし すでに開いたことのあるタブなら一番上じゃなくて前回のスクロール場所に戻すべきですからスクロールをキャンセルとできないなら 前の値を保持して手動で再設定することになります
さすがにこれはやりたくないので別のアプローチを考えます
フォーカスによってスクロールされたのを戻す
ではなく
フォーカスされないようにする
です
これなら ScrollViewer や TabControl を拡張しないで済みそうです
方法ですが そもそもなぜスクロールするかは ボタンなどのフォーカス出来る要素がスクロールしたところにあるからです
スクロールの必要のない上の部分 というか TabItem の中の ScrollViewer の外側かつ ScollViewer より前にフォーカス出来る要素があればそっちをフォーカスしてくれるはずです
TabItem をこれから
こうします
フォーカス可能な Control をおいてます
Width と Height には 0 を指定して見えないようにしておきます
ScrollViewer のほうが前面ですが フォーカス時の点線は見えるので 0 にしておいたほうがいいです
画面上は何もないのに Control の分 一度フォーカスを見失うときがあります
気にならないようなものですが 気にする人もいると思うので Control がフォーカスを受けるとスキップするようにします
画面上では見えないので 通常の使い方ではクリックでフォーカスされることはないです
キーボードでフォーカス切り替えたときだけ発生する問題です
XAML では Control に GotFocus イベントリスナを設定します
スキップする処理はこうなります
Shift キーが押されてるとバックなので前方向 それ以外は後方向の要素をフォーカスさせます
これでおっけい
それにしても TabControl に切替時に内側の最初の要素をフォーカスするかどうかのオプションがほしいですね
デフォルトはタブの中身じゃなくてタブ切り替えのボタン自体をフォーカスしておいてくれればいいのに
原因はタブを切り替えるとそのタブの中の最初の要素にフォーカスが当たるから
ScrollViewer の中にはボタンがあって ボタンにフォーカスが当たることでボタンが見える位置までスクロールされていました
切り替え前の最初に画面を表示したときのタブではスクロールされていません
タブを切り替えると切り替え先のタブ内の ScrollViewer がスクロールされます
ただでさえ 最初から中途半端なところにスクロールあると気持ち悪いのに タブを切り替えるたびにボタンが見える位置までスクロールされて前回の位置が保持されないのは使いづらいです
この Window を開いてタブを切り替えるだけで再現できます
<Window x:Class="w02.Window1"
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:w02"
mc:Ignorable="d"
Title="Window1"
Height="320" Width="480">
<Grid>
<TabControl>
<TabItem Header="1" Padding="20 10">
<Grid></Grid>
</TabItem>
<TabItem Header="2" Padding="20 10">
<ScrollViewer>
<StackPanel>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Button>b</Button>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Button>d</Button>
</StackPanel>
</ScrollViewer>
</TabItem>
</TabControl>
</Grid>
</Window>
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:w02"
mc:Ignorable="d"
Title="Window1"
Height="320" Width="480">
<Grid>
<TabControl>
<TabItem Header="1" Padding="20 10">
<Grid></Grid>
</TabItem>
<TabItem Header="2" Padding="20 10">
<ScrollViewer>
<StackPanel>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Button>b</Button>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Button>d</Button>
</StackPanel>
</ScrollViewer>
</TabItem>
</TabControl>
</Grid>
</Window>
対策
対策をするにもタブ切り替えイベントで ScrollViewer のスクロールポジションを一番上にするというのもちょっと大変ですそのタブに ScrollViewer があるかないかもわからないですし どこにあるか構造も統一されていないです
複数あった場合にはフォーカスされたものだけを対象にしないといけないですし すでに開いたことのあるタブなら一番上じゃなくて前回のスクロール場所に戻すべきですからスクロールをキャンセルとできないなら 前の値を保持して手動で再設定することになります
さすがにこれはやりたくないので別のアプローチを考えます
フォーカスによってスクロールされたのを戻す
ではなく
フォーカスされないようにする
です
これなら ScrollViewer や TabControl を拡張しないで済みそうです
方法ですが そもそもなぜスクロールするかは ボタンなどのフォーカス出来る要素がスクロールしたところにあるからです
スクロールの必要のない上の部分 というか TabItem の中の ScrollViewer の外側かつ ScollViewer より前にフォーカス出来る要素があればそっちをフォーカスしてくれるはずです
TabItem をこれから
<TabItem Header="2" Padding="20 10">
<ScrollViewer>
<StackPanel>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Button>b</Button>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Button>d</Button>
</StackPanel>
</ScrollViewer>
</TabItem>
<ScrollViewer>
<StackPanel>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Button>b</Button>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Button>d</Button>
</StackPanel>
</ScrollViewer>
</TabItem>
こうします
<TabItem Header="2" Padding="20 10">
<Grid>
<Control Width="0" Height="0"></Control>
<ScrollViewer>
<StackPanel>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Button>b</Button>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Button>d</Button>
</StackPanel>
</ScrollViewer>
</Grid>
</TabItem>
<Grid>
<Control Width="0" Height="0"></Control>
<ScrollViewer>
<StackPanel>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Button>b</Button>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Button>d</Button>
</StackPanel>
</ScrollViewer>
</Grid>
</TabItem>
フォーカス可能な Control をおいてます
Width と Height には 0 を指定して見えないようにしておきます
ScrollViewer のほうが前面ですが フォーカス時の点線は見えるので 0 にしておいたほうがいいです
フォーカス
一応完成ですが Control がないものと比べると Tab キーを押してフォーカスを切り替える場合に余計なものが入ります画面上は何もないのに Control の分 一度フォーカスを見失うときがあります
気にならないようなものですが 気にする人もいると思うので Control がフォーカスを受けるとスキップするようにします
画面上では見えないので 通常の使い方ではクリックでフォーカスされることはないです
キーボードでフォーカス切り替えたときだけ発生する問題です
XAML では Control に GotFocus イベントリスナを設定します
<TabItem Header="2" Padding="20 10">
<Grid>
<Control Width="0" Height="0" GotFocus="skipFocus"></Control>
<ScrollViewer>
<StackPanel>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Button>b</Button>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Button>d</Button>
</StackPanel>
</ScrollViewer>
</Grid>
</TabItem>
<Grid>
<Control Width="0" Height="0" GotFocus="skipFocus"></Control>
<ScrollViewer>
<StackPanel>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Label Margin="0 20">a</Label>
<Button>b</Button>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Label Margin="0 20">c</Label>
<Button>d</Button>
</StackPanel>
</ScrollViewer>
</Grid>
</TabItem>
スキップする処理はこうなります
private void skipFocus(object sender, RoutedEventArgs e)
{
var direction = FocusNavigationDirection.Next;
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
direction = FocusNavigationDirection.Previous;
}
var treq = new TraversalRequest(direction);
if (((UIElement)sender).MoveFocus(treq)) e.Handled = true;
}
{
var direction = FocusNavigationDirection.Next;
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
direction = FocusNavigationDirection.Previous;
}
var treq = new TraversalRequest(direction);
if (((UIElement)sender).MoveFocus(treq)) e.Handled = true;
}
Shift キーが押されてるとバックなので前方向 それ以外は後方向の要素をフォーカスさせます
これでおっけい
それにしても TabControl に切替時に内側の最初の要素をフォーカスするかどうかのオプションがほしいですね
デフォルトはタブの中身じゃなくてタブ切り替えのボタン自体をフォーカスしておいてくれればいいのに