我的编程空间,编程开发者的网络收藏夹
学习永远不晚

在WPF中实现平滑滚动的方法详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

在WPF中实现平滑滚动的方法详解

WPF实现滚动条还是比较方便的,只要在控件外围加上ScrollViewer即可,但美中不足的是:滚动的时候没有动画效果。在滚动的时候添加过渡动画能给我们的软件增色不少,例如Office 2013的滚动的时候支持动画看起来就舒服多了。 之前倒是研究过如何实现这个平滑滚动,不过网上的方案大部分大多数如下:

  • 通过VisualTree找到ScrollViewer

  • 在ScrollChanged事件中添加动画

这种方案效果并不好,以为我们的滚动很多时候都是一口气滚动好几格滚轮的,这个时候上一个动画还没有结束,下一个动画就来了,反而还出现了卡顿的感觉,并且网上的一些算法大部分还都会导致偏移错位。

趁着这两天有点时间,就研究了一下ScorllViewer,从MSDN文档中看到,它是支持两种滚动方式的:

物理滚动:

系统默认的滚动方案,控件本身啥都不用干,完全由ScrollViewer来实现滚动。这种方式的好处是简单,但也正由于简单,控件本身完全感知不到ScorllViewer的存在,也就无法加以控制了。

逻辑滚动:

将这种方式需要设置ScrollViewer的CanContentScroll为"True"才能生效,同时需要控件实现IScrollInfo接口。此时ScrollViewer只是将滚动事件通过IScrollInfo接口传递给控件,由控件本身自己去实现滚动。同时从IScrollInfo接口中读取相关的属性更新滚动条界面。

也就是说,逻辑滚动才是我们所需要的方案。由于它要求控件实现IScrollInfo接口,自行控制滚动。也就是说我们要实现自己的Panel,并且实现IScrollInfo接口。关于这个接口,MSDN上有一系列文章介绍过如何实现它:

  • IScrollInfo in Avalon part I

  • IScrollInfo in Avalon part II

  • IScrollInfo in Avalon part III

  • IScrollInfo in Avalon part IV

这个接口实现也不算麻烦,我倒没有细看这几篇文章,自己照着最后的一个例子尝试着弄了一阵子也弄出来了。实际上麻烦的地方不在于实现这个接口,而是实现Panel,我这里为了简单,直接继承了WrapPanel类,代码如下: 

    class MyWrapPanel : WrapPanel, IScrollInfo
    {
        TranslateTransform _transForm;
        public MyWrapPanel()
        {
            _transForm = new TranslateTransform();
            this.RenderTransform = _transForm;
        }

        #region Layout

        Size _screenSize;
        Size _totalSize;

        protected override Size MeasureOverride(Size availableSize)
        {
            _screenSize = availableSize;

            if (Orientation == Orientation.Horizontal)
                availableSize = new Size(availableSize.Width, double.PositiveInfinity);
            else
                availableSize = new Size(double.PositiveInfinity, availableSize.Height);

            _totalSize = base.MeasureOverride(availableSize);
            return _totalSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            var size = base.ArrangeOverride(finalSize);
            if (ScrollOwner != null)
            {
                _transForm.Y = -VerticalOffset;
                _transForm.X = -HorizontalOffset;

                ScrollOwner.InvalidateScrollInfo();
            }
            return _screenSize;
        }
        #endregion

        #region IScrollInfo

        public ScrollViewer ScrollOwner { get; set; }
        public bool CanHorizontallyScroll { get; set; }
        public bool CanVerticallyScroll { get; set; }

        public double ExtentHeight { get { return _totalSize.Height; } }
        public double ExtentWidth { get { return _totalSize.Width; } }

        public double HorizontalOffset { get; private set; }
        public double VerticalOffset { get; private set; }

        public double ViewportHeight { get { return _screenSize.Height; } }
        public double ViewportWidth { get { return _screenSize.Width; } }

        void appendOffset(double x, double y)
        {
            var offset = new Vector(HorizontalOffset + x, VerticalOffset + y);

            offset.Y = range(offset.Y, 0, _totalSize.Height - _screenSize.Height);
            offset.X = range(offset.X, 0, _totalSize.Width - _screenSize.Width);

            HorizontalOffset = offset.X;
            VerticalOffset = offset.Y;

            InvalidateArrange();
        }

        double range(double value, double value1, double value2)
        {
            var min = Math.Min(value1, value2);
            var max = Math.Max(value1, value2);

            value = Math.Max(value, min);
            value = Math.Min(value, max);

            return value;
        }


        const double _lineOffset = 30;
        const double _wheelOffset = 90;

        public void LineDown()
        {
            appendOffset(0, _lineOffset);
        }

        public void LineUp()
        {
            appendOffset(0, -_lineOffset);
        }

        public void LineLeft()
        {
            appendOffset(-_lineOffset, 0);
        }

        public void LineRight()
        {
            appendOffset(_lineOffset, 0);
        }

        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            throw new NotSupportedException();
        }

        public void MouseWheelDown()
        {
            appendOffset(0, _wheelOffset);
        }

        public void MouseWheelUp()
        {
            appendOffset(0, -_wheelOffset);
        }

        public void MouseWheelLeft()
        {
            appendOffset(0, _wheelOffset);
        }

        public void MouseWheelRight()
        {
            appendOffset(_wheelOffset, 0);
        }

        public void PageDown()
        {
            appendOffset(0, _screenSize.Height);
        }

        public void PageUp()
        {
            appendOffset(0, -_screenSize.Height);
        }

        public void PageLeft()
        {
            appendOffset(-_screenSize.Width, 0);
        }

        public void PageRight()
        {
            appendOffset(_screenSize.Width, 0);
        }

        public void SetVerticalOffset(double offset)
        {
            this.appendOffset(HorizontalOffset, offset - VerticalOffset);
        }

        public void SetHorizontalOffset(double offset)
        {
            this.appendOffset(offset - HorizontalOffset, VerticalOffset);
        }
        #endregion
    }

基本上从代码中也能看出IScrollInfo接口的交互流程,这里就不多介绍了。

主界面代码如下: 

<ItemsControl ItemsSource="{Binding}" >
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border BorderThickness="1" BorderBrush="Black" Margin="8" Width="150" Height="50">
                    <Rectangle Fill="{Binding}"  />
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <local:MyWrapPanel />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.Template>
            <ControlTemplate>
                <ScrollViewer CanContentScroll="True">
                    <ItemsPresenter />
                </ScrollViewer>
            </ControlTemplate>
        </ItemsControl.Template>
    </ItemsControl>

需要注意的是,这儿需要设置<ScrollViewer CanContentScroll="True">,否则使用的不是逻辑滚动。

数据源代码如下:

    var brushes = from property in typeof(Brushes).GetProperties()
                    let value = property.GetValue(null)
                    select value;

    this.DataContext = brushes.Take(100).ToArray();

由于使用了IscrollInfo接口,所有的滚动操作是自己实现的,这里我是通过设置Panel的RenderTransFrom的X,Y偏移来实现滚动操作的。运行后看上去上和WrapPanel没有什么区别,但是由于是自己控制的滚动,加上动画效果也只是分分钟的事情了,把上面代码的RenderTransFrom的X,Y硬切换改成动画切换即可:

    protected override Size ArrangeOverride(Size finalSize)
    {
        var size = base.ArrangeOverride(finalSize);
        if (ScrollOwner != null)
        {
            var yOffsetAnimation = new DoubleAnimation() { To = -VerticalOffset, Duration = TimeSpan.FromSeconds(0.3) };
            _transForm.BeginAnimation(TranslateTransform.YProperty, yOffsetAnimation);

            var xOffsetAnimation = new DoubleAnimation() { To = -HorizontalOffset, Duration = TimeSpan.FromSeconds(0.3) };
            _transForm.BeginAnimation(TranslateTransform.XProperty, xOffsetAnimation);

            ScrollOwner.InvalidateScrollInfo();
        }
        return _screenSize;
    }

对于其它的Panel,如Grid,DockPanel等,基本上也可以按照这种方式实现,IScrollInfo接口处基本上可以保持不变,只需要重写MeasureOverride和ArrangeOverride两个函数即可。一个特殊的控件是StackPanel,由于它本身已经实现了IScrollInfo接口,也就是说它本身就有自身的自绘制滚动的方案,并且没有提供接口在覆盖自身的自绘制滚动,因此我们需要自己写一个StackPanel,好在实现StackPanel并不难,由于篇幅有限,这里我懒得继续写了,读者朋友自己实现吧。至于那些非Panel的控件,实现就更简单了,也留着读者朋友自己实现吧。

到此这篇关于WPF实现平滑滚动的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

在WPF中实现平滑滚动的方法详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

在WPF中怎么实现平滑滚动

本篇内容介绍了“在WPF中怎么实现平滑滚动”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!WPF实现滚动条还是比较方便的,只要在控件外围加上S
2023-07-02

详解如何在PyQt5中实现平滑滚动的QScrollArea

Qt自带的QScrollArea滚动时只能在两个像素节点之间跳变,看起来很突兀。所以本文将通过定时器,重写wheelEvent()来实现平滑滚动,需要的可以参考一下
2023-01-28

怎么在Android中使用SeekBar实现一个平滑滚动效果

这篇文章将为大家详细讲解有关怎么在Android中使用SeekBar实现一个平滑滚动效果,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。把max改为100,progress改为50
2023-06-14

WPF实现在控件上显示Loading等待动画的方法详解

这篇文章主要介绍了WPF如何在控件上显示Loading等待动画,文中的示例代码讲解详细,对我们学习或工作有一定帮助,需要的可以参考一下
2023-03-24

Android实现左右滑动效果的方法详解

本示例演示在Android中实现图片左右滑动效果。关于滑动效果,在Android中用得比较多,本示例实现的滑动效果是使用ViewFlipper来实现的,当然也可以使用其它的View来实现。接下来就让我们开始实现这种效果。为了方便大家理解,我
2022-06-06

Android实现Activity水平和垂直滚动条的方法

本文实例讲述了Android实现Activity水平和垂直滚动条的方法。分享给大家供大家参考,具体如下:2022-06-06

详解Android中实现ListView左右滑动删除条目的方法

使用Scroller实现绚丽的ListView左右滑动删除Item效果 这里来给大家带来使用Scroller的小例子,同时也能用来帮助初步解除的读者更加熟悉的掌握Scroller的使用,掌握好了Scroller的使用我们就能实现很多滑动的效
2022-06-06

详解iOS中position:fixed吸底时的滑动出现抖动的解决方案

两种抖动为什么抖动还会有两种? 其实是我碰到过两种抖动的场景,第一个场景是native的抖动,第二个场景是h5的抖动。 native的抖动前端开发人员会在app中打开webview,这个时候iOS中position:fixed吸底时的滑动出
2022-05-17

解析Android中实现滑动翻页之ViewFlipper的使用详解

1)View切换的控件—ViewFlipper介绍 ViewFilpper类继承于ViewAnimator类。而ViewAnimator类继承于FrameLayout。 查看ViewAnimator类的源码可以看出此类的作用主要是为其中的V
2022-06-06

Android中ScrollView实现滑动距离监听器的方法

前言 众所周知ScrollView是我们经常使用的一个UI控件,也许你在使用ScrollView的过程中会发现,当你想监听ScrollView滑动的距离时却没有合适的监听器!当然在API 23中有setOnScrollChangeListe
2022-06-06

详解Spring中实现接口动态的解决方法

前言本文主要给大家介绍的是关于Spring实现接口动态的相关内容,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍吧。关于这个问题是因为领导最近跟我提了一个需求,是有关于实现类Mybatis的@Select、@Insert注解的功
2023-05-31

Android项目中实现滑动功能的方法有哪些

本篇文章给大家分享的是有关Android项目中实现滑动功能的方法有哪些,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。一、Android坐标体系在讲解滑动之前,我们有必要简单提一
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录