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

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
K
Kaspersky official blog
T
The Exploit Database - CXSecurity.com
NISL@THU
NISL@THU
AWS News Blog
AWS News Blog
V2EX - 技术
V2EX - 技术
Google DeepMind News
Google DeepMind News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Security @ Cisco Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
J
Java Code Geeks
Microsoft Azure Blog
Microsoft Azure Blog
Attack and Defense Labs
Attack and Defense Labs
Jina AI
Jina AI
The Last Watchdog
The Last Watchdog
W
WeLiveSecurity
H
Help Net Security
V
Visual Studio Blog
宝玉的分享
宝玉的分享
C
Cybersecurity and Infrastructure Security Agency CISA
T
Threat Research - Cisco Blogs
IT之家
IT之家
Hugging Face - Blog
Hugging Face - Blog
Latest news
Latest news
T
Tor Project blog
I
Intezer
美团技术团队
GbyAI
GbyAI
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 三生石上(FineUI控件)
Google DeepMind News
Google DeepMind News
Scott Helme
Scott Helme
Y
Y Combinator Blog
博客园 - 司徒正美
T
Tenable Blog
O
OpenAI News
N
News and Events Feed by Topic
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
Vulnerabilities – Threatpost
P
Palo Alto Networks Blog
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
云风的 BLOG
云风的 BLOG
Help Net Security
Help Net Security

博客园 - wuty007

TelegramConsole C# 范围运算符 C# 调用WGC 实现桌面屏幕的捕获 C# 依赖注入 Microsoft.Extensions.DependencyInjection 实现 控制反转(IOC) C# 获取Windows系统的设备名称 记录 Windows系统开启hyper-v ,部分端口被保留,导致端口不能使用而报错的问题 WPF 调用 Win32的SetWindowDisplayAffinity 函数 实现捕获屏幕时,过滤指定的窗口 记录WPF 在清单列表设置了UIACESS为true,没有签名的报错“从服务器返回了一个参照” WPF 的ListBox 去除默认的Item项的 鼠标hover的背景颜色 WPF 调用 ChangeWindowMessageFilterEx 修改指定窗口 (UIPI) 消息筛选器的用户界面特权隔离 记录一下 WPF进程 SendMessage 发送窗口消息进行进程间通信,存在进程权限无法接受消息的问题 记录 使用PsExec启动System权限的WPF 程序 记录 命令行的 findstr 的使用 排查Windows 下的内存使用率过高,但是任务管理器看不到进程 Everything 支持 多实例 运行 指定应用 在 控制面板或者 设置的安装应用 置灰或隐藏卸载按钮 WPF 通过RawInput 获取 系统全局触摸事件 C# 定时任务 Quartz.NET 的使用 记录一下Windows系统下的命令行参数的字符个数限制 WPF 实现支持动态调整高度的文本显示控件
完善基于WPF开发的标尺控件(含实例代码)
wuty007 · 2026-01-31 · via 博客园 - wuty007

在2021年的时候,有个拖拽的项目用到了标尺的控件,我写了一篇大概实现代码的博客:仿Word的支持横轴竖轴的WPF 标尺 - wuty007 - 博客园。在里边的实例代码里边,鼠标横纵坐标移动显示的代码没有完善。直到上个月有个水友发信息找我要源码。趁着这段时间正在使用AI编程,于是让AI帮我补充一下代码了

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Media3D;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows;

namespace RulerDemo
{
    [TemplatePart(Name = "verticalTrackLine", Type = typeof(Line))]
    [TemplatePart(Name = "horizontalTrackLine", Type = typeof(Line))]
    internal class RulerControl : Control
    {
        public static readonly DependencyProperty DpiProperty = DependencyProperty.Register(nameof(Dpi), typeof(Dpi), typeof(RulerControl));
        public static readonly DependencyProperty DisplayPercentProperty = DependencyProperty.Register("DisplayPercent", typeof(double), typeof(RulerControl));
        public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register("DisplayType", typeof(RulerDisplayType), typeof(RulerControl));
        public static readonly DependencyProperty DisplayUnitProperty = DependencyProperty.Register("DisplayUnit", typeof(RulerDisplayUnit), typeof(RulerControl));
        public static readonly DependencyProperty ZeroPointProperty = DependencyProperty.Register("ZeroPoint", typeof(double), typeof(RulerControl));

        /// <summary>
        /// 定义静态构造函数
        /// </summary>
        static RulerControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(RulerControl), new FrameworkPropertyMetadata(typeof(RulerControl)));
        }

        #region 属性
        /// <summary>
        /// 屏幕分辨率
        /// </summary>
        public Dpi Dpi
        {
            get => ((Dpi)GetValue(DpiProperty));
            set => SetValue(DpiProperty, value);
        }

        /// <summary>
        /// 设置0点从哪里开始
        /// </summary>
        public double ZeroPoint
        {
            get => ((double)GetValue(ZeroPointProperty));
            set
            {
                SetValue(ZeroPointProperty, value);
                InvalidateVisual();
            }
        }

        /// <summary>
        /// 显示的比率(目前支持0-1的选项)
        /// </summary>
        public double DisplayPercent
        {
            get => ((double)GetValue(DisplayPercentProperty));
            set
            {
                if (value > 1)
                {
                    value = 1;
                }
                SetValue(DisplayPercentProperty, value);
                InvalidateVisual();
            }
        }

        /// <summary>
        /// 显示的类型:枚举类(支持横向或者竖向)
        /// </summary>
        public RulerDisplayType DisplayType
        {
            get => ((RulerDisplayType)GetValue(DisplayTypeProperty));
            set => SetValue(DisplayTypeProperty, value);
        }

        /// <summary>
        /// 显示的单位:cm和pixel
        /// </summary>
        public RulerDisplayUnit DisplayUnit
        {
            get => ((RulerDisplayUnit)GetValue(DisplayUnitProperty));
            set => SetValue(DisplayUnitProperty, value);
        }
        #endregion

        #region 常量
        public const double InchCm = 2.54; //一英寸为2.54cm
        private const int P100StepSpanPixel = 100;
        private const int P100StepSpanCm = 2;
        private const int P100StepCountPixel = 20;
        private const int P100StepCountCm = 20;

        #endregion

        #region 变量
        private double _minStepLengthCm;
        private double _maxStepLengthCm;
        private double _actualLength;
        private int _stepSpan;
        private int _stepCount;
        private double _stepLength;
        private Line _mouseVerticalTrackLine;
        private Line _mouseHorizontalTrackLine;
        #endregion

        #region 标尺边框加指针显示
        public void RaiseHorizontalRulerMoveEvent(MouseEventArgs e)
        {
            var mousePoint = e.GetPosition(this);
            Debug.WriteLine($"水平标尺 - 鼠标位置: X={mousePoint.X}, Y={mousePoint.Y}");

            if (_mouseHorizontalTrackLine != null)
            {
                // 水平标尺:移动垂直跟踪线,X跟随鼠标
                _mouseHorizontalTrackLine.X1 = _mouseHorizontalTrackLine.X2 = mousePoint.X;
                // Y坐标从0开始,延伸足够长
                _mouseHorizontalTrackLine.Y1 = 0;
                _mouseHorizontalTrackLine.Y2 = 5000;

                _mouseHorizontalTrackLine.Visibility = Visibility.Visible;

                Debug.WriteLine($"水平标尺 - 垂直线更新: X1={_mouseHorizontalTrackLine.X1}, Y1={_mouseHorizontalTrackLine.Y1}, X2={_mouseHorizontalTrackLine.X2}, Y2={_mouseHorizontalTrackLine.Y2}, Visibility={_mouseHorizontalTrackLine.Visibility}");
            }
        }

        public void RaiseVerticalRulerMoveEvent(MouseEventArgs e)
        {
            var mousePoint = e.GetPosition(this);
            Debug.WriteLine($"垂直标尺 - 鼠标位置: X={mousePoint.X}, Y={mousePoint.Y}");

            if (_mouseVerticalTrackLine != null)
            {
                // 垂直标尺:移动水平跟踪线,Y跟随鼠标
                _mouseVerticalTrackLine.Y1 = _mouseVerticalTrackLine.Y2 = mousePoint.Y;
                // X坐标从0开始,延伸足够长
                _mouseVerticalTrackLine.X1 = 0;
                _mouseVerticalTrackLine.X2 = 5000;

                _mouseVerticalTrackLine.Visibility = Visibility.Visible;

                Debug.WriteLine($"垂直标尺 - 水平线更新: X1={_mouseVerticalTrackLine.X1}, Y1={_mouseVerticalTrackLine.Y1}, X2={_mouseVerticalTrackLine.X2}, Y2={_mouseVerticalTrackLine.Y2}, Visibility={_mouseVerticalTrackLine.Visibility}");
            }
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _mouseVerticalTrackLine = GetTemplateChild("verticalTrackLine") as Line;
            _mouseHorizontalTrackLine = GetTemplateChild("horizontalTrackLine") as Line;
           
            Debug.WriteLine($"OnApplyTemplate调用: _mouseVerticalTrackLine={_mouseVerticalTrackLine != null}, _mouseHorizontalTrackLine={_mouseHorizontalTrackLine != null}");
        }
        #endregion

        /// <summary>
        /// 重画标尺数据
        /// </summary>
        /// <param name="drawingContext"></param>
        protected override void OnRender(DrawingContext drawingContext)
        {
            try
            {
                var pen = new Pen(new SolidColorBrush(Colors.Black), 0.8d);
                pen.Freeze();
                Initialize();
                GetActualLength();
                GetStep();
                base.OnRender(drawingContext);

                this.BorderBrush = new SolidColorBrush(Colors.Black);
                this.BorderThickness = new Thickness(0.1);
                this.Background = new SolidColorBrush(Colors.White);

                #region try
                // double actualPx = this._actualLength / DisplayPercent;
                var currentPosition = new Position
                {
                    CurrentStepIndex = 0,
                    Value = 0
                };

                switch (DisplayType)
                {
                    case RulerDisplayType.Horizontal:
                        {
                            /* 绘制前半段 */
                            DrawLine(drawingContext, ZeroPoint, currentPosition, pen, 0);
                            /* 绘制后半段 */
                            DrawLine(drawingContext, ZeroPoint, currentPosition, pen, 1);
                            break;
                        }
                    case RulerDisplayType.Vertical:
                        {
                            /* 绘制前半段 */
                            DrawLine(drawingContext, ZeroPoint, currentPosition, pen, 0);
                            /* 绘制后半段 */
                            DrawLine(drawingContext, ZeroPoint, currentPosition, pen, 1);
                            break;
                        }
                }
                #endregion
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        private void DrawLine(DrawingContext drawingContext, double currentPoint, Position currentPosition, Pen pen, int type)
        {
            while (true)
            {
                var linePercent = 0d;
                if (currentPosition.CurrentStepIndex == 0)
                {

                    var formattedText = GetFormattedText((currentPosition.Value / 10).ToString());


                    switch (DisplayType)
                    {
                        case RulerDisplayType.Horizontal:
                            {
                                var point = new Point(currentPoint + formattedText.Width / 2, formattedText.Height / 3);
                                if (point.X < 0)
                                {
                                    break;
                                }
                                drawingContext.DrawText(formattedText, point);
                                break;
                            }
                        case RulerDisplayType.Vertical:
                            {
                                var point = new Point(this.ActualWidth, currentPoint + formattedText.Height / 2);
                                var rotateTransform = new RotateTransform(90, point.X, point.Y);
                                if (point.Y < 0)
                                {
                                    break;
                                }
                                drawingContext.PushTransform(rotateTransform);
                                drawingContext.DrawText(formattedText, point);
                                drawingContext.Pop();
                                break;
                            }
                    }

                    linePercent = (int)LinePercent.P100;
                }
                else if (IsFinalNum(currentPosition.CurrentStepIndex, 3))
                {
                    linePercent = (int)LinePercent.P30;
                }
                else if (IsFinalNum(currentPosition.CurrentStepIndex, 5))
                {
                    linePercent = (int)LinePercent.P50;
                }
                else if (IsFinalNum(currentPosition.CurrentStepIndex, 7))
                {
                    linePercent = (int)LinePercent.P30;
                }
                else if (IsFinalNum(currentPosition.CurrentStepIndex, 0))
                {
                    linePercent = (int)LinePercent.P70;
                }
                else
                {
                    linePercent = (int)LinePercent.P20;
                }

                linePercent = linePercent * 0.01;

                switch (DisplayType)
                {
                    case RulerDisplayType.Horizontal:
                        {
                            if (currentPoint > 0)
                            {
                                drawingContext.DrawLine(pen, new Point(currentPoint, 0), new Point(currentPoint, this.ActualHeight * linePercent));
                            }

                            if (type == 0)
                            {
                                currentPoint = currentPoint - _stepLength;
                                currentPosition.CurrentStepIndex--;

                                if (currentPosition.CurrentStepIndex < 0)
                                {
                                    currentPosition.CurrentStepIndex = _stepCount - 1;
                                    currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 0);
                                }
                                else if (currentPosition.CurrentStepIndex == 0)
                                {
                                    if (currentPosition.Value % _stepSpan != 0)
                                    {
                                        currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 0);
                                    }
                                }

                                if (currentPoint <= 0)
                                {
                                    return;
                                }
                            }
                            else
                            {
                                currentPoint = currentPoint + _stepLength;
                                currentPosition.CurrentStepIndex++;

                                if (currentPosition.CurrentStepIndex >= _stepCount)
                                {
                                    currentPosition.CurrentStepIndex = 0;
                                    currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 1);
                                }

                                if (currentPoint >= _actualLength)
                                {
                                    return;
                                }
                            }
                            break;
                        }
                    case RulerDisplayType.Vertical:
                        {
                            if (currentPoint > 0)
                            {
                                drawingContext.DrawLine(pen, new Point(0, currentPoint), new Point(this.ActualWidth * linePercent, currentPoint));
                            }
                            if (type == 0)
                            {
                                currentPoint = currentPoint - _stepLength;
                                currentPosition.CurrentStepIndex--;

                                if (currentPosition.CurrentStepIndex < 0)
                                {
                                    currentPosition.CurrentStepIndex = _stepCount - 1;
                                    currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 0);
                                }
                                else if (currentPosition.CurrentStepIndex == 0)
                                {
                                    if (currentPosition.Value % _stepSpan != 0)
                                    {
                                        currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 0);
                                    }
                                }

                                if (currentPoint <= 0)
                                {
                                    return;
                                }
                            }
                            else
                            {
                                currentPoint = currentPoint + _stepLength;
                                currentPosition.CurrentStepIndex++;

                                if (currentPosition.CurrentStepIndex >= _stepCount)
                                {
                                    currentPosition.CurrentStepIndex = 0;
                                    currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 1);
                                }

                                if (currentPoint >= _actualLength)
                                {
                                    return;
                                }
                            }
                            break;
                        }
                }
            }
        }

        /// <summary>
        /// 获取下一个步长值
        /// </summary>
        /// <param name="value">起始值</param>
        /// <param name="times">跨度</param>
        /// <param name="type">半段类型,分为前半段、后半段</param>
        /// <returns></returns>
        private int GetNextStepValue(int value, int times, int type)
        {
            if (type == 0)
            {
                do
                {
                    value--;
                }
                while (value % times != 0);
            }
            else
            {
                do
                {
                    value++;
                }
                while (value % times != 0);
            }
            return (value);
        }

        [Obsolete]
        private FormattedText GetFormattedText(string text)
        {
            return (new FormattedText(text,
                          //CultureInfo.GetCultureInfo("zh-cn"),
                          CultureInfo.GetCultureInfo("en-us"),
                          FlowDirection.LeftToRight,
                          new Typeface("宋体"),
                          12,
                          Brushes.Black));
        }

        private bool IsFinalNum(int value, int finalNum)
        {
            var valueStr = value.ToString();
            if (valueStr.Substring(valueStr.Length - 1, 1) == finalNum.ToString())
            {
                return (true);
            }
            return (false);
        }

        /// <summary>
        /// 初始化获取屏幕的DPI
        /// </summary>
        private void Initialize()
        {
            var dpi = new Dpi();
            dpi.DpiX = Dpi.DpiX;
            dpi.DpiY = Dpi.DpiY;
            if (Dpi.DpiX == 0)
            {
                dpi.DpiX = 96;
            }

            if (Dpi.DpiY == 0)
            {
                dpi.DpiY = 96;
            }

            Dpi = dpi;
            _minStepLengthCm = 0.1;
            _maxStepLengthCm = 0.3;

            if (DisplayPercent == 0)
                DisplayPercent = 1;

            switch (DisplayUnit)
            {
                case RulerDisplayUnit.Pixel:
                    {
                        _stepSpan = P100StepSpanPixel;
                        _stepCount = P100StepCountPixel;
                        break;
                    }
                case RulerDisplayUnit.Cm:
                    {
                        _stepSpan = P100StepSpanCm;
                        _stepCount = P100StepCountCm;
                        break;
                    }
            }
            var width = 15;
            switch (DisplayType)
            {
                case RulerDisplayType.Horizontal:
                    {
                        if (this.ActualHeight == 0)
                        {
                            Height = width;
                        }
                        break;
                    }
                case RulerDisplayType.Vertical:
                    {
                        if (this.ActualWidth == 0)
                        {
                            Width = width;
                        }
                        break;
                    }
            }
        }

        /// <summary>
        /// 获取每一个数字间隔的跨度
        /// </summary>
        private void GetStep()
        {
            switch (DisplayUnit)
            {
                case RulerDisplayUnit.Pixel:
                    {
                        while (true)
                        {
                            var stepSpanCm = _stepSpan / Convert.ToDouble(GetDpi()) * InchCm * DisplayPercent;
                            var stepLengthCm = stepSpanCm / _stepCount;
                            var type = 0;
                            var isOut = false;
                            if (stepLengthCm > _maxStepLengthCm)
                            {
                                type = 1;
                                _stepCount = GetNextStepCount(_stepCount, type, ref isOut);
                            }

                            if (stepLengthCm < _minStepLengthCm)
                            {
                                type = 0;
                                _stepCount = GetNextStepCount(_stepCount, type, ref isOut);
                            }

                            if (stepLengthCm <= _maxStepLengthCm && stepLengthCm >= _minStepLengthCm)
                            {
                                _stepLength = stepSpanCm / InchCm * Convert.ToDouble(GetDpi()) / _stepCount;
                                break;
                            }
                            /* 已超出或小于最大步进长度 */
                            if (!isOut) continue;
                            _stepSpan = GetNextStepSpan(_stepSpan, type);
                        }
                        break;
                    }
            }
        }


        private int GetNextStepCount(int stepCount, int type, ref bool isOut)
        {
            var result = stepCount;
            isOut = false;
            switch (type)
            {
                case 0:
                    {
                        if (stepCount == 20)
                        {
                            result = 10;
                        }
                        else
                        {
                            isOut = true;
                        }
                        break;
                    }
                case 1:
                    {
                        if (stepCount == 10)
                        {
                            result = 20;
                        }
                        else
                        {
                            isOut = true;
                        }

                        break;
                    }
            }
            return result;
        }


        private int GetNextStepSpan(int stepSpan, int type)
        {
            var stepCountStr = stepSpan.ToString();
            var resultStr = string.Empty;

            switch (DisplayUnit)
            {
                case RulerDisplayUnit.Pixel:
                    {
                        switch (type)
                        {
                            case 0:
                                {
                                    if (stepCountStr.IndexOf('5') > -1)
                                    {
                                        resultStr = GetNumberAndZeroNum(1, stepCountStr.Length);
                                    }
                                    else if (stepCountStr.IndexOf('2') > -1)
                                    {
                                        resultStr = GetNumberAndZeroNum(5, stepCountStr.Length - 1);
                                    }
                                    else if (stepCountStr.IndexOf('1') > -1)
                                    {
                                        resultStr = GetNumberAndZeroNum(2, stepCountStr.Length - 1);
                                    }
                                    break;
                                }
                            case 1:
                                {
                                    if (stepCountStr.IndexOf('5') > -1)
                                    {
                                        resultStr = GetNumberAndZeroNum(2, stepCountStr.Length - 1);
                                    }
                                    else if (stepCountStr.IndexOf('2') > -1)
                                    {
                                        resultStr = GetNumberAndZeroNum(1, stepCountStr.Length - 1);
                                    }
                                    else if (stepCountStr.IndexOf('1') > -1)
                                    {
                                        resultStr = GetNumberAndZeroNum(5, stepCountStr.Length - 2);
                                    }
                                    break;
                                }
                        }
                        break;
                    }
            }

            if (string.IsNullOrWhiteSpace(resultStr))
            {
                return 0;
            }

            if (int.TryParse(resultStr, out var result))
            {
                return result;
            }
            return result;
        }


        private string GetNumberAndZeroNum(int num, int zeroNum)
        {
            var result = string.Empty;
            result += num;
            for (var i = 0; i < zeroNum; i++)
            {
                result += "0";
            }
            return (result);
        }


        private int GetDpi()
        {
            switch (DisplayType)
            {
                case RulerDisplayType.Horizontal:
                    {
                        return (Dpi.DpiX);
                    }
                case RulerDisplayType.Vertical:
                    {
                        return (Dpi.DpiY);
                    }
                default:
                    {
                        return (Dpi.DpiX);
                    }
            }
        }

        private void GetActualLength()
        {
            switch (DisplayType)
            {
                case RulerDisplayType.Horizontal:
                    {
                        _actualLength = this.ActualWidth;
                        break;
                    }
                case RulerDisplayType.Vertical:
                    {
                        _actualLength = this.ActualHeight;
                        break;
                    }
            }
        }
    }

    public enum RulerDisplayType
    {
        Horizontal, Vertical
    }

    public enum RulerDisplayUnit
    {
        Pixel,
        Cm
    }

    public enum LinePercent
    {
        P20 = 20,
        P30 = 30, 
        P50 = 50, 
        P70 = 70, 
        P100 = 100
    }

    public struct Dpi
    {
        public int DpiX
        {
            get; set;
        }
        public int DpiY
        {
            get; set;
        }
    }

    public struct Position
    {
        public int Value
        {
            get; set;
        }
        public int CurrentStepIndex
        {
            get; set;
        }
    }
}
 [TemplatePart(Name = "verticalTrackLine", Type = typeof(Line))]
 [TemplatePart(Name = "horizontalTrackLine", Type = typeof(Line))]
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:RulerDemo">

    <Style TargetType="{x:Type local:RulerControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:RulerControl}">
                    <Grid>
                        <ContentPresenter/>
                        <!-- 垂直跟踪线(垂直线,X坐标固定,Y跨越整个高度) -->
                        <Line x:Name="verticalTrackLine"
                              Stroke="Red"
                              StrokeThickness="1"
                              StrokeDashArray="2,2"
                              X1="0" Y1="0" X2="0" Y2="2000"
                              Visibility="Collapsed"/>
                        <!-- 水平跟踪线(水平线,Y坐标固定,X跨越整个宽度) -->
                        <Line x:Name="horizontalTrackLine"
                              Stroke="Blue"
                              StrokeThickness="1"
                              StrokeDashArray="2,2"
                              X1="0" Y1="0" X2="2000" Y2="0"
                              Visibility="Collapsed"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
<UserControl x:Class="RulerDemo.CanvaseCoreEditor"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:RulerDemo"
             mc:Ignorable="d"   x:Name="CanvaseEditor"
             d:DesignHeight="450" d:DesignWidth="800">

    <!-- 主网格,使用 ClipToBounds 确保内容不溢出 -->
    <Grid x:Name="MainGrid" ClipToBounds="True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <!-- 横向标尺 -->
        <local:RulerControl DisplayUnit="pixel"  DisplayType="Horizontal" Grid.Row="0" Grid.Column="1" x:Name="ucPanleHor"/>

        <!-- 纵向标尺 -->
        <local:RulerControl DisplayUnit="pixel"  DisplayType="Vertical" Grid.Row="1" Grid.Column="0" x:Name="ucPanleVer"/>
    </Grid>
</UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace RulerDemo
{
    /// <summary>
    /// CanvaseCoreEditor.xaml 的交互逻辑
    /// </summary>
    public partial class CanvaseCoreEditor : UserControl
    {
        public CanvaseCoreEditor()
        {
            InitializeComponent();
           
        }

        public void MainWindowMouseMove(MouseEventArgs e)
        {
            ucPanleHor.RaiseHorizontalRulerMoveEvent(e);
            ucPanleVer.RaiseVerticalRulerMoveEvent(e);
        }
    }
}
<Window x:Class="RulerDemo.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:RulerDemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>


        <local:CanvaseCoreEditor x:Name="CanvaseCoreEditor"></local:CanvaseCoreEditor>


    </Grid>
</Window>
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace RulerDemo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            MouseMove += MainWindow_MouseMove;
        }

        private void MainWindow_MouseMove(object sender, MouseEventArgs e)
        {
            Debug.WriteLine("MainWindow_MouseMove");
            CanvaseCoreEditor.MainWindowMouseMove(e);
        }
    }
}