C# 使用ScrollViewer单击并拖动滚动

C# 使用ScrollViewer单击并拖动滚动,c#,wpf,scrollviewer,C#,Wpf,Scrollviewer,我想允许使用ScrollViewer单击并拖动滚动(即单击ScrollViewer中的任意位置并向上或向下拖动,它将相应地滚动) 我有一个StackPanel嵌套在ScrollViewer中,我已经让滚动工作了。我相信我在某处看到了答案,但我似乎再也找不到了 这只能使用代码来完成。请看: 公共类TouchScrolling:DependencyObject { 公共静态bool GetIsEnabled(DependencyObject obj) { 返回(bool)对象GetValue(IsE

我想允许使用
ScrollViewer
单击并拖动滚动(即单击
ScrollViewer
中的任意位置并向上或向下拖动,它将相应地滚动)

我有一个
StackPanel
嵌套在
ScrollViewer
中,我已经让滚动工作了。我相信我在某处看到了答案,但我似乎再也找不到了

这只能使用代码来完成。

请看:

公共类TouchScrolling:DependencyObject
{
公共静态bool GetIsEnabled(DependencyObject obj)
{
返回(bool)对象GetValue(IsEnabledProperty);
}
公共静态无效设置已启用(DependencyObject对象,布尔值)
{
对象设置值(IsEnabledProperty,值);
}
公共场所被禁止
{
获取{return(bool)GetValue(IsEnabledProperty);}
set{SetValue(IsEnabledProperty,value);}
}
公共静态只读从属属性IsEnabledProperty=
DependencyProperty.RegisterAttached(“IsEnabled”、typeof(bool)、typeof(TouchScrolling)、新UIPropertyMetadata(false、IsEnabledChanged));
静态字典_captures=新字典();
静态void IsEnabledChanged(DependencyObject d、DependencyPropertyChangedEventArgs e)
{
var target=d作为ScrollViewer;
if(target==null)返回;
if((bool)e.NewValue)
{
target.Loaded+=target\u Loaded;
}
其他的
{
已卸载目标(目标,新路由EventTargets());
}
}
已卸载静态无效目标(对象发送器,路由目标e)
{
System.Diagnostics.Debug.WriteLine(“目标卸载”);
var target=作为ScrollViewer的发送方;
if(target==null)返回;
_捕获。删除(发送方);
target.Loaded-=target\u Loaded;
target.unload-=target\u unload;
target.PreviewMouseLeftButtonDown-=target\u PreviewMouseLeftButtonDown;
target.PreviewMouseMove-=target\u PreviewMouseMove;
target.PreviewMouseLeftButtonUp-=target_PreviewMouseLeftButtonUp;
}
静态无效目标\u预览鼠标左键向下(对象发送器,鼠标按钮Ventargs e)
{
var target=作为ScrollViewer的发送方;
if(target==null)返回;
_捕获[发送者]=新鼠标捕获
{
VerticalOffset=target.VerticalOffset,
点=e.GetPosition(目标),
};
}
已加载静态无效目标(对象发送器,路由目标e)
{
var target=作为ScrollViewer的发送方;
if(target==null)返回;
System.Diagnostics.Debug.WriteLine(“目标加载”);
target.unload+=target\u unload;
target.PreviewMouseLeftButtonDown+=目标_PreviewMouseLeftButtonDown;
target.PreviewMouseMove+=target_PreviewMouseMove;
target.PreviewMouseLeftButtonUp+=target_PreviewMouseLeftButtonUp;
}
静态无效目标\u预览鼠标左键(对象发送器,鼠标按钮Ventargs e)
{
var target=作为ScrollViewer的发送方;
if(target==null)返回;
target.ReleaseMouseCapture();
}
静态无效目标\u预览鼠标移动(对象发送器,鼠标目标)
{
如果(!_captures.ContainsKey(发送方))返回;
如果(例如LeftButton!=鼠标按钮状态已按下)
{
_捕获。删除(发送方);
返回;
}
var target=作为ScrollViewer的发送方;
if(target==null)返回;
变量捕获=_捕获[发送方];
var点=e.GetPosition(目标);
var dy=point.Y-capture.point.Y;
如果(数学绝对值(dy)>5)
{
target.CaptureMouse();
}
target.ScrollToVerticalOffset(capture.VerticalOffset-dy);
}
内置类鼠标座
{
公共双垂直偏移量{get;set;}
公共点{get;set;}
}
}

这里有一些怪癖。我注意到,当显示内容时,ScrollViewer实际上正在加载、卸载和再次加载

这意味着我不能在
IsEnabledChanged
方法中连接事件,然后在
target\u卸载的
事件处理程序中取消它们的挂钩,因为它们会立即被取消挂钩。相反,我必须将它们连接到加载事件的处理程序中,而加载事件则永远不会被解除挂钩


这意味着那里有某种“内存泄漏”,但这是我准备接受的

它会导致内存泄漏,有人有更好的吗?
public class TouchScrolling : DependencyObject
{
    public static bool GetIsEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsEnabledProperty);
    }

    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }

    public bool IsEnabled
    {
        get { return (bool)GetValue(IsEnabledProperty); }
        set { SetValue(IsEnabledProperty, value); }
    }

    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(TouchScrolling), new UIPropertyMetadata(false, IsEnabledChanged));

    static Dictionary<object, MouseCapture> _captures = new Dictionary<object, MouseCapture>();

    static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = d as ScrollViewer;
        if (target == null) return;

        if ((bool)e.NewValue)
        {
            target.Loaded += target_Loaded;
        }
        else
        {
            target_Unloaded(target, new RoutedEventArgs());
        }
    }

    static void target_Unloaded(object sender, RoutedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Target Unloaded");

        var target = sender as ScrollViewer;
        if (target == null) return;

        _captures.Remove(sender);

        target.Loaded -= target_Loaded;
        target.Unloaded -= target_Unloaded;
        target.PreviewMouseLeftButtonDown -= target_PreviewMouseLeftButtonDown;
        target.PreviewMouseMove -= target_PreviewMouseMove;

        target.PreviewMouseLeftButtonUp -= target_PreviewMouseLeftButtonUp;
    }

    static void target_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        var target = sender as ScrollViewer;
        if (target == null) return;

        _captures[sender] = new MouseCapture
        {
            VerticalOffset = target.VerticalOffset,
            Point = e.GetPosition(target),
        };
    }

    static void target_Loaded(object sender, RoutedEventArgs e)
    {
        var target = sender as ScrollViewer;
        if (target == null) return;

        System.Diagnostics.Debug.WriteLine("Target Loaded");

        target.Unloaded += target_Unloaded;
        target.PreviewMouseLeftButtonDown += target_PreviewMouseLeftButtonDown;
        target.PreviewMouseMove += target_PreviewMouseMove;

        target.PreviewMouseLeftButtonUp += target_PreviewMouseLeftButtonUp;
    }

    static void target_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var target = sender as ScrollViewer;
        if (target == null) return;

        target.ReleaseMouseCapture();
    }

    static void target_PreviewMouseMove(object sender, MouseEventArgs e)
    {
        if (!_captures.ContainsKey(sender)) return;

        if (e.LeftButton != MouseButtonState.Pressed)
        {
            _captures.Remove(sender);
            return;
        }

        var target = sender as ScrollViewer;
        if (target == null) return;

        var capture = _captures[sender];

        var point = e.GetPosition(target);

        var dy = point.Y - capture.Point.Y;
        if (Math.Abs(dy) > 5)
        {
            target.CaptureMouse();
        }

        target.ScrollToVerticalOffset(capture.VerticalOffset - dy);
    }

    internal class MouseCapture
    {
        public Double VerticalOffset { get; set; }
        public Point Point { get; set; }
    }