仿Word的支持横轴竖轴的WPF 标尺

  • 2021 年 11 月 20 日
  • 笔记

最近在  //mp.weixin.qq.com/s/3dEO0NZQv5YLqK72atG4Wg   官方公众号看到了 用WPF 制作 标尺

 

在去年项目上也接到了一个需求,用于排版自定义拖拽控件画布对齐的标尺,当时接到的要求是 需要横纵对齐的表次,并且鼠标滑动,刻度的上方需要跟着有影子划过的效果。

 

具体实现如下:

创建 标尺控件 RulerControl.cs 

  1 [TemplatePart(Name = "trackLine", Type = typeof(Line))]
  2     internal class RulerControl : Control
  3     {
  4         public static readonly DependencyProperty DpiProperty = DependencyProperty.Register("Dpi", typeof(Dpi), typeof(RulerControl));
  5         public static readonly DependencyProperty DisplayPercentProperty = DependencyProperty.Register("DisplayPercent", typeof(double), typeof(RulerControl));
  6         public static readonly DependencyProperty DisplayTypeProperty = DependencyProperty.Register("DisplayType", typeof(RulerDisplayType), typeof(RulerControl));
  7         public static readonly DependencyProperty DisplayUnitProperty = DependencyProperty.Register("DisplayUnit", typeof(RulerDisplayUnit), typeof(RulerControl));
  8         public static readonly DependencyProperty ZeroPointProperty = DependencyProperty.Register("ZeroPoint", typeof(double), typeof(RulerControl));
  9 
 10         /// <summary>
 11         /// 定义静态构造函数
 12         /// </summary>
 13         static RulerControl()
 14         {
 15             DefaultStyleKeyProperty.OverrideMetadata(typeof(RulerControl), new FrameworkPropertyMetadata(typeof(RulerControl)));
 16         }
 17 
 18         #region 属性
 19         /// <summary>
 20         /// 屏幕分辨率
 21         /// </summary>
 22         public Dpi Dpi
 23         {
 24             get
 25             {
 26                 return ((Dpi)GetValue(DpiProperty));
 27             }
 28             set
 29             {
 30                 SetValue(DpiProperty, value);
 31             }
 32         }
 33 
 34         /// <summary>
 35         /// 设置0点从哪里开始
 36         /// </summary>
 37         public double ZeroPoint
 38         {
 39             get
 40             {
 41                 return ((double)GetValue(ZeroPointProperty));
 42             }
 43             set
 44             {
 45                 SetValue(ZeroPointProperty, value);
 46                 InvalidateVisual();
 47             }
 48         }
 49 
 50         /// <summary>
 51         /// 显示的比率(目前支持0-1的选项)
 52         /// </summary>
 53         public double DisplayPercent
 54         {
 55             get
 56             {
 57                 return ((double)GetValue(DisplayPercentProperty));
 58             }
 59             set
 60             {
 61                 if (value > 1)
 62                 {
 63                     value = 1;
 64                 }
 65                 SetValue(DisplayPercentProperty, value);
 66                 InvalidateVisual();
 67             }
 68         }
 69 
 70         /// <summary>
 71         /// 显示的类型:枚举类(支持横向或者竖向)
 72         /// </summary>
 73         public RulerDisplayType DisplayType
 74         {
 75             get
 76             {
 77                 return ((RulerDisplayType)GetValue(DisplayTypeProperty));
 78             }
 79             set
 80             {
 81                 SetValue(DisplayTypeProperty, value);
 82             }
 83         }
 84 
 85         /// <summary>
 86         /// 显示的单位:cm和pixel
 87         /// </summary>
 88         public RulerDisplayUnit DisplayUnit
 89         {
 90             get
 91             {
 92                 return ((RulerDisplayUnit)GetValue(DisplayUnitProperty));
 93             }
 94             set
 95             {
 96                 SetValue(DisplayUnitProperty, value);
 97             }
 98         }
 99         #endregion
100 
101         #region 常量
102         public const double _inchCm = 2.54; //一英寸为2.54cm
103         private const int _p100StepSpanPixel = 100;
104         private const int _p100StepSpanCm = 2;
105         private const int _p100StepCountPixel = 20;
106         private const int _p100StepCountCm = 20;
107 
108         #endregion
109 
110         #region 变量
111         private double _minStepLengthCm;
112         private double _maxStepLengthCm;
113         private double _actualLength;
114         private int _stepSpan;
115         private int _stepCount;
116         private double _stepLength;
117         Line mouseVerticalTrackLine;
118         Line mouseHorizontalTrackLine;
119         #endregion
120 
121         #region 标尺边框加指针显示 
122         public void RaiseHorizontalRulerMoveEvent(MouseEventArgs e)
123         {
124             Point mousePoint = e.GetPosition(this);
125             mouseHorizontalTrackLine.X1 = mouseHorizontalTrackLine.X2 = mousePoint.X;
126         }
127         public void RaiseVerticalRulerMoveEvent(MouseEventArgs e)
128         {
129             Point mousePoint = e.GetPosition(this);
130             mouseVerticalTrackLine.Y1 = mouseVerticalTrackLine.Y2 = mousePoint.Y;
131         }
132 
133         public override void OnApplyTemplate()
134         {
135             base.OnApplyTemplate();
136             mouseVerticalTrackLine = GetTemplateChild("verticalTrackLine") as Line;
137             mouseHorizontalTrackLine = GetTemplateChild("horizontalTrackLine") as Line;
138             mouseVerticalTrackLine.Visibility = Visibility.Visible;
139             mouseHorizontalTrackLine.Visibility = Visibility.Visible;
140         }
141         #endregion
142 
143         /// <summary>
144         /// 重画标尺数据
145         /// </summary>
146         /// <param name="drawingContext"></param>
147         protected override void OnRender(DrawingContext drawingContext)
148         {
149             try
150             {
151                 Pen pen = new Pen(new SolidColorBrush(Colors.Black),0.8d);
152                 pen.Freeze();
153                 Initialize();
154                 GetActualLength();
155                 GetStep();
156                 base.OnRender(drawingContext);
157 
158                 this.BorderBrush = new SolidColorBrush(Colors.Black);
159                 this.BorderThickness = new Thickness(0.1);
160                 this.Background = new SolidColorBrush(Colors.White);
161 
162                 #region try
163                 // double actualPx = this._actualLength / DisplayPercent;
164                 Position currentPosition = new Position
165                 {
166                     CurrentStepIndex = 0,
167                     Value = 0
168                 };
169 
170                 switch (DisplayType)
171                 {
172                     case RulerDisplayType.Horizontal:
173                         {
174                             /* 绘制前半段 */
175                             DrawLine(drawingContext, ZeroPoint, currentPosition, pen, 0);
176                             /* 绘制后半段 */
177                             DrawLine(drawingContext, ZeroPoint, currentPosition, pen, 1);
178                             break;
179                         }
180                     case RulerDisplayType.Vertical:
181                         {
182                             /* 绘制前半段 */
183                             DrawLine(drawingContext, ZeroPoint, currentPosition, pen, 0);
184                             /* 绘制后半段 */
185                             DrawLine(drawingContext, ZeroPoint, currentPosition, pen, 1);
186                             break;
187                         }
188                 }
189                 #endregion
190             }
191             catch (Exception ex)
192             {
193                 Console.WriteLine(ex.Message);
194             }
195         }
196 
197         private void DrawLine(DrawingContext drawingContext, double currentPoint, Position currentPosition, Pen pen, int type)
198         {
199             double linePercent = 0d;
200             while (true)
201             {
202                 if (currentPosition.CurrentStepIndex == 0)
203                 {
204             
205                     FormattedText formattedText = GetFormattedText((currentPosition.Value / 10).ToString());
206 
207                     
208                     switch (DisplayType)
209                     {
210                         case RulerDisplayType.Horizontal:
211                             {
212                                 var point = new Point(currentPoint + formattedText.Width / 2, formattedText.Height / 3);
213                                 if (point.X<0)
214                                 {
215                                     break;
216                                 }
217                                 drawingContext.DrawText(formattedText, point);
218                                 break;
219                             }
220                         case RulerDisplayType.Vertical:
221                             {       
222                                 Point point = new Point(this.ActualWidth, currentPoint + formattedText.Height / 2);
223                                 RotateTransform rotateTransform = new RotateTransform(90, point.X, point.Y);
224                                 if (point.Y<0)
225                                 {
226                                     break;
227                                 }
228                                 drawingContext.PushTransform(rotateTransform);
229                                 drawingContext.DrawText(formattedText, point);
230                                 drawingContext.Pop();
231                                 break;
232                             }
233                     }
234 
235                     linePercent = (int)LinePercent.P100;
236                 }
237                 else if (IsFinalNum(currentPosition.CurrentStepIndex, 3))
238                 {
239                     linePercent = (int)LinePercent.P30;
240                 }
241                 else if (IsFinalNum(currentPosition.CurrentStepIndex, 5))
242                 {
243                     linePercent = (int)LinePercent.P50;
244                 }
245                 else if (IsFinalNum(currentPosition.CurrentStepIndex, 7))
246                 {
247                     linePercent = (int)LinePercent.P30;
248                 }
249                 else if (IsFinalNum(currentPosition.CurrentStepIndex, 0))
250                 {
251                     linePercent = (int)LinePercent.P70;
252                 }
253                 else
254                 {
255                     linePercent = (int)LinePercent.P20;
256                 }
257 
258                 linePercent = linePercent * 0.01;        
259 
260                 switch (DisplayType)
261                 {
262                     case RulerDisplayType.Horizontal:
263                         {
264                             if (currentPoint > 0)
265                             {
266                                 drawingContext.DrawLine(pen, new Point(currentPoint, 0), new Point(currentPoint, this.ActualHeight * linePercent));
267                             }
268 
269                             if (type == 0)
270                             {
271                                 currentPoint = currentPoint - _stepLength;
272                                 currentPosition.CurrentStepIndex--;
273 
274                                 if (currentPosition.CurrentStepIndex < 0)
275                                 {
276                                     currentPosition.CurrentStepIndex = _stepCount - 1;
277                                     currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 0);
278                                 }
279                                 else if (currentPosition.CurrentStepIndex == 0)
280                                 {
281                                     if (currentPosition.Value % _stepSpan != 0)
282                                     {
283                                         currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 0);
284                                     }
285                                 }
286 
287                                 if (currentPoint <= 0)
288                                 {
289                                     return;
290                                 }
291                             }
292                             else
293                             {
294                                 currentPoint = currentPoint + _stepLength;
295                                 currentPosition.CurrentStepIndex++;
296 
297                                 if (currentPosition.CurrentStepIndex >= _stepCount)
298                                 {
299                                     currentPosition.CurrentStepIndex = 0;
300                                     currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 1);
301                                 }
302 
303                                 if (currentPoint >= _actualLength)
304                                 {
305                                     return;
306                                 }
307                             }
308                             break;
309                         }
310                     case RulerDisplayType.Vertical:
311                         {
312                             if (currentPoint > 0)
313                             {
314                                 drawingContext.DrawLine(pen, new Point(0, currentPoint), new Point(this.ActualWidth * linePercent, currentPoint));
315                             }
316                             if (type == 0)
317                             {
318                                 currentPoint = currentPoint - _stepLength;
319                                 currentPosition.CurrentStepIndex--;
320 
321                                 if (currentPosition.CurrentStepIndex < 0)
322                                 {
323                                     currentPosition.CurrentStepIndex = _stepCount - 1;
324                                     currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 0);
325                                 }
326                                 else if (currentPosition.CurrentStepIndex == 0)
327                                 {
328                                     if (currentPosition.Value % _stepSpan != 0)
329                                     {
330                                         currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 0);
331                                     }
332                                 }
333 
334                                 if (currentPoint <= 0)
335                                 {
336                                     return;
337                                 }
338                             }
339                             else
340                             {
341                                 currentPoint = currentPoint + _stepLength;
342                                 currentPosition.CurrentStepIndex++;
343 
344                                 if (currentPosition.CurrentStepIndex >= _stepCount)
345                                 {
346                                     currentPosition.CurrentStepIndex = 0;
347                                     currentPosition.Value = GetNextStepValue(currentPosition.Value, _stepSpan, 1);
348                                 }
349 
350                                 if (currentPoint >= _actualLength)
351                                 {
352                                     return;
353                                 }
354                             }
355                             break;
356                         }
357                 }
358             }
359         }
360 
361         /// <summary>
362         /// 获取下一个步长值
363         /// </summary>
364         /// <param name="value">起始值</param>
365         /// <param name="times">跨度</param>
366         /// <param name="type">半段类型,分为前半段、后半段</param>
367         /// <returns></returns>
368         private int GetNextStepValue(int value, int times, int type)
369         {
370             if (type == 0)
371             {
372                 do
373                 {
374                     value--;
375                 }
376                 while (value % times != 0);
377             }
378             else
379             {
380                 do
381                 {
382                     value++;
383                 }
384                 while (value % times != 0);
385             }
386             return (value);
387         }
388 
389         [Obsolete]
390         private FormattedText GetFormattedText(string text)
391         {
392             return (new FormattedText(text,
393                           //CultureInfo.GetCultureInfo("zh-cn"),
394                           CultureInfo.GetCultureInfo("en-us"),
395                           FlowDirection.LeftToRight,
396                           new Typeface("宋体"),
397                           12,
398                           Brushes.Black));
399         }
400 
401         private bool IsFinalNum(int value, int finalNum)
402         {
403             string valueStr = value.ToString();
404             if (valueStr.Substring(valueStr.Length - 1, 1) == finalNum.ToString())
405             {
406                 return (true);
407             }
408             return (false);
409         }
410 
411         /// <summary>
412         /// 初始化获取屏幕的DPI
413         /// </summary>
414         private void Initialize()
415         {
416             Dpi dpi = new Dpi();
417             dpi.DpiX = Dpi.DpiX;
418             dpi.DpiY = Dpi.DpiY;
419             if (Dpi.DpiX == 0)
420             {
421                 dpi.DpiX = 96;
422             }
423 
424             if (Dpi.DpiY == 0)
425             {
426                 dpi.DpiY = 96;
427             }
428 
429             Dpi = dpi;
430             _minStepLengthCm = 0.1;
431             _maxStepLengthCm = 0.3;
432 
433             if (DisplayPercent == 0)
434                 DisplayPercent = 1;
435 
436             switch (DisplayUnit)
437             {
438                 case RulerDisplayUnit.pixel:
439                     {
440                         _stepSpan = _p100StepSpanPixel;
441                         _stepCount = _p100StepCountPixel;
442                         break;
443                     }
444                 case RulerDisplayUnit.cm:
445                     {
446                         _stepSpan = _p100StepSpanCm;
447                         _stepCount = _p100StepCountCm;
448                         break;
449                     }
450             }
451             int width = 15;
452             switch (DisplayType)
453             {
454                 case RulerDisplayType.Horizontal:
455                     {
456                         if (this.ActualHeight == 0)
457                         {
458                             Height = width;
459                         }
460                         break;
461                     }
462                 case RulerDisplayType.Vertical:
463                     {
464                         if (this.ActualWidth == 0)
465                         {
466                             Width = width;
467                         }
468                         break;
469                     }
470             }
471         }
472 
473         /// <summary>
474         /// 获取每一个数字间隔的跨度
475         /// </summary>
476         private void GetStep()
477         {
478             switch (DisplayUnit)
479             {
480                 case RulerDisplayUnit.pixel:
481                     {
482                         double stepSpanCm;
483                         while (true)
484                         {  
485                             stepSpanCm = _stepSpan / Convert.ToDouble(GetDpi()) * _inchCm * DisplayPercent;
486                             double stepLengthCm = stepSpanCm / _stepCount;
487                             int type = 0;
488                             bool isOut = false;
489                             if (stepLengthCm > _maxStepLengthCm)
490                             {
491                                 type = 1;
492                                 _stepCount = GetNextStepCount(_stepCount, type, ref isOut);
493                             }
494 
495                             if (stepLengthCm < _minStepLengthCm)
496                             {
497                                 type = 0;
498                                 _stepCount = GetNextStepCount(_stepCount, type, ref isOut);
499                             }
500 
501                             if (stepLengthCm <= _maxStepLengthCm && stepLengthCm >= _minStepLengthCm)
502                             {
503                                 _stepLength = stepSpanCm / _inchCm * Convert.ToDouble(GetDpi()) / _stepCount;
504                                 break;
505                             }
506                             /* 已超出或小于最大步进长度 */
507                             if (isOut)
508                             {
509                                 _stepSpan = GetNextStepSpan(_stepSpan, type);
510                                 continue;
511                             }
512                         }
513                         break;
514                     }
515             }
516         }
517 
518        
519         private int GetNextStepCount(int stepCount, int type, ref bool isOut)
520         {
521             int result = stepCount;
522             isOut = false;
523             switch (type)
524             {
525                 case 0:
526                     {
527                         if (stepCount == 20)
528                         {
529                             result = 10;
530                         }
531                         else
532                         {
533                             isOut = true;
534                         }
535                         break;
536                     }
537                 case 1:
538                     {
539                         if (stepCount == 10)
540                         {
541                             result = 20;
542                         }
543                         else
544                         {
545                             isOut = true;
546                         }
547 
548                         break;
549                     }
550             }
551             return result;
552         }
553 
554 
555         private int GetNextStepSpan(int stepSpan, int type)
556         {
557             string stepCountStr = stepSpan.ToString();
558             string resultStr = string.Empty;
559 
560             switch (DisplayUnit)
561             {
562                 case RulerDisplayUnit.pixel:
563                     {
564                         switch (type)
565                         {
566                             case 0:
567                                 {
568                                     if (stepCountStr.IndexOf('5') > -1)
569                                     {
570                                         resultStr = GetNumberAndZeroNum(1, stepCountStr.Length);
571                                     }
572                                     else if (stepCountStr.IndexOf('2') > -1)
573                                     {
574                                         resultStr = GetNumberAndZeroNum(5, stepCountStr.Length - 1);
575                                     }
576                                     else if (stepCountStr.IndexOf('1') > -1)
577                                     {
578                                         resultStr = GetNumberAndZeroNum(2, stepCountStr.Length - 1);
579                                     }
580                                     break;
581                                 }
582                             case 1:
583                                 {
584                                     if (stepCountStr.IndexOf('5') > -1)
585                                     {
586                                         resultStr = GetNumberAndZeroNum(2, stepCountStr.Length - 1);
587                                     }
588                                     else if (stepCountStr.IndexOf('2') > -1)
589                                     {
590                                         resultStr = GetNumberAndZeroNum(1, stepCountStr.Length - 1);
591                                     }
592                                     else if (stepCountStr.IndexOf('1') > -1)
593                                     {
594                                         resultStr = GetNumberAndZeroNum(5, stepCountStr.Length - 2);
595                                     }
596                                     break;
597                                 }
598                         }
599                         break;
600                     }
601             }
602 
603             int result = 0;
604             if (string.IsNullOrWhiteSpace(resultStr))
605             {
606                 return 0;
607             }
608 
609             if (int.TryParse(resultStr, out result))
610             {
611                 return result;
612             }
613             return result;
614         }
615 
616 
617         private string GetNumberAndZeroNum(int num, int zeroNum)
618         {
619             string result = string.Empty;
620             result += num;
621             for (int i = 0; i < zeroNum; i++)
622             {
623                 result += "0";
624             }
625             return (result);
626         }
627 
628 
629         private int GetDpi()
630         {
631             switch (DisplayType)
632             {
633                 case RulerDisplayType.Horizontal:
634                     {
635                         return (Dpi.DpiX);
636                     }
637                 case RulerDisplayType.Vertical:
638                     {
639                         return (Dpi.DpiY);
640                     }
641                 default:
642                     {
643                         return (Dpi.DpiX);
644                     }
645             }
646         }
647 
648         private void GetActualLength()
649         {
650             switch (DisplayType)
651             {
652                 case RulerDisplayType.Horizontal:
653                     {
654                         _actualLength = this.ActualWidth;
655                         break;
656                     }
657                 case RulerDisplayType.Vertical:
658                     {
659                         _actualLength = this.ActualHeight;
660                         break;
661                     }
662             }
663         }
664     }
665 
666     public enum RulerDisplayType
667     {
668         Horizontal, Vertical
669     }
670 
671     public enum RulerDisplayUnit
672     {
673         pixel, cm
674     }
675 
676     public enum LinePercent
677     {
678         P20 = 20, P30 = 30, P50 = 50, P70 = 70, P100 = 100
679     }
680 
681     public struct Dpi
682     {
683         public int DpiX
684         {
685             get; set;
686         }
687         public int DpiY
688         {
689             get; set;
690         }
691     }
692 
693     public struct Position
694     {
695         public int Value
696         {
697             get; set;
698         }
699         public int CurrentStepIndex
700         {
701             get; set;
702         }
703     }

 

控件的引用:

 1 <UserControl x:Class="Hjmos.DataPainter.Controls.CanvaseCoreEditor"
 2              xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
 3              xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
 4              xmlns:mc="//schemas.openxmlformats.org/markup-compatibility/2006" 
 5              xmlns:d="//schemas.microsoft.com/expression/blend/2008" 
 6              xmlns:local="clr-namespace:Hjmos.DataPainter.Controls"
 7              mc:Ignorable="d" 
 8              x:Name="CanvaseEditor"
 9              d:DesignHeight="450" d:DesignWidth="800">
10     <Grid>
11      
12             <Grid.ColumnDefinitions>
13                 <ColumnDefinition Width="20"/>
14                 <ColumnDefinition/>
15             </Grid.ColumnDefinitions>
16             <Grid.RowDefinitions>
17                 <RowDefinition Height="20"/>
18                 <RowDefinition/>
19             </Grid.RowDefinitions>
20 <!--横向标尺-->
21             <local:RulerControl DisplayUnit="pixel"  DisplayType="Horizontal" Grid.Row="0" Grid.Column="1" x:Name="ucPanleHor"/>
22 <!--纵向标尺-->
23             <local:RulerControl DisplayUnit="pixel"  DisplayType="Vertical" Grid.Row="1" Grid.Column="0" x:Name="ucPanleVer"/>
24 
25        
26     </Grid>
27 </UserControl>

 

 鼠标在画布移动的时候触发标尺上的刻度阴影移动

        /// <summary>
        /// 鼠标在画布移动的时候触发标尺上的刻度阴影移动
        /// </summary>
        /// <param name="obj"></param>
        private void _diagramView_MouseMove(MouseEventArgs e)
        {
            ucPanleHor.RaiseHorizontalRulerMoveEvent(e);
            ucPanleVer.RaiseVerticalRulerMoveEvent(e);
        }

 

总结:技术点主要是用到了WPF 内部的渲染机制,调用 OnRender的方法,实现对线条绘制;通过计算总长度从而得出步长,绘制不同的长度线的标尺刻度。