C# wpf 实现局部刷新的尺子控件

概述

wpf的界面功能很强大,尤其是其绑定机制天然的支持mvvm模式,这样使得界面和业务逻辑可以完全分离,大大提高了自定义控件的灵活性和通用性。在做视频剪辑工具的时候是需要尺子控件的,在wpf中很容易实现一个自定义的尺子控件。但是在实际使用中会遇到一个问题,即尺子越长,渲染速度越慢,当其总刻度到达几百万时拖动会直接造成界面卡顿。所以需要给标尺控件加入局部刷新技术,即只渲染当前可见部分的刻度,减少看不见部分不必要的渲染。

原理说明

1、尺子实现
(1)自定义控件
定义一个尺子类,继承: FrameworkElement。
(2)重写OnRender
在OnRender中使用DrawingContext对象的DrawText方法绘制文字、DrawLine方法绘制刻度。
实现效果如下(单位是时间):
在这里插入图片描述
2、局部刷新
(1)获取可视区域
要实现局部刷新,需要获取当前控件可视区域。可视区域即是ScrollViewer的区域(如果标尺不在ScrollViewer滚动区域中,则其界面完全不需要变化,也就不存在局部刷新的问题)。
(2)计算可视区域刻度
根据滚动条的位置,以及其大小,计算当前区域需要显示的所有刻度。这里需要确保刻度的一致性,即消除浮点计算过程中的误差。最后通过DrawingContext的DrawText和DrawLine将局部刻度绘制即可。
在这里插入图片描述

程序设计

1、接口设计
(1)类
定义一个尺子类继承于FrameworkElement。

 public class Ruler : FrameworkElement

(2)属性
定义一些必要的属性,提供给xaml使用。

 /// <summary>
/// 尺子长度
///比如单位为分钟,则1位一分钟。
/// </summary>
 public double Length{set;get;}
/// <summary>
/// 指针,指示某一刻度
/// </summary>
public double Chip{set;get;}
/// <summary>
/// 指针是否可见	
/// </summary>
public bool  ChipVisible{set;get;}
/// <summary>
/// 像素每单位
///比如设备的dpi为96,那这个属性设为96,以厘米为单位,那就是真实比例的尺子。
/// </summary>
public double PixelsPerUnit
/// <summary>
/// 缩放倍数
/// </summary>
public double Zoom{set;get;}

2、关键实现
(1)、获取可视区域
在OnRender方法中:

 protected override void OnRender(DrawingContext drawingContext){
            //查询是否存在滚动条
            var sv = GetAncestor<ScrollViewer>(this);
            if (sv != ParentScrollViewer && ParentScrollViewer != null)
            {
                ParentScrollViewer.ScrollChanged -= ScrollChangedEventHandler;
                ParentScrollViewer = null;
            }
            else
            {
                ParentScrollViewer = sv;
                if(ParentScrollViewer!=null)
                ParentScrollViewer.ScrollChanged += ScrollChangedEventHandler;
            }
            if (ParentScrollViewer != null)
                this.Width = DipHelper.CmToDip(Length) * this.Zoom;
            //取得绘制区域
            double start = LeftOffset;
            if (ParentScrollViewer != null)
                start += ParentScrollViewer.HorizontalOffset - ParentScrollViewer.Width * 0.5;
            double end = start + (ParentScrollViewer == null ? this.ActualWidth : ParentScrollViewer.Width * 2d);
            if (start < LeftOffset)
                start = LeftOffset;
            if (end > this.ActualWidth)
                end = this.ActualWidth;
//计算可视区域刻度以及绘制代码......
}

(2)计算刻度
①刻度实体

 class Mark
        {
            public double Pos { set; get; }
            public double Num { set; get; }
        }

②生成刻度

        /// <summary>
        /// 刻度参数
        /// </summary>
        class MarkParameter
        {
            public Scale FullScale { set; get; }//全尺寸范围
            public Scale VisualScale { set; get; }//可视区域范围
            public double Unit { set; get; }//单位长度
            public double MaxBlank { set; get; }//最小空白
            public double SubUnitRatio { set; get; }//单位转换比
            public double NumberVisualWidth { set; get; }//显示数字的最小间距
            public double NumberStart { set; get; }//起始的数字
            public double NumberStep { set; get; }  //数字增加的步长       
        }
        /// <summary>
        /// 生成刻度
        /// </summary>
        /// <param name="mp">刻度参数</param>
        /// <returns></returns>
        List<Mark> GenerateMarks(MarkParameter mp)
        {
           
            var unit = mp.Unit;
            var step = mp.NumberStep;
            while (unit > mp.MaxBlank)
            {

                unit /= mp.SubUnitRatio;
                step /= mp.SubUnitRatio;
            }
            double a = mp.VisualScale.Start - mp.FullScale.Start;
            int c = (int)(a / unit);
            double s = c * unit + mp.FullScale.Start;
            double e = mp.VisualScale.End;
            return GenerateMarks(s, e, unit, mp.MaxBlank, mp.SubUnitRatio, mp.NumberVisualWidth, mp.NumberStart + c * step, step, c);
        }
        /// <summary>
        /// 生成刻度
        /// </summary>
        /// <param name="start">起始位置</param>
        /// <param name="end">结束位置</param>
        /// <param name="unit">像素每单位</param>
        /// <param name="maxBlank">最小空白</param>
        /// <param name="subUnitRatio">单位转换比</param>
        /// <param name="numberVisualWidth"></param>
        /// <param name="startNum">起始的数字</param>
        /// <param name="step">数字增加的补充</param>
        /// <param name="index"></param>
        /// <returns>刻度集合</returns>
        List<Mark> GenerateMarks(double start, double end, double unit, double maxBlank, double subUnitRatio, double numberVisualWidth, double startNum, double step, int index)
        {
            List<Mark> marks = new List<Mark>();
            Mark m;
            double n, w;
            int c;
            while (unit > maxBlank)
            {
                unit /= subUnitRatio;
                step /= subUnitRatio;
            }
            n = startNum;
            if (index == 0)
                w = numberVisualWidth;
            else
            //计算开始显示数字的位置
            {

                var x = numberVisualWidth / unit;
                if (x > (int)x)
                {
                    x += 1;
                }
                var y = (int)x;

                if ((index) % y == 0)
                    w = numberVisualWidth;
                else
                {
                    w = (index) % y * unit;
                }
            }
            c = (int)((end - start) / unit) + 1;
            for (int j = 0; j < c; j++)
            {
                m = new Mark();
                marks.Add(m);
                m.Pos = j * unit + start;//采用乘是为了统一精度。
                if (w >= numberVisualWidth)
                {
                    m.Num = n;
                    w = unit;
                }
                else
                {
                    m.Num = double.NaN;
                    w += +unit;
                }
                n += step;
            }
            return marks;
        }
    

3、使用例子
直接在xaml中使用即可:

<Window x:Class="WpfRuler.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:WpfRuler"
        xmlns:localCommon="clr-namespace:View.Common"
        mc:Ignorable="d"
        Title="MainWindow" Height="360" Width="640">
    <Grid>
        <StackPanel  VerticalAlignment="Bottom"  Orientation="Horizontal" Height="200" >
            <ScrollBar VerticalAlignment="Top" Height="40" Width="100"    ToolTip="放大、缩小右边标尺"     x:Name="ScB_Zoom"    Minimum="1" Maximum="200"  Value="55"    Orientation="Horizontal" />
            <ScrollViewer    x:Name="Scrl_Tracks" Width="540"     Height="200"        VerticalScrollBarVisibility="Disabled"    HorizontalScrollBarVisibility="Visible"  >
                <ScrollViewer.Content>
                    <Border Background="Black" Height="40"   VerticalAlignment="Top" >
                        <localCommon:Ruler   ToolTip="单位(分:秒)"  
                                 PixelsPerUnit="96"  
                                 Marks="Down"  HorizontalAlignment="Left"  Height="40"   
                                 Length="0.5" 
                                 Chip="10" 
                                 ChipVisible="True"
                                 Zoom="{Binding ElementName=ScB_Zoom,Path=Value}" >
                        </localCommon:Ruler>
                    </Border>
                </ScrollViewer.Content>
                <ScrollViewer.Resources>
                </ScrollViewer.Resources>
            </ScrollViewer>
        </StackPanel>
    </Grid>
</Window>

界面效果如下:
在这里插入图片描述

完整代码

https://download.csdn.net/download/u013113678/32968591

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeOfCC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值