惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
MyScale Blog
MyScale Blog
量子位
月光博客
月光博客
J
Java Code Geeks
A
About on SuperTechFans
H
Hackread – Cybersecurity News, Data Breaches, AI and More
U
Unit 42
WordPress大学
WordPress大学
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
腾讯CDC
G
Google Developers Blog
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
IT之家
IT之家
N
Netflix TechBlog - Medium
Microsoft Security Blog
Microsoft Security Blog
博客园 - 叶小钗
B
Blog
Martin Fowler
Martin Fowler
P
Proofpoint News Feed
B
Blog RSS Feed

博客园 - HotSky

Chrome启用CDP(chrome-devtools-protocol)进行远程操控 WPF 让ScrollViewer支持按住鼠标中键拖拽滚动内容 WPF 让ScrollViewer支持鼠标中键滚动缩放内容 Nodejs使用 nodejs中写sql需要用in时的写法 C#城市最短路径 C#Animation Sqlite PDF附录A: 内容流操作码 WPF FPS类 生命模拟 C# Sql帮助类,可扩展 WPF WriteableBitmap通过GDI+绘制帮助类 Alpha混合 Bmp读写二值图 WPF支持任意快捷键+鼠标组合的绑定类 WPF阻止窗体被系统缩放,使用显示器DPI WPF DataGrid自动增长序号列 C#访问或修改私有类、函数、变量、属性 WPF一个简单的属性编辑控件
WPF游标直尺
HotSky · 2026-07-31 · via 博客园 - HotSky

效果图:

屏幕截图 2026-07-31 172811

使用方法:

<controls:Ruler x:Name="rulerV" Background="White" Foreground="Black" UnitType="Px" Dpi="72" Orientation="Vertical" Maximum="{Binding ElementName=paintAreaCanvas, Path=ActualHeight}" Scale="{Binding ElementName=scaleBehavior, Path=Scale}" ReservedSpace="{Binding ElementName=scrollviewer, Path=VerticalOffset}" SnapsToDevicePixels="True"/>

代码:

    public enum UnitType { Px, MM }
    public class Ruler : FrameworkElement
    {
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(Ruler), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsRender));

        public UnitType UnitType
        {
            get { return (UnitType)GetValue(UnitTypeProperty); }
            set { SetValue(UnitTypeProperty, value); }
        }

        public static readonly DependencyProperty UnitTypeProperty =
            DependencyProperty.Register("UnitType", typeof(UnitType), typeof(Ruler), new FrameworkPropertyMetadata(UnitType.Px, FrameworkPropertyMetadataOptions.AffectsRender));

        public double Scale
        {
            get { return (double)GetValue(ScaleProperty); }
            set { SetValue(ScaleProperty, value); }
        }

        public static readonly DependencyProperty ScaleProperty =
            DependencyProperty.Register("Scale", typeof(double), typeof(Ruler), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender));

        public double ReservedSpace
        {
            get { return (double)GetValue(ReservedSpaceProperty); }
            set { SetValue(ReservedSpaceProperty, value); }
        }

        public static readonly DependencyProperty ReservedSpaceProperty =
            DependencyProperty.Register("ReservedSpace", typeof(double), typeof(Ruler), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));

        public double Maximum
        {
            get { return (double)GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }

        public static readonly DependencyProperty MaximumProperty =
            DependencyProperty.Register("Maximum", typeof(double), typeof(Ruler), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsRender));

        public double TickFrequency
        {
            get { return (double)GetValue(TickFrequencyProperty); }
            set { SetValue(TickFrequencyProperty, value); }
        }

        public static readonly DependencyProperty TickFrequencyProperty =
            DependencyProperty.Register("TickFrequency", typeof(double), typeof(Ruler), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender));



        public double Dpi
        {
            get { return (double)GetValue(DpiProperty); }
            set { SetValue(DpiProperty, value); }
        }

        public static readonly DependencyProperty DpiProperty =
            DependencyProperty.Register("Dpi", typeof(double), typeof(Ruler), new FrameworkPropertyMetadata(96.0, FrameworkPropertyMetadataOptions.AffectsRender));



        public Brush Background
        {
            get { return (Brush)GetValue(BackgroundProperty); }
            set { SetValue(BackgroundProperty, value); }
        }

        public static readonly DependencyProperty BackgroundProperty =
            DependencyProperty.Register("Background", typeof(Brush), typeof(Ruler), new FrameworkPropertyMetadata(Brushes.White, FrameworkPropertyMetadataOptions.AffectsRender));



        public Brush Foreground
        {
            get { return (Brush)GetValue(ForegroundProperty); }
            set { SetValue(ForegroundProperty, value); }
        }

        public static readonly DependencyProperty ForegroundProperty =
            DependencyProperty.Register("Foreground", typeof(Brush), typeof(Ruler), new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));



        public Ruler()
        {
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            if(Orientation == Orientation.Horizontal)
                RenderHorizon(drawingContext);
            else
                RenderVertical(drawingContext);
        }

        void RenderHorizon(DrawingContext drawingContext)
        {
            if (double.IsNaN(Maximum)) return;
            drawingContext.PushGuidelineSet(new GuidelineSet(new double[] { 0, 0.5 }, new double[] { 0, 0.5, ActualHeight / 2.0, ActualHeight * 3.0 / 4.0 }));
            Pen pen = new Pen(Foreground, 1);

            double max = Math.Min(Maximum, ReservedSpace + ActualWidth);
            double unit0 = 0;
            double scales = 1;
            switch (UnitType)
            {
                case UnitType.Px:
                    unit0 = 10.0;
                    if (Scale >= 5)
                        scales = 0.1;
                    else if (Scale >= 4)
                        scales = 0.2;
                    else if (Scale >= 3)
                        scales = 0.4;
                    else if (Scale >= 2)
                        scales = 0.5;
                    break;
                case UnitType.MM:
                    unit0 = 2.0;
                    if (Scale >= 14)
                        scales = 0.05;
                    else if(Scale >= 8)
                        scales = 0.1;
                    else if (Scale >= 5)
                        scales = 0.2;
                    else if (Scale >= 4)
                        scales = 0.3;
                    else if (Scale >= 3)
                        scales = 0.4;
                    else if (Scale >= 2)
                        scales = 0.5;
                    break;
            }
            double unit = (UnitType == UnitType.MM ? unit0.ToPx((float)Dpi) : unit0) * scales * Scale;
            double startValue = ReservedSpace;
            double offset = startValue % unit;
            int c = (int)Math.Floor(startValue / unit);
            double current = startValue;
            drawingContext.DrawLine(pen, new Point(2, 0), new Point(2, ActualHeight));
            current += unit - offset;
            while (current < max)
            {
                c++;
                bool isHalf = c % 5 == 0;
                bool isFull = c % 10 == 0;
                double x = current - startValue;
                double y1 = isFull ? 0.0 : (isHalf ? ActualHeight / 2.0 : ActualHeight * 3 / 4.0);
                double y2 = ActualHeight;
                drawingContext.DrawLine(pen, new Point(x + 2, y1), new Point(x + 2, y2));

                if (isFull)
                {
                    drawingContext.DrawText(new FormattedText((c * unit0 * scales).ToString(),CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("sim"), 12, Foreground, 1), new Point(x + 2, 0));
                }
                current += unit;
            }

            if (current >= max)
            {
                current = max;
                drawingContext.DrawLine(pen, new Point(current - startValue, 0), new Point(current - startValue, ActualHeight));
            }
        }

        void RenderVertical(DrawingContext drawingContext)
        {
            if (Maximum == double.NaN) return;
            drawingContext.PushGuidelineSet(new GuidelineSet(new double[] { 0, 0.5, ActualWidth / 2.0, ActualWidth * 3.0 / 4.0, ActualWidth }, new double[] { 0, 0.5 }));
            Pen pen = new Pen(Foreground, 1);

            double max = Math.Min(Maximum, ReservedSpace + ActualHeight);
            double unit0 = 0;
            double scales = 1;
            switch (UnitType)
            {
                case UnitType.Px:
                    unit0 = 10.0;
                    if (Scale >= 5)
                        scales = 0.1;
                    else if (Scale >= 4)
                        scales = 0.2;
                    else if (Scale >= 3)
                        scales = 0.4;
                    else if (Scale >= 2)
                        scales = 0.5;
                    break;
                case UnitType.MM:
                    unit0 = 2.0;
                    if (Scale >= 6)
                        scales = 0.05;
                    else if (Scale >= 5)
                        scales = 0.1;
                    else if (Scale >= 4)
                        scales = 0.2;
                    else if (Scale >= 3)
                        scales = 0.4;
                    else if (Scale >= 2)
                        scales = 0.5;
                    break;
            }
            double unit = (UnitType == UnitType.MM ? unit0.ToPx((float)Dpi) : unit0) * scales * Scale;
            double startValue = ReservedSpace;
            double offset = startValue % unit;
            int c = (int)Math.Floor(startValue / unit);
            double current = startValue;
            drawingContext.DrawLine(pen, new Point(0, 2), new Point(ActualWidth, 2));
            current += unit - offset;
            while (current < max)
            {
                c++;
                bool isHalf = c % 5 == 0;
                bool isFull = c % 10 == 0;
                double y = current - startValue;
                double x1 = isFull ? 0.0 : (isHalf ? ActualWidth / 2.0 : ActualWidth * 3 / 4.0);
                double x2 = ActualWidth;
                drawingContext.DrawLine(pen, new Point(x1, y + 2), new Point(x2, y + 2));

                if (isFull)
                {
                    drawingContext.PushTransform(new RotateTransform(90, 0, y));
                    drawingContext.DrawText(new FormattedText((c * unit0 * scales).ToString(), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("sim"), 12, Foreground, 1), new Point(4, y - 12));
                    drawingContext.Pop();
                }
                current += unit;
            }

            if(current >= max)
            {
                current = max;
                drawingContext.DrawLine(pen, new Point(0, current - startValue), new Point(ActualWidth, current - startValue));
            }
        }
    }