-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
1335 lines (1243 loc) · 70.4 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>https://www.avaloninparadise.cn</id>
<title>理想乡</title>
<updated>2021-11-27T08:38:49.122Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="https://www.avaloninparadise.cn"/>
<link rel="self" href="https://www.avaloninparadise.cn/atom.xml"/>
<subtitle>远离喧嚣,静下心来感受世界</subtitle>
<logo>https://www.avaloninparadise.cn/images/avatar.png</logo>
<icon>https://www.avaloninparadise.cn/favicon.ico</icon>
<rights>All rights reserved 2021, 理想乡</rights>
<entry>
<title type="html"><![CDATA[网络编程学习总结]]></title>
<id>https://www.avaloninparadise.cn/post/tcp-wang-luo-bian-cheng-xue-xi-zong-jie/</id>
<link href="https://www.avaloninparadise.cn/post/tcp-wang-luo-bian-cheng-xue-xi-zong-jie/">
</link>
<updated>2021-04-27T05:55:56.000Z</updated>
<content type="html"><![CDATA[<h1 id="1-核心模块">1. 核心模块</h1>
<table>
<thead>
<tr>
<th>模块</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>序列化(Serializer)</td>
<td>把需要发送的数据序列化成能够在网络上传输的数据格式</td>
</tr>
<tr>
<td>数据连接(e.g. Socket)</td>
<td>负责收发序列化后的数据</td>
</tr>
<tr>
<td>调度器(Dispatcher)</td>
<td>把收到的消息对应到需要执行的操作</td>
</tr>
<tr>
<td>连接管理(ConnectionManager)</td>
<td>负责连接的建立,移除等</td>
</tr>
</tbody>
</table>
<h1 id="2-基于上述模块的一个网络编程模型">2. 基于上述模块的一个网络编程模型</h1>
<figure data-type="image" tabindex="1"><img src="https://www.avaloninparadise.cn/post-images/1619503368586.png" alt="" loading="lazy"></figure>
<h1 id="3-模块详解">3. 模块详解</h1>
<h2 id="31-序列化">3.1 序列化</h2>
<p>常见的序列化格式有xml、json、MessagePack等。<br>
把序列化格式对应的类作为调度器的参数会使得架构更容易。通常会使用泛型来进行序列化。常见的序列化接口如下:</p>
<pre><code class="language-csharp">public interface ISerializer<TMessage>
{
TMessage Serialize<T>(T message);
T Deserialize<T>(TMessage m);
}
</code></pre>
<h2 id="32-数据连接">3.2 数据连接</h2>
<p>常见的数据连接为TCP/IP协议。实际上任何用于通信的协议都行。一般来说区别只是在于建立连接的方式,连接的断开检测,以及网络流格式和本地格式。</p>
<h2 id="33-调度器">3.3 调度器</h2>
<p>网络编程的核心之一就是调度器。如何将收到的消息对应到需要执行的函数,如何从收到的消息中获取到执行函数需要的参数。是调度器设计的难点之一。另一个设计难点在于,如何将不同类型的函数注册给调度器。</p>
<p>如何将消息匹配到函数?ASP.NET中使用路由解决这一问题。一般来说,是在消息中携带一段用于匹配函数的信息,在函数注册到调度器时,把如何从这段信息中匹配到该函数的规则一同注册到调度器。收到消息时,挨个按照规则进行匹配,执行符合条件的函数。</p>
<p>如何从消息中获取到执行函数需要的参数?简单的做法就是反射。考虑到执行效率,可以编写特定的反序列化函数。</p>
<p>如何将不同类型的函数注册给调度器?这个就要结合序列化我们提到的,将序列化格式对应的类作为参数,来解释了。因为我们传输的实际上都是序列化之后的数据,所以我们可以把任何函数都用一个闭包包起来,而闭包的参数就使用序列化格式的类。</p>
<h2 id="34-连接管理">3.4 连接管理</h2>
<p>连接管理的主要几个目的:</p>
<ol>
<li>hold住通信线程</li>
<li>负责在断开时调用dispose释放资源。长时间未收到消息认定连接断开,释放相关资源。</li>
</ol>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[ProgressBar滚动动画以及关闭CommonFileDialog的方式]]></title>
<id>https://www.avaloninparadise.cn/post/progressbar-gun-dong-dong-hua-yi-ji-guan-bi-commonfiledialog-de-fang-shi/</id>
<link href="https://www.avaloninparadise.cn/post/progressbar-gun-dong-dong-hua-yi-ji-guan-bi-commonfiledialog-de-fang-shi/">
</link>
<updated>2020-12-14T05:59:53.000Z</updated>
<content type="html"><![CDATA[<h1 id="progressbar动画">ProgressBar动画</h1>
<p>这里的动画是指最基本的一个长条从左往右滚动的那种。</p>
<pre><code class="language-xaml"> <Window.Resources>
<Storyboard x:Key="OnLoaded">
<DoubleAnimationUsingKeyFrames RepeatBehavior="Forever" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="border">
<LinearDoubleKeyFrame KeyTime="0" Value="0.25"/>
<LinearDoubleKeyFrame KeyTime="0:0:1" Value="0.25"/>
<LinearDoubleKeyFrame KeyTime="0:0:2" Value="0.25"/>
</DoubleAnimationUsingKeyFrames>
<PointAnimationUsingKeyFrames RepeatBehavior="Forever" Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)" Storyboard.TargetName="border">
<LinearPointKeyFrame KeyTime="0" Value="-0.5,0.5"/>
<LinearPointKeyFrame KeyTime="0:0:1" Value="0.5,0.5"/>
<LinearPointKeyFrame KeyTime="0:0:2" Value="1.5,0.5"/>
</PointAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Border Name="border" Background="Red">
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform/>
</TransformGroup>
</Border.RenderTransform>
</Border>
</code></pre>
<h1 id="关闭commonfiledialog">关闭CommonFileDialog</h1>
<pre><code class="language-c#"> static class DialogCloser
{
public static void Close()
{
// Enumerate windows to find dialogs
EnumThreadWndProc callback = new EnumThreadWndProc(checkWindow);
EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero);
GC.KeepAlive(callback);
}
private static bool checkWindow(IntPtr hWnd, IntPtr lp)
{
// Checks if <hWnd> is a Windows dialog
StringBuilder sb = new StringBuilder(260);
GetClassName(hWnd, sb, sb.Capacity);
if (sb.ToString() == "#32770")
{
// Close it by sending WM_CLOSE to the window
SendMessage(hWnd, 0x0010, IntPtr.Zero, IntPtr.Zero);
}
return true;
}
// P/Invoke declarations
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
</code></pre>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[使用ChromeTracing工具协助定位bug]]></title>
<id>https://www.avaloninparadise.cn/post/shi-yong-roslyn-wei-mei-ge-han-shu-tian-jia-dai-ma/</id>
<link href="https://www.avaloninparadise.cn/post/shi-yong-roslyn-wei-mei-ge-han-shu-tian-jia-dai-ma/">
</link>
<updated>2020-09-23T13:36:58.000Z</updated>
<content type="html"><![CDATA[<h1 id="背景">背景</h1>
<p>不知各位是否经历过多线程bug的地狱。在各种线程的时序之间来回挣扎,大概就是写多线程的痛。到底是谁先谁后,两次运行之间到底有什么区别,为什么有的时候会复现bug,有的时候就不能。此类问题会令人怀疑人生。在读完本文后,这些都将成为过去式!</p>
<h1 id="chrome-tracing-tool">Chrome Tracing Tool</h1>
<p>Chrome Tracing Tool是chrome浏览器提供的一个工具。该工具考可以录制或加载已录制好的数据,并将其可视化的展现给用户,以便用户进行分析。没错,就是为了搞清楚,代码在运行时,到底干了什么。</p>
<h2 id="如何使用">如何使用</h2>
<h3 id="1-生成数据">1. 生成数据</h3>
<p>由于我不是搞web的,所以录制对我来说没什么用。所以我得自己生成数据。具体的数据格式请看<a href="https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit?pli=1">文档</a>。<br>
特别复杂的我反正是没用上,所以基本的json格式就是</p>
<pre><code class="language-json">{
"traceEvents" : [
{
"cat": "function", //分类
"dur": 20, //持续时间
"name": "Event1", //事件名称
"ph": "X",
"pid": 0, //进程ID
"tid": 1, //线程ID
"ts": 0, //事件起始时间
"args": { //额外参数
"filePath": "sc file path1"
}
},
{
"cat": "function", //分类
"dur": 20, //持续时间
"name": "Event2", //事件名称
"ph": "X",
"pid": 0, //进程ID
"tid": 2, //线程ID
"ts": 10, //事件起始时间
"args": { //额外参数
"filePath": "sc file path2"
}
}
]
}
</code></pre>
<h3 id="2-在chrome中打开">2. 在chrome中打开</h3>
<p>将上面的json保存,然后打开chrome,地址栏输入<code>Chrome://Tracing</code>按回车。我这边用的Edge,所以地址栏稍稍有点变化。总之会看起来像这样:<br>
<img src="https://www.avaloninparadise.cn/post-images/1600916179025.png" alt="EdgeTracing" loading="lazy"><br>
之后我们点左上角的load按钮,选中我们的数据。<strong>注意不要直接粘贴之前文章中的json,因为json是不允许注释的,你需要手动把那些注释都去掉。</strong><br>
<img src="https://www.avaloninparadise.cn/post-images/1600916513250.png" alt="完成加载" loading="lazy"></p>
<h3 id="3-查看数据">3. 查看数据</h3>
<p>这个就自己摸索啦。可以用鼠标点点点,也可以用键盘。最近常用的是<code>A</code>和<code>D</code>,对应往左和往右,<code>W</code>和<code>S</code>对应的是放大和缩小。</p>
<h1 id="如何生成数据">如何生成数据?</h1>
<p>很明显,由于不能录制,如何生成数据就成了一个问题。这里可以参考一下这位小哥的视频(<a href="https://www.bilibili.com/video/BV1VJ411M7WR?p=81">B站</a>,<a href="https://www.youtube.com/watch?v=xlAH4dbMVnU&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=81">原视频</a>)。我会告诉你我就是从这学来的吗?为了避免你们太长不看,我把github的文件地址也放在<a href="https://github.com/TheCherno/Hazel/blob/0090b94af9be167cf5c399d8091c58e78bff41fd/Hazel/src/Hazel/Debug/Instrumentor.h">这里</a>了。<br>
C++的话参考小哥的就可以了,其实还应该把写文件用队列去做以免影响到性能。但是C#应该怎么办呢?C#也没有像C++一样的特性(没看视频的不知道我在说什么)。唯一类似的就是using了,能够自动的在结束时调用Dispose方法。虽然可能会影响到GC,但是我用C#也不做什么高性能的东西,也就无所谓了。我把我自己用的代码贴在这里。</p>
<blockquote>
<p>这里必须吐槽一下Gridea,不能上传文件就是很难受。也不知道是不是github page的限制,等我有空了自己添加这个功能试试看。</p>
</blockquote>
<p>AutoEventTracer.cs</p>
<pre><code class="language-c#">
using System;
using System.Runtime.CompilerServices;
namespace EventTracing
{
public class AutoEventTracer : IDisposable
{
public void Dispose()
{
EventTracer.TraceEvent(_marker,_eventName,_filePath);
}
private readonly EventTraceMaker _marker;
private readonly string _eventName;
private readonly string _filePath;
public AutoEventTracer([CallerMemberName] string eventName = null, [CallerFilePath] string filePath = null)
{
_marker = EventTracer.StartTrace();
_eventName = eventName;
_filePath = filePath;
}
}
}
</code></pre>
<p>EventTraceMaker.cs</p>
<pre><code class="language-c#">using System;
namespace EventTracing
{
public class EventTraceMaker
{
private readonly DateTime m_startTime;
private double m_duration;
internal EventTraceMaker()
{
m_startTime = DateTime.Now;
}
internal void Stop()
{
m_duration = (DateTime.Now - m_startTime).TotalMilliseconds;
}
internal double GetDuration()
{
return m_duration;
}
internal DateTime StartTime { get { return m_startTime; } }
}
}
</code></pre>
<p>EventTracer.cs</p>
<pre><code class="language-c#">using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
namespace EventTracing
{
public class EventTracer
{
private readonly BlockingCollection<string> m_messageQueue;
private EventTracer()
{
m_messageQueue = new BlockingCollection<string>();
}
private Task m_saveTask;
private async void SaveTraceInfo(string filePath)
{
m_saveTask = Task.Run(() =>
{
using var fs = File.Open(filePath, FileMode.Create, FileAccess.Write);
using var sw = new StreamWriter(fs);
foreach (var message in m_messageQueue.GetConsumingEnumerable())
{
sw.Write(message);
sw.Flush();
}
});
await m_saveTask;
}
public static void Init(string filePath)
{
s_instance.IInit(filePath);
}
private bool m_traceInited = false;
public void IInit(string filePath)
{
if (m_traceInited)
throw new Exception("Tracing already initialized.");
m_traceInited = true;
SaveTraceInfo(filePath);
}
private static readonly EventTracer s_instance;
private static readonly DateTime s_applicationStartTime;
static EventTracer()
{
s_instance = new EventTracer();
s_applicationStartTime = DateTime.Now;
s_instance.m_messageQueue.Add("{\"otherData\":{},\"displayTimeUnit\":\"ms\",\"traceEvents\":[{}");
}
public static EventTracer GetInstance()
{
return s_instance;
}
public static EventTraceMaker StartTrace()
{
return s_instance.IStartTrace();
}
public EventTraceMaker IStartTrace()
{
return new EventTraceMaker();
}
public static void TraceEvent(EventTraceMaker marker, [CallerMemberName] string eventName = null, [CallerFilePath] string filePath = null)
{
s_instance.ITraceEvent(marker, eventName, filePath);
}
public void ITraceEvent(EventTraceMaker marker, [CallerMemberName] string eventName = null, [CallerFilePath] string filePath = null)
{
marker.Stop();
var message = $",{{\"cat\":\"function\",\"dur\":{marker.GetDuration() * 1000},\"name\":\"{eventName}\",\"ph\":\"X\",\"pid\":0,\"tid\":{Thread.CurrentThread.ManagedThreadId},\"ts\":{(marker.StartTime - s_applicationStartTime).TotalMilliseconds * 1000},\"args\":{{\"filePath\":\"{filePath.Replace("\\", "\\\\")}\"}}}}";
m_messageQueue.Add(message);
}
public static void EndTrace()
{
s_instance.m_messageQueue.CompleteAdding();
s_instance.m_saveTask.Wait();
s_instance.IEndTrace();
}
private bool m_traceEnded = false;
public void IEndTrace()
{
if (m_traceEnded)
throw new Exception("Tracing has already ended.");
m_traceEnded = true;
m_messageQueue.Add("]}");
m_messageQueue.CompleteAdding();
}
}
}
</code></pre>
<h2 id="在项目中如何使用">在项目中如何使用?</h2>
<p>请参考下面的代码。同时记得在程序开始和结束时分别调用<code>EventTracer.Init(filePath)</code>和<code>EventTracer.EndTrace()</code>。</p>
<pre><code class="language-c#">//在任何你需要记录的地方直接用!
public void Test()
{
using (var tracer = new AutoEventTracer())
{
//your actual code here
}
}
public void Test1()
{
//手动指定事件名
using (var tracer = new AutoEventTracer("Test1Event"))
{
//your actual code here
}
}
</code></pre>
<h1 id="已有项目如何使用">已有项目如何使用?</h1>
<p>有读者可能会想了:亲,你说的都对,但是我总不能一点一点的往已有项目添加吧。<br>
淡定,贴心如我已经帮你想好了!请看下面的代码!运行它就能够往指定文件夹下的cs文件中的<strong>每个函数体</strong>添加AutoEventTracer。缺点就是可能会把一些无关紧要的函数也添加上,这个时候你就得手动去改啦。<strong>运行前记得版本备份,玩坏了我不负责。</strong></p>
<blockquote>
<p>如何编译下面的code请参考<a href="https://blog.walterlv.com/post/analysis-code-of-existed-projects-using-roslyn">这篇文章</a>。<br>
对Roslyn感兴趣请阅读<a href="https://joshvarty.com/learn-roslyn-now/">此系列</a><br>
想知道我那串linq怎么写的,看<a href="http://roslynquoter.azurewebsites.net/">这里</a></p>
</blockquote>
<pre><code class="language-c#">class Program
{
static void Main(string[] args)
{
DoWork();
}
static void DoWork()
{
var files = GetAllCSharpFilesInFolder(@"your file path");
Parallel.For(0, files.Length, (i) =>
{
Console.WriteLine($"{i} working on {files[i]}");
ParseCsharpFile(files[i]);
Console.WriteLine($"{i} working on {files[i]}");
});
}
static string[] GetAllCSharpFilesInFolder(string folder)
{
var directoryInfo = new DirectoryInfo(folder);
return directoryInfo
.GetFiles("*.cs", SearchOption.AllDirectories)
.Select(fi=>fi.FullName).ToArray();
}
static async Task ParseCsharpFileAsync(string filePath)
{
await Task.Run(() => ParseCsharpFile(filePath));
}
static void ParseCsharpFile(string filePath)
{
SyntaxNode result = null;
using (var fs = File.Open(filePath, FileMode.Open, FileAccess.Read))
{
using (var sr = new StreamReader(fs))
{
var rewriter = new AddEventTrace();
result = rewriter.Visit(CSharpSyntaxTree.ParseText(sr.ReadToEnd()).GetRoot());
}
}
File.WriteAllText(filePath, result.ToFullString());
}
}
class AddEventTrace : CSharpSyntaxRewriter
{
public override SyntaxNode VisitBlock(BlockSyntax node)
{
var usingStatement = SyntaxFactory.UsingStatement(SyntaxFactory.Block(node.Statements))
.WithDeclaration(
SyntaxFactory.VariableDeclaration(
SyntaxFactory.IdentifierName(
SyntaxFactory.Identifier(
SyntaxFactory.TriviaList(),
"var",
SyntaxFactory.TriviaList(
SyntaxFactory.Space))))
.WithVariables(
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier(SyntaxFactory.TriviaList(),
"eventTracer",
SyntaxFactory.TriviaList(
SyntaxFactory.Space)))
.WithInitializer(
SyntaxFactory.EqualsValueClause(
SyntaxFactory.ObjectCreationExpression(
SyntaxFactory.IdentifierName(SyntaxFactory.Identifier("EventTracer")))
.WithNewKeyword(
SyntaxFactory.Token(
SyntaxFactory.TriviaList(SyntaxFactory.Space),
SyntaxKind.NewKeyword,
SyntaxFactory.TriviaList(
SyntaxFactory.Space)))
.WithArgumentList(
SyntaxFactory.ArgumentList()))
))));
return node.Update(node.OpenBraceToken, new SyntaxList<SyntaxNode>(usingStatement), node.CloseBraceToken);
}
}
```</code></pre>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[手动创建Telerik空项目(Telerik for WPF)]]></title>
<id>https://www.avaloninparadise.cn/post/shou-dong-chuang-jian-telerik-kong-xiang-mu-telerik-for-wpf/</id>
<link href="https://www.avaloninparadise.cn/post/shou-dong-chuang-jian-telerik-kong-xiang-mu-telerik-for-wpf/">
</link>
<updated>2020-02-24T09:21:14.000Z</updated>
<content type="html"><![CDATA[<p>Telerik在使用安装包进行安装的情况下,会提供VS的插件。使用该插件能够很方便的建立一个Telerik的空项目。那么在只使用NuGet包的情况下应该如何建立一个Telerik的空项目呢?</p>
<h1 id="step-1">Step 1</h1>
<p>新建一个WPF的项目。这一步没啥可说的,不会建的同学可以点右上角的X了。</p>
<h1 id="step-2">Step 2</h1>
<p>添加需要的NuGet包。这一步需要添加的至少有:</p>
<ol>
<li>一个主题包。比如Fluent</li>
<li>Navigation包。这个包包含RadWindow。</li>
</ol>
<h1 id="step-3">Step 3</h1>
<p>添加项目资源。前两个是默认都得添加的,是一些基础的东西。后面的根据你添加的包来添加。比如我们使用了Navigation包,所以添加了Navigation的资源。</p>
<pre><code class="language-xml"> <Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Telerik.Windows.Themes.Fluent;component/Themes/System.Windows.xaml"/>
<ResourceDictionary Source="/Telerik.Windows.Themes.Fluent;component/Themes/Telerik.Windows.Controls.xaml"/>
<ResourceDictionary Source="/Telerik.Windows.Themes.Fluent;component/Themes/Telerik.Windows.Controls.Navigation.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</code></pre>
<h1 id="step-4">Step 4</h1>
<p>更改默认的MainWindow。把类型从Window改成RadWindow。两步走,xaml和cs。<br>
xaml需要注意的是必须添加Style,否则程序运行起来什么都没有。需要使用RadWindowInteropHelper来将程序显示到任务栏。<br>
cs就很简单了,把继承自window的那点点东西删掉就好了。</p>
<blockquote>
<p>为了让反应慢的同学明白,要删掉的东西就是 <code>: Window</code>。</p>
</blockquote>
<pre><code class="language-xml"><telerik:RadWindow x:Class="TelerikTest.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:TelerikTest"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:radWindowHelper="clr-namespace:Telerik.Windows.Controls.Navigation;assembly=Telerik.Windows.Controls.Navigation"
mc:Ignorable="d" radWindowHelper:RadWindowInteropHelper.ShowInTaskbar="True"
Header="MainWindow" Height="450" Width="800" Style="{StaticResource RadWindowStyle}">
<Grid>
</Grid>
</telerik:RadWindow>
</code></pre>
<h1 id="step-5">Step 5</h1>
<p>改造启动类App。依旧是两步走。<br>
xaml中删掉StartUri。<br>
cs中添加如下代码:</p>
<pre><code class="language-c#"> protected override void OnStartup(StartupEventArgs e)
{
var window = new MainWindow();
window.Show();
}
</code></pre>
<h1 id="搞定">搞定</h1>
<p>F5跑起来就行了。需要注意的是每个主题都有自己的特色。需要仔细的去阅读一下Themes Suite。否则用起来四不像就很尴尬了。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[WPF 自定义Panel以及实现IScrollInfo以支持ScrollViewer]]></title>
<id>https://www.avaloninparadise.cn/post/wpf-zi-ding-yi-panel-yi-ji-shi-xian-iscrollinfo-yi-zhi-chi-scrollviewer/</id>
<link href="https://www.avaloninparadise.cn/post/wpf-zi-ding-yi-panel-yi-ji-shi-xian-iscrollinfo-yi-zhi-chi-scrollviewer/">
</link>
<updated>2020-01-08T05:37:15.000Z</updated>
<content type="html"><![CDATA[<p>本文主要介绍了一下内容。</p>
<ol>
<li>什么是Panel。为什么要使用自定义Panel。</li>
<li>如何实现自动一Panel。</li>
<li>如何实现IScrollInfo。</li>
</ol>
<h2 id="什么是panel">什么是Panel</h2>
<p>下图为官方文档中给出的Panel继承关系。<br>
<img src="https://www.avaloninparadise.cn/post-images/1578462264146.png" alt="Panel的继承关系" loading="lazy"><br>
我们可以看到,Panel是日常使用的布局类的基类。在WPF中,使用Panel类来对界面进行布局。</p>
<h2 id="为什么要使用自定义panel">为什么要使用自定义Panel</h2>
<p>在上面的Panel继承关系图中我们可以看到,默认提供的布局类是有限的。如果他们满足不了我们的布局需求,我们就需要自定义Panel。比如,实现文件浏览器中平铺的布局。</p>
<h2 id="如何实现自定义panel">如何实现自定义Panel</h2>
<p>首先需要知道一个Panel的完整布局过程。<br>
<img src="https://www.avaloninparadise.cn/post-images/1578463198629.png" alt="Panel布局时序" loading="lazy"><br>
如图,在父节点决定重新布局时,会进行一系列的操作。对于Panel而言,核心的是两个步骤。这两个步骤的实现是通过两个核心的方法实现的,<a href="https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.measureoverride?view=netframework-4.8#System_Windows_FrameworkElement_MeasureOverride_System_Windows_Size_"><code>MeasureOverride</code></a>以及<a href="https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.arrangeoverride?view=netframework-4.8#System_Windows_FrameworkElement_ArrangeOverride_System_Windows_Size_"><code>ArrangeOverride</code></a>。</p>
<h3 id="测量measure">测量Measure</h3>
<p>这一步是对自身大小进行一个预估。</p>
<pre><code class="language-c#">Size MeasureOverride(Size availableSize)
</code></pre>
<p><code>MeasureOverride</code>的唯一参数是<code>Size</code>类型的,这个参数代表着上层预计给你的布局空间。也就是说,预估情况下,你能够使用的大小。比如<code>ContentControl</code>这种承载类的控件,会给你一个无限大的范围,由你自身计算你会使用的大小,返回给上层。</p>
<blockquote>
<p>这里虽然给你一个大小无限的参数,但是你却不能返回一个大小无限的Size。</p>
</blockquote>
<p>那么如何进行自身使用大小的计算呢?简单来说,就是问一下Panel自身承载的所有控件需要的大小,然后对这些控件按布局排列之后,需要的大小就是自身的大小。</p>
<blockquote>
<p>举个例子,StackPanel纵向排列的情况下,有4个预计大小为400 * 300的Children。那么这个Panel本身需要的大小就是400 * 1200。这个1200 = 300 * 4,因为是纵向排列。如果换成横向排列,那么需要的大小是1600 * 300。</p>
</blockquote>
<p>通常情况下,你的MeasureOverride会写成这个样子。</p>
<pre><code class="language-c#"> protected override Size MeasureOverride(Size availableSize)
{
var result = new Size();
foreach (UIElement child in InternalChildren)
{
//询问包含的控件需要的大小。预期的大小会保存在DesiredSize中。
child.Measure(availableSize);
//根据布局方式,计算实际使用的大小。这一部分根据你的需求进行编写
result.Width = Math.Max(result.Width, child.DesiredSize.Width);
result.Height += child.DesiredSize.Height;
}
return result;
}
</code></pre>
<h3 id="布局arrange">布局Arrange</h3>
<p>这一步是对控件进行布局,会实际应用到界面上。上一步只是进行一个预估,如果你的上一步乱写,而你在这一步进行了正确的布局,你也很大概率会得到一个正确的界面布局。</p>
<pre><code class="language-c#">Size ArrageOverride(Size finalSize)
</code></pre>
<p>同Measure,这一步的finalSize是你实际布局时可以用的大小。<strong>通常情况</strong>下不会是无限大。而且这个大小会跟上一步有管关,有时就直接是上一步的返回值。同样的,这个函数的返回值就是Panel在界面上的最终大小。<br>
在这一步,你需要做的是根据可用大小,以及每个控件希望的大小,来对控件进行布局。<br>
通常情况下,你的函数会写得像这样。</p>
<pre><code class="language-c#"> protected override Size ArrangeOverride(Size availableSize)
{
var result = new Size();
var offset = new Point();
foreach (UIElement child in InternalChildren)
{
//根据布局方式计算出当前控件的布局位置
var rect = new Rect(offset, child.DesiredSize);
offset.Y += rect.Height;
// 计算完成后应该更新最终要使用的大小
result.Height += rect.Height;
//布局控件
child.Arrange(rect);
}
return result;
}
</code></pre>
<h2 id="实现iscrollinfo">实现IScrollInfo</h2>
<p>按照之前的步骤进行,我们就能实现一个可用的自定义Panel。不过一旦我们将Panel置于<code>ScrollViewer</code>中,我们会发现Panel会产生很诡异的布局,甚至可能会直接运行时报错。这是因为<code>ScrollViewer</code>的特殊性。该控件认为其内部能够承载无限大的控件,但是可视区域有限。通过滚动条来调整可视区域以观察内部承载的内容。所以,<code>MeasureOverride</code>以及<code>ArrageOverride</code>都会收到无限大的参数。这时候我们的布局计算就很难进行,因为我们并不知道可视区域在哪。此时,我们需要实现<code>IScrollInfo</code>来支持<code>ScrollViewer</code>。<br>
<img src="https://www.avaloninparadise.cn/post-images/1579154053887.png" alt="IScrollInfo" loading="lazy"><br>
如上图,<code>IScrollInfo</code>还是算一个比较大的接口。所以让我们来对其进行一下分类整理。<br>
从属性上来看,有好几个成对出现的。唯一一个单独的就是<code>ScrollOwner</code>,这个很好理解。之前也解释过,为什么需要用到<code>IScrollInfo</code>,就是为了支持<code>ScrollViewer</code>。所以这里的<code>ScrollOwner</code>就是Host。</p>
<table>
<thead>
<tr>
<th>类别</th>
<th>属性1</th>
<th>属性2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Extent</td>
<td>ExtentWidth</td>
<td>ExtentHeight</td>
</tr>
<tr>
<td>Viewport</td>
<td>ViewportWidth</td>
<td>ViewportHeight</td>
</tr>
<tr>
<td>Offset</td>
<td>HorizontalOffset</td>
<td>VerticalOffset</td>
</tr>
<tr>
<td>CanScroll</td>
<td>CanHorizontallyScroll</td>
<td>CanVerticallyScroll</td>
</tr>
</tbody>
</table>
<p>首先需要明确一件事,这些属性到底是给谁用的。答案是,这些属性都是给<code>ScrollViewer</code>用的。没有这些属性,我们也能完成控件的布局。这些属性都是为了正确地显示滚动条而存在的。故,每次更改这些值之后,需要调用<code>ScrollOwner.InvalidateScrollInfo()</code>来通知<code>ScrollViewer</code>更新滚动条。<br>
首先最好理解的就是CanScroll的这一对了。很直白。就是能不能在某个方向上进行滚动。<br>
剩下的三对属性其实是组合使用的。<strong>Extent表示实际内容的大小,Viewport表示可视区域的大小,Offset表示可视区域的偏移量。</strong><br>
<img src="https://www.avaloninparadise.cn/post-images/1579156736387.png" alt="示意图" loading="lazy"><br>
如图所示,假设我们有四个控件,都是400*350的大小,可视区域也是400*350,已经将可视区域滚动到显示第二个控件。那么我们的Extent的大小就是400*1400,Viewport是400*350,Offset是0*350。是不是能够理解这三对属性了呢。<br>
我们的<code>ArrangeOverride</code>也需要进行一些调整。<strong>对控件进行布局的时候,需要减去Offset</strong>。对于上面的一张图,控件分别的布局是:</p>
<ol>
<li><code>control1.Arrage(new Rect(0 - HorizontalOffset, 0 - VerticalOffset, 400, 350));</code></li>
<li><code>control2.Arrage(new Rect(0 - HorizontalOffset, 350 - VerticalOffset, 400, 350));</code></li>
<li><code>control3.Arrage(new Rect(0 - HorizontalOffset, 700 - VerticalOffset, 400, 350));</code></li>
<li><code>control4.Arrage(new Rect(0 - HorizontalOffset, 1050 - VerticalOffset, 400, 350));</code><br>
然后是,在完成布局后,更新Extent以及Viewport的值。</li>
</ol>
<blockquote>
<p>这里其实还需要判断Offset是否超界,超界后应对其校正。</p>
</blockquote>
<p>那么剩下的方法其实就很好处理了。无非就是更改Offset,然后重新布局。以<code>LineUp</code>为例:</p>
<pre><code class="language-c#">public void LineUp()
{
_offset += new Vector(0, -20);
//校正Offset以避免超界
CorrectOffset();
InvalidateArrange();
}
</code></pre>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[WPF使用IDataErrorInfo接口进行数据校验]]></title>
<id>https://www.avaloninparadise.cn/post/wpf-shi-yong-idataerrorinfo-jie-kou-jin-xing-shu-ju-xiao-yan/</id>
<link href="https://www.avaloninparadise.cn/post/wpf-shi-yong-idataerrorinfo-jie-kou-jin-xing-shu-ju-xiao-yan/">
</link>
<updated>2019-10-22T07:22:52.000Z</updated>
<content type="html"><![CDATA[<pre><code class="language-cs"> class ValidationBindableBase : BindableBase, IDataErrorInfo
{
public string this[string columnName]
{
get
{
if (_errorMap.ContainsKey(columnName))
{
var error = _errorMap[columnName];
_errorMap.Remove(columnName);
return error;
}
return null;
}
}
public string Error => string.Join("\n",_errorMap.Values);
private readonly Dictionary<string, string> _errorMap = new Dictionary<string, string>();
protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
var result = base.SetProperty(ref storage, value, propertyName);
var type = this.GetType();
foreach (var methodInfo in type.GetMethods())
{
if (methodInfo.Name == propertyName + "Validation"&& methodInfo.ReturnType == typeof(string) && methodInfo.GetParameters().Length == 0)
{
_errorMap.Add(propertyName,(string)methodInfo.Invoke(this, null));
}
}
return result;
}
}
</code></pre>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[WPF RoutedEvent and HitTest]]></title>
<id>https://www.avaloninparadise.cn/post/wpf-routedevent-and-hittest/</id>
<link href="https://www.avaloninparadise.cn/post/wpf-routedevent-and-hittest/">
</link>
<updated>2019-10-22T07:22:31.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>学习的时候切忌心浮气躁,慢慢的过每一个知识点,不要漏掉任何细节。不然当遇到细节问题的时候,会恼,会闹,会悔不该当初——花一下午调bug最后只改了一个参数有感。</p>
</blockquote>
<p>相信很多用过WPF的人都知道WPF中的路由事件。一般看书的话,这个知识点也会在前几章讲到。总的来说,也就是</p>
<ul>
<li>WPF的控件都存在于一颗Visual tree当中。</li>
<li>事件在控件中的传递,其实也就是事件在Visual tree中的传递</li>
<li>隧道事件从上往下,由树根开始向叶子传播</li>
<li>冒泡事件从下往上,由子节点开始向根传播</li>
</ul>
<p>假设我们有一个Visual tree长这样:</p>
<pre><code>MainWindow
|_Border
|_Grid
|_TextBlock
</code></pre>
<p>那么如果用户点击了<code>TextBlock</code>。那么会产生什么事件,然后会怎么传递呢?<br>
答案是</p>
<ol>
<li>会产生<code>PreviewMouseDown</code>和<code>MouseDown</code>事件</li>
<li><code>PreviewMouseDown</code>是隧道事件,事件的顺序是<code>MainWindow</code>-><code>Border</code>-><code>Grid</code>-><code>TextBlock</code></li>
<li><code>MouseDown</code>是冒泡事件,事件的顺序与之前相反,是<code>TextBlock</code>-><code>Grid</code>-><code>Border</code>-><code>MainWindow</code></li>
</ol>
<blockquote>
<p>Tips:<br>
如何查看WPF中的事件?有一个开源工具<a href="https://github.com/cplotts/snoopwpf">snoop</a>可以帮助你。下图是一个实际示例,UI结构以及操作和上述一致。<br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-850a7e65284915a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="example" loading="lazy"></p>
</blockquote>
<p>好,我们再看一个例子。</p>
<pre><code>MainWindow
|_Border
|_Grid
|_TextBlock(Margin="32")
</code></pre>
<p>和上个例子不同的地方在于,我们把<code>TextBlock</code>的边距扩大了。这就意味着,我们可以点击在<code>TextBlock</code>的边距上,那么会发生什么呢?先自己想想哦。<br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-ab2f03b76a086920.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="正确答案" loading="lazy"></p>
<blockquote>
<p>注意这里的border是window中自带的,不是我们自己声明的。所以正确答案是,只传播到了<code>MainWindow</code>。为了区别,我给声明的<code>Border</code>随便起了个名字。</p>
</blockquote>
<p>要是答对了的同学,那不是一般的棒!<br>
我们这里有两个问题:</p>
<ol>
<li><code>PreviewMouseDown</code>只传递到了<code>MainWindow</code>。作为一个隧道事件,没有继续往下传递。</li>
<li>触发事件的是<code>MainWindow</code>,不是<code>Border</code>也不是<code>Grid</code>。</li>
</ol>
<p>先解答第一个问题。路由事件的准确触发顺序应该是</p>
<ol>
<li>隧道事件从根开始,传播到产生事件的控件为止。如果中间有控件处理(<code>e.Handled = true</code>)掉,就停止传播。</li>
<li>冒泡事件从产生事件的控件开始,传播到根节点为止。如果中间有控件处理(<code>e.Handled = true</code>)掉,就停止传播。</li>
<li>系统提供的Preview事件先触发。如果被处理(<code>e.Handled = true</code>)掉,不会在产生对应的冒泡事件。</li>
</ol>
<p>第二个问题就很恼人了。总的来说就是</p>
<ul>
<li>如果没有控件没有被渲染,那么该控件不能被HitTest,也不能被路由事件触发。(参见<a href="https://stackoverflow.com/questions/6395525/previewmousedown-not-tunnelling-as-expected">这里</a>)<br>
也就是说,<code>Border</code>和<code>Grid</code>没能触发,是因为他们没有<code>Background</code>,没有被渲染。如果加上,即使你加的是<code>TransParent</code>,也会有效。<br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-5ee4a346b277004f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="添加了TransParent为Border的背景" loading="lazy"><br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-0a30383362314ca1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="添加了TransParent为Grid的背景" loading="lazy"></li>
</ul>
<p>也就是说,如果你希望下面的控件能够触发事件。那么让上面的控件不能被HitTest就可以了。我今天遇到的坑是,上面一层自己画的一个框,用的函数是</p>
<pre><code class="language-cs">dc.DrawGeometry(Brushes.Transparent, new Pen(brush, GraphicsLineWidth), PathGeometry);
</code></pre>
<p>改成</p>
<pre><code class="language-cs">dc.DrawGeometry(null, new Pen(brush, GraphicsLineWidth), PathGeometry);
</code></pre>
<p>就好了。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[使用Infragistics WPF controls遇到BaseOn问题]]></title>
<id>https://www.avaloninparadise.cn/post/shi-yong-infragistics-wpf-controls-yu-dao-baseon-wen-ti/</id>
<link href="https://www.avaloninparadise.cn/post/shi-yong-infragistics-wpf-controls-yu-dao-baseon-wen-ti/">
</link>
<updated>2019-10-22T07:21:45.000Z</updated>
<content type="html"><![CDATA[<h1 id="问题描述">问题描述</h1>
<p>WPF程序中使用Infragistics的控件,需要对控件进行全局的属性设置。一般来说,会想到使用BaseOn默认的Style来进行设置。但是Infragistics使用BaseOn会导致Style直接丢失。特别是在使用了其提供的主题的情况下(废话,不适用主题用它控件干嘛)。</p>
<h1 id="问题分析">问题分析</h1>
<h3 id="常规baseon写法">常规BaseOn写法</h3>
<p>下面的代码对Button进行了背景色设置。由于没有指定Key,如果放在App.xaml中,会对全局程序生效。</p>
<pre><code class="language-xaml"><Style TargetType="Button" BaseOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="Red"/>
</Style>
</code></pre>
<h3 id="同样的写法用于infragistics控件">同样的写法,用于Infragistics控件</h3>
<p>下面代码对ColorPicker是否显示RecentColor进行设置。</p>
<pre><code class="language-xaml"><Window x:Class="WpfApp1.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:ig="http://schemas.infragistics.com/xaml"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style TargetType="{x:Type ig:XamColorPicker}" BasedOn="{StaticResource {x:Type ig:XamColorPicker}}">
<Setter Property="ShowRecentColorsPalette" Value="false"/>
</Style>
</Window.Resources>
<Grid>
<ig:XamColorPicker MinWidth="80" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Window>
</code></pre>
<p>当然,需要先在App.xaml.cs中设置主题。</p>
<pre><code class="language-cs"> public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
ThemeManager.ApplicationTheme = new RoyalDarkTheme();
base.OnStartup(e);
}
}
</code></pre>
<p><img src="https://upload-images.jianshu.io/upload_images/1802880-d77774385a41fab4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="运行效果" loading="lazy"><br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-a10a7c8f92540f8f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="预期结果" loading="lazy"></p>
<h1 id="解决方案">解决方案</h1>
<p><code>ThemeManager</code>中提供一个<code>RegisterControl</code>方法。我们需要在设置主题前对控件进行注册。修改App.xaml.cs。</p>
<pre><code class="language-cs"> public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
//Added this line
ThemeManager.RegisterControl(typeof(XamColorPicker));
ThemeManager.ApplicationTheme = new RoyalDarkTheme();
base.OnStartup(e);
}
}
</code></pre>
<h1 id="问题分析-2">问题分析</h1>
<p>To be continue.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[DependencyPropertyChangedCallback造成循环调用的问题]]></title>
<id>https://www.avaloninparadise.cn/post/dependencypropertychangedcallback-zao-cheng-xun-huan-diao-yong-de-wen-ti/</id>
<link href="https://www.avaloninparadise.cn/post/dependencypropertychangedcallback-zao-cheng-xun-huan-diao-yong-de-wen-ti/">
</link>
<updated>2019-10-22T07:17:02.000Z</updated>
<content type="html"><![CDATA[<p>我学习WPF使用的是《WPF编程宝典》(第四版)。之前读到自定义控件,也就是18.2颜色拾取器的时候,有一句话我深深的记在脑海里。</p>
<blockquote>
<p>因为WPF不允许重新进行属性变化回调函数。<br>
多么智能,多么贴心。为了避免有的人没有看过这本书,我把书里的例子贴在这里。</p>
</blockquote>
<pre><code class="language-C#">public class ColorPicker:System.Windows.Controls.UserControl
{
public static DependencyProperty RedProperty;
public static DependencyProperty GreenProperty;
public static DependencyProperty BlueProperty;
public static DependencyProperty ColorProperty;
static ColorPicker()
{
ColorProperty = DependencyProperty.Register("Color", typeof(Color),
typeof(ColorPicker), new FramewrokPropertyMetadata(Colors.Black, new PropertyChangedCallback(OnColorChanged));
RedProperty = DependencyProperty.Register("Red", typeof(byte),
typeof(ColorPicker), new FramewrokPropertyMetadata(new PropertyChangedCallback(OnColorRGBChanged));
GreenProperty = DependencyProperty.Register("Green", typeof(byte),
typeof(ColorPicker), new FramewrokPropertyMetadata(new PropertyChangedCallback(OnColorRGBChanged));
BlueProperty = DependencyProperty.Register("Blue", typeof(byte),
typeof(ColorPicker), new FramewrokPropertyMetadata(new PropertyChangedCallback(OnColorRGBChanged));
}
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public byte Red
{
get { return (byte)GetValue(RedProperty); }
set { SetValue(RedProperty, value); }
}
public byte Green
{
get { return (byte)GetValue(GreenProperty); }
set { SetValue(GreebProperty, value); }
}
public byte Blue
{
get { return (byte)GetValue(BlueProperty); }
set { SetValue(BlueProperty, value); }
}
private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Color newColor = (Color)e.NewValue;
Color oldColor = (Color)e.OldValue;
ColorPicker colorPicker = (ColorPicker)sender;
colorPicker.Red = newColor.R;
colorPicker.Green = newColor.G;
colorPikcer.Blue = newColor.B;
}
private static void OnColorRGBChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ColorPicker colorPicker = (ColorPicker)sender;
Color color = colorPicker.Color;
if (e.Property == RedProperty)
color.R = (byte)e.NewValue;
else if (e.Property == GreenProperty)
color.G = (byte)e.NewValue;
else if (e.Property == BlueProperty)
color.B = (byte)e.NewValue;
colorPicker.Color = color;
}
}
</code></pre>
<p>很明显,这段代码是希望在改变RGB的值时,能够更新Color。同时在更新Color时,能够更新RGB。并且,这样写并不会造成循环调用。<br>
我一直坚信这是WPF内部进行优化,能够保证在任何情况下,callback中对依赖属性进行设置,不会发生循环调用的情况。<br>
知道今天,我把对应的Color改成了TimeSpan,循环调用发生了。那一刻,我是怀疑人生的。</p>
<p>先给出结论:<strong>DependencyPropertyChangedCallback中并不会进行什么神奇的操作来规避循环调用。能够规避部分循环调用的原因时,SetValue会对值进行判定,如果值没有发生改变,则不会调用DependencyPropertyChangedCallback。</strong></p>
<p>为什么改成TimeSpan类型会发生循环调用呢?因为如果把Second赋值一个超过60的数,会进位。感兴趣的朋友可以想想为什么。欢迎留言讨论。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[使用微软提供的Settings以及自定义SettingsProvider]]></title>
<id>https://www.avaloninparadise.cn/post/shi-yong-wei-ruan-ti-gong-de-settings-yi-ji-zi-ding-yi-settingsprovider/</id>
<link href="https://www.avaloninparadise.cn/post/shi-yong-wei-ruan-ti-gong-de-settings-yi-ji-zi-ding-yi-settingsprovider/">
</link>
<updated>2019-10-22T07:11:42.000Z</updated>
<content type="html"><![CDATA[<h1 id="前言">前言</h1>
<p>在程序中,难免会用到配置文件。如何处理配置文件问题,有许许多多的解决方案。简单的就是直接在app.config里面加一条,然后读取。复杂一些的,可能需要自己去定义格式,存储位置。微软为我们提供了一个方案,就是在项目中的<code>Settings</code>。<br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-bbfc08a80494db97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Settings" loading="lazy"></p>
<h1 id="使用settings">使用Settings</h1>
<p><code>Settings</code>为我们提供了设置界面,方便操作。使用起来非常简单。<br>
如果你新建一个WPF项目,模板中其实默认就带有一个<code>Settings</code>文件。就像上一节中的插图一样,展开Proerpties即可见。你也可以像添加一般的类一样添加新的<code>Settings</code>文件,只需要在添加文件的窗口中找到<code>Settings</code>类型,添加即可。<br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-2589526e7f2cfbcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="添加新的Settings文件" loading="lazy"><br>
双击<code>Settings</code>文件,进入可视化编辑界面。<br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-59ae020a7d766b6f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="可视化编辑界面" loading="lazy"><br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-68cb99eeca19151b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="类型选择" loading="lazy"><br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-1081d79f56e01259.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="作用范围选择" loading="lazy"></p>
<p>第一列是设置项的名称。该值作为将来在程序中获取或设置此项设置时的键。<br>
第二列是设置项的类型。默认有很多的基本类型,注意最有一项,是可以选择自定义类型的。但是也要支持序列化的类型才行,如果是很复杂的类型,需要对其添加序列化和反序列化的方法。<br>
第三项是设置项的作用范围。只有两种选择,Application和User。如果你选择Application,那么该设置项的值不能被修改。如果你选择User,该设置项可以被修改,但是仅针对于当前计算机用户生效。<br>
第四项是设置项的默认值。这个没什么好说的,记得默认值要和类型匹配,不然会编译不通过。</p>
<p>添加完成后,记得点一下保存。VS会帮你生成对应的代码。对应的代码你可以在<code>Settings</code>文件对应的cs文件中找到。<br>
<img src="https://upload-images.jianshu.io/upload_images/1802880-6627dc5b7899fc55.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="生成的代码位置" loading="lazy"><br>
单项设置对应的代码:</p>
<pre><code class="language-c#"> [global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("123")]
public string Setting {
get {
return ((string)(this["Setting"]));
}
set {
this["Setting"] = value;
}
}
</code></pre>
<p>截图中的设置生成的对应代码:</p>
<pre><code class="language-c#"> [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("123")]
public string Setting {
get {
return ((string)(this["Setting"]));
}
set {
this["Setting"] = value;
}
}
}