最近在  https://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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5 xmlns:d="http://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的方法,实现对线条绘制;通过计算总长度从而得出步长,绘制不同的长度线的标尺刻度。

最新文章

  1. 深入浅出聊优化:从Draw Calls到GC
  2. 编译安装 Zend Opcache 缓存Opcache,加速 PHP
  3. some things
  4. CodeForces 742A Arpa’s hard exam and Mehrdad’s naive cheat
  5. 关于 UINavigationController 的一些知识
  6. 利用模板在RM里部署VM
  7. ZOJ 1808 Immediately Decodable
  8. 基于spark实现表的join操作
  9. ASP.NET注意事项
  10. JQuery replace 替换全部
  11. [MySQL] MySQL的自己主动化安装部署
  12. WallsEveryDay 必应桌面壁纸
  13. 如何在Visio 2007中画接口和实现类的关系图
  14. 生活常用类API调用的代码示例合集:邮编查询、今日热门新闻查询、区号查询等
  15. javascript简单介绍
  16. Java设计模式知识整理
  17. tomcat redis 集群 session共享
  18. Gym 101911E &quot;Painting the Fence&quot;(线段树区间更新+双端队列)
  19. 基于socketserver模块并发套接字
  20. JavaScript 数字转汉字+element时间选择器快速选择

热门文章

  1. 神器----IntelliJ IDEA基本配置
  2. Zookeeper原理系列-Paxos协议的原理和Zookeeper中的应用分析
  3. SpringCloud升级之路2020.0.x版-26.OpenFeign的组件
  4. mysql从零开始之MySQL 教程
  5. 前段---css
  6. Dapr 虚拟机集群部署 (非K8S)
  7. maven指令安装jar包到本地仓库
  8. 【Python】 第三周:基本数据类型
  9. 使用ShardingSphere-JDBC完成Mysql的分库分表和读写分离
  10. js--Symbol 符号基本数据类型