画像ビューアを作っていて、ユーザーが画像をドラッグ操作でスクロールできる機能が欲しかったのでビヘイビアを作りました。
環境
.NET 10、C#、WPF
必要なもの
NuGetパッケージ
- Microsoft.Xaml.Behaviors.Wpf
コード
using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace Common.Behaviors
{
/// <summary>
/// DragScrollBehavior は ScrollViewer の拡張です。
/// DragScrollBehavior を関連付けた ScrollViewer はユーザーがドラッグ操作でコンテンツをスクロールできます。
/// </summary>
public class DragScrollBehavior : Behavior<ScrollViewer>
{
// スクロールの方向を逆にするかどうかを指定します。
private const bool ScrollDirectionReversed = true;
// コンテンツではなくビューポートのスケールを使用するかどうかを指定します。
private const bool UseViewportScale = true;
private Point? _startPosition;
private double _startHorizontalOffset;
private double _startVerticalOffset;
private double _scrollDirectionFactor = ScrollDirectionReversed ? -1 : 1;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseLeftButtonDown += ScrollViewer_PreviewMouseLeftButtonDown;
AssociatedObject.PreviewMouseLeftButtonUp += ScrollViewer_PreviewMouseLeftButtonUp;
AssociatedObject.PreviewMouseMove += ScrollViewer_PreviewMouseMove;
AssociatedObject.LostMouseCapture += ScrollViewer_LostMouseCapture;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewMouseLeftButtonDown -= ScrollViewer_PreviewMouseLeftButtonDown;
AssociatedObject.PreviewMouseLeftButtonUp -= ScrollViewer_PreviewMouseLeftButtonUp;
AssociatedObject.PreviewMouseMove -= ScrollViewer_PreviewMouseMove;
AssociatedObject.LostMouseCapture -= ScrollViewer_LostMouseCapture;
}
private void ScrollViewer_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// スクロールバーの使用を邪魔しないようにスクロールバー上では開始しません。
if ((e.OriginalSource is not DependencyObject obj)
|| (FindScrollViewerOrScrollBar(obj) != AssociatedObject)
|| ((AssociatedObject.ScrollableWidth == 0) && (AssociatedObject.ScrollableHeight == 0)))
{
return;
}
_startPosition = e.GetPosition(AssociatedObject);
_startHorizontalOffset = AssociatedObject.HorizontalOffset;
_startVerticalOffset = AssociatedObject.VerticalOffset;
}
private void ScrollViewer_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!_startPosition.HasValue)
{
return;
}
_startPosition = null;
if (!AssociatedObject.IsMouseCaptured)
{
return;
}
AssociatedObject.Cursor = Cursors.Arrow;
AssociatedObject.ReleaseMouseCapture();
}
private void ScrollViewer_PreviewMouseMove(object sender, MouseEventArgs e)
{
// ドラッグ開始後の場合
if (AssociatedObject.IsMouseCaptured)
{
if (_startPosition is not Point startPositionValue)
{
return;
}
// 開始点からの差を求め ScrollViewer のオフセットを設定します。
Point currentPosition = e.GetPosition(AssociatedObject);
Vector difference = currentPosition - startPositionValue;
double horizontalScaleFactor = UseViewportScale ? AssociatedObject.ExtentWidth / AssociatedObject.ViewportWidth : 1;
double verticalScaleFactor = UseViewportScale ? AssociatedObject.ExtentHeight / AssociatedObject.ViewportHeight : 1;
AssociatedObject.ScrollToHorizontalOffset(_startHorizontalOffset + difference.X * horizontalScaleFactor * _scrollDirectionFactor);
AssociatedObject.ScrollToVerticalOffset(_startVerticalOffset + difference.Y * verticalScaleFactor * _scrollDirectionFactor);
}
// ドラッグ開始前の場合
else
{
if ((e.LeftButton != MouseButtonState.Pressed)
|| (_startPosition is not Point startPositionValue))
{
return;
}
// 開始点からの差を求め SystemParameters で定義される距離を上回ったらドラッグを開始します。
Point currentPosition = e.GetPosition(AssociatedObject);
Vector difference = currentPosition - startPositionValue;
if ((Math.Abs(difference.X) < SystemParameters.MinimumHorizontalDragDistance)
&& (Math.Abs(difference.Y) < SystemParameters.MinimumVerticalDragDistance))
{
return;
}
AssociatedObject.CaptureMouse();
AssociatedObject.Cursor = Cursors.ScrollAll;
}
}
private void ScrollViewer_LostMouseCapture(object sender, MouseEventArgs e)
{
_startPosition = null;
AssociatedObject.Cursor = Cursors.Arrow;
}
/// <summary>
/// 指定したオブジェクトからビジュアルツリーの祖先をたどり ScrollViewer か ScrollBar を探します。
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
private DependencyObject? FindScrollViewerOrScrollBar(DependencyObject obj)
{
while (true)
{
switch (obj)
{
case Window:
return null;
case ScrollViewer or ScrollBar:
return obj;
case DependencyObject:
obj = VisualTreeHelper.GetParent(obj);
break;
default:
return null;
}
}
}
}
}
0 件のコメント:
コメントを投稿