WPF開発メモ - ScrollViewerスライダーが小さすぎる場合の解決策

WPF開発メモ - ScrollViewerスライダーが小さすぎる場合の解決策

ScrollViewer内のコンテンツが長すぎると、スクロールバーのスライダーが非常に小さくなり、クリックしにくくなります。

最終更新 2022/05/13 7:13
流浪g
読了目安 3 分
カテゴリ
WPF
タグ
.NET WPF

一、はじめに

WPFの開発において、ScrollViewerはよく使われるコントロールです。自分のプロジェクトで、ScrollViewer内のコンテンツが長すぎるとスクロールバーのつまみが小さくなり、クリックしにくくなるというフィードバックを受けました。最初はスタイルでつまみの最小値を設定しようとしましたが、効果がありませんでした。そこで別の方法として、元のつまみを非表示にし、代わりにコントロールを追加して間接的にScrollViewerのスクロールを制御することにしました。

二、本文

  1. ここでは以前作成した曲線グラフコントロールを例に示します。曲線グラフのデータが多いと、つまみが非常に小さくなります。これはデフォルトスタイルの場合で、カスタムスタイルの場合はさらに小さくなります。

  1. 曲線グラフの上にCanvasを配置し、Borderをつまみとして追加します。Canvas全体を曲線グラフに重ねているのは、クリックしてドラッグ移動する機能も追加したいからです。そしてScrollViewerのつまみを非表示にし、つまみの最小幅と高さを設定し、デフォルトで非表示にします。
<Grid>
    <local:CruveDrawingVisual x:Name="curve" Margin="0,10,0,15" />
    <ScrollViewer
        Name="scroll"
        HorizontalScrollBarVisibility="Hidden"
        ScrollChanged="ScrollViewer_ScrollChanged"
        VerticalScrollBarVisibility="Disabled">
        <Canvas x:Name="canvas" />
    </ScrollViewer>
    <Canvas x:Name="CurvePanel" Background="Transparent">
        <Border
            x:Name="border"
            Canvas.Left="0"
            Canvas.Bottom="0"
            Height="15"
            MinWidth="80"
            Background="Green"
            PreviewMouseLeftButtonDown="Border_PreviewMouseLeftButtonDown"
            Visibility="Collapsed" />
    </Canvas>
</Grid>
  1. 次にバックエンドで対応するロジックコードを追加します。詳細はコード内のコメントに記載しているので、ここでは省略します。
public partial class MainWindow : Window
{
    private bool isAdd = true;
    private List<int> lists = new List<int>();

    private Point point_border;

    private double offset = -1;

    public MainWindow()
    {
        InitializeComponent();

        CurvePanel.MouseMove += delegate (object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (Mouse.Captured == null) Mouse.Capture(CurvePanel);

                // つまみをドラッグ
                if (isBorder)
                {
                    Point point = e.GetPosition(this);
                    // マウスがコントロールの左端を超えた場合
                    if (point.X - point_border.X <= 0)
                    {
                        scroll.ScrollToHorizontalOffset(0);
                    }
                    // マウスがコントロールの右端を超えた場合
                    else if (point.X - point_border.X >= CurvePanel.ActualWidth - border.ActualWidth)
                    {
                        scroll.ScrollToHorizontalOffset(lists.Count - CurvePanel.ActualWidth);
                    }
                    // マウスがコントロール範囲内の場合
                    else if (point.X - point_border.X > 0 && point.X - point_border.X < CurvePanel.ActualWidth - border.ActualWidth)
                    {
                        double left = point.X - point_border.X;
                        scroll.ScrollToHorizontalOffset((lists.Count - CurvePanel.ActualWidth) / (CurvePanel.ActualWidth - border.ActualWidth) * left);
                    }
                }
                // キャンバスをドラッグ
                else
                {
                    if (offset >= 0 && offset <= CurvePanel.ActualWidth)
                    {
                        scroll.ScrollToHorizontalOffset(scroll.HorizontalOffset - (e.GetPosition(this).X - offset));
                    }
                    offset = e.GetPosition(this).X;
                }
            }
            else
            {
                offset = -1;
                isBorder = false;
                Mouse.Capture(null); // マウスキャプチャを解放
            }
        };
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        int temp = 20;
        for (int i = 0; i < 24 * 60 * 60 * 2; i++)
        {
            if (isAdd)
            {
                lists.Add(temp);
                temp += 2;
            }
            else
            {
                lists.Add(temp);
                temp -= 2;
            }

            if (temp == 280) isAdd = false;
            if (temp == 20) isAdd = true;
        }
        canvas.Width = lists.Count;
        // つまみを表示するか判定
        if (canvas.Width > CurvePanel.ActualWidth)
        {
            border.Visibility = Visibility.Visible;
            // ScrollViewerのコンテンツの比率に基づいてつまみの幅を計算
            border.Width = CurvePanel.ActualWidth * CurvePanel.ActualWidth / canvas.Width;
        }
        else
        {
            border.Visibility = Visibility.Collapsed;
        }
        curve.SetupData(lists);
    }

    private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        curve.OffsetX(scroll.HorizontalOffset);

        Canvas.SetLeft(border, scroll.HorizontalOffset / ((lists.Count - CurvePanel.ActualWidth) / (CurvePanel.ActualWidth - border.ActualWidth)));
    }

    private bool isBorder = false;
    private void Border_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        isBorder = true;
        // マウスがつまみをクリックした位置を取得
        point_border = e.GetPosition(border);
    }
}
  1. 実行結果は以下の通りです。

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2025/09/13

WPF から Avalonia への移行シリーズ:なぜ WPF プログラムを Avalonia に移行しなければならないのか

過去数年間、当社の上位機ソフトウェアは主に WPF と WinForm で開発されてきました。これらの技術は Windows プラットフォームで非常に便利であり、小規模試作から現在の規模拡大による納品まで、私たちを支えてきました。しかし、ビジネスの発展や顧客ニーズの変化に伴い、単一の Windows テクノロジースタックは私たちが必ず乗り越えなければならない壁となってきました。

続きを読む
同じカテゴリ / 同じタグ 2025/01/26

WPF カスタムXMLファイルによる国際化

この記事では、WPFプログラムでカスタムXMLファイルを使用して国際化を実現する方法について詳しく説明します。必要なNuGetパッケージのインストール、言語リストの動的取得、言語の動的切り替え、コードおよびXAMLインターフェースでの翻訳文字列の使用などを含み、ソースコードのリンクも提供し、開発者がWPFアプリケーションの国際化を簡単に実装できるように支援します。

続きを読む