午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPF、ScrollViewer内で見えない要素を、見える位置まで自動スクロール調節するにはBringIntoView

昨日の続き?

gogowaten.hatenablog.com



BringIntoViewなし

Thumbを移動させてもスクロール位置が変化しないので見えなくなってしまう

BringIntoViewを使っていない場合


BringIntoViewあり

Thumbの移動後にBringIntoViewを実行しているので、Thumbの位値に合わせてスクロール位置が変化している

BringIntoView!!!!!!!!


FrameworkElement.BringIntoView メソッド (System.Windows) | Microsoft Learn learn.microsoft.com より

この要素が含まれているスクロール可能な領域内に、この要素を表示することを試みます。



テストアプリのコード

2024WPF/20241218_ScrollViewer_BringIntoView at master · gogowaten/2024WPF github.com


環境



MainWindow.xaml

<Window x:Class="_20241218_ScrollViewer_BringIntoView.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:_20241218_ScrollViewer_BringIntoView"
        mc:Ignorable="d"
        Title="MainWindow" Height="280" Width="440">
  <Window.Resources>
    <Style TargetType="Thumb">
      <Setter Property="Width" Value="100"/>
      <Setter Property="Height" Value="100"/>
      <Setter Property="Focusable" Value="True"/>
      <EventSetter Event="DragDelta" Handler="Thumb_DragDelta"/>
      <EventSetter Event="KeyDown" Handler="Thumb_KeyDown"/>
      <EventSetter Event="KeyUp" Handler="Thumb_KeyUp"/>
      <EventSetter Event="PreviewMouseDown" Handler="Thumb_PreviewMouseDown"/>
      <EventSetter Event="PreviewMouseUp" Handler="Thumb_PreviewMouseUp"/>
    </Style>
  </Window.Resources>
  
  <Grid Background="PaleGreen">
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition Width="120"/>
    </Grid.ColumnDefinitions>
    
    <ScrollViewer x:Name="MyScroll"
                  HorizontalScrollBarVisibility="Auto"
                  VerticalScrollBarVisibility="Auto">
      <Canvas x:Name="MyCanvas" Width="500" Height="500" Background="ForestGreen"
              KeyboardNavigation.TabNavigation="Cycle">        
        <Thumb Canvas.Left="50" Canvas.Top="50"/>
        <Thumb Canvas.Left="160" Canvas.Top="100"/>
      </Canvas>
    </ScrollViewer>
    
    <Grid Grid.Column="1" Margin="20">
      <Grid.Background>
        <VisualBrush Visual="{Binding ElementName=MyCanvas}" Stretch="Uniform"/>
      </Grid.Background>
    </Grid>
  </Grid>
</Window>

Canvasで指定しているKeyboardNavigation.TabNavigation="Cycle"は
タブキーでのフォーカス移動を自身の子要素内で循環させる
gogowaten.hatenablog.com




MainWindow.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace _20241218_ScrollViewer_BringIntoView
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// マウスドラッグ移動
        /// </summary>
        private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            if (sender is Thumb t)
            {
                Canvas.SetLeft(t, Canvas.GetLeft(t) + e.HorizontalChange);
                Canvas.SetTop(t, Canvas.GetTop(t) + e.VerticalChange);
                e.Handled = true;
            }
        }

        /// <summary>
        /// 方向キーの方向へ10ピクセル移動
        /// </summary>
        private void Thumb_KeyDown(object sender, KeyEventArgs e)
        {
            if (sender is Thumb t)
            {
                if (e.Key == Key.Left)
                {
                    Canvas.SetLeft(t, Canvas.GetLeft(t) - 10);
                    e.Handled = true;
                }
                else if (e.Key == Key.Right)
                {
                    Canvas.SetLeft(t, Canvas.GetLeft(t) + 10);
                    e.Handled = true;
                }
                else if (e.Key == Key.Up)
                {
                    Canvas.SetTop(t, Canvas.GetTop(t) - 10);
                    e.Handled = true;
                }
                else if (e.Key == Key.Down)
                {
                    Canvas.SetTop(t, Canvas.GetTop(t) + 10);
                    e.Handled = true;
                }

            }
        }

        /// <summary>
        /// キーが離されたとき
        /// BringIntoViewを実行することで、Thumbが見える位置までスクロールする
        /// </summary>
        private void Thumb_KeyUp(object sender, KeyEventArgs e)
        {
            if (sender is Thumb t)
            {
                t.BringIntoView();
            }
        }

        /// <summary>
        /// マウスボタン押したとき、
        /// フォーカスを無効化することで、
        /// Thumbの表示位置(スクロール位置)と
        /// マウスカーソルの位置に違和感がなくなり、ドラッグ移動動作も自然になる
        /// </summary>
        private void Thumb_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (sender is Thumb t)
            {
                t.Focusable = false;
            }
        }

        /// <summary>
        /// マウスボタンを離したとき
        /// フォーカスを有効化してからフォーカスする
        /// </summary>
        private void Thumb_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (sender is Thumb t)
            {
                t.Focusable = true;
                t.Focus();
            }
        }
    }
}

KeyUp時にBringIntoView

PreviewMouseDownとPreviewMouseUpでの処理は昨日の記事から



参照したところ

wpf コントロール - WPF ScrollViewer が認識されたコンテンツに自動的にスクロールするのを停止する - Stack Overflow

stackoverflow.com ここでBringIntoViewってのがあることを知った
目的は今回の逆だけど、スクロールの制御って面では同じ


感想

知っていればなんてことないけど、その存在を知るまでが難しかった



関連記事

次のWPF記事
WPFCanvasの拡大表示時の子要素のドラッグ移動距離とぼやけ表示の解消 - 午後わてんのブログ

gogowaten.hatenablog.com

13日後
WPF、この一ヶ月でのカスタムコントロールThumbのマウスドラッグ移動のまとめ - 午後わてんのブログ
gogowaten.hatenablog.com