forked from DebugST/STNodeEditor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtutorials_cn.html
1225 lines (1223 loc) · 142 KB
/
tutorials_cn.html
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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<title>STNodeEditor - 教程</title>
<link rel="stylesheet" type="text/css" href="./css/stdoc.css"/>
<script type="text/javascript" src="./js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="./js/stdoc.js"></script>
</head>
<body>
<div id="div_body">
<div id="div_left">
<div id="div_left_list">
<ul class='ul_group_root'>
<li>
<a class='a_node_root anchor_btn' anchor='a_a'>前言</a>
<ul>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_b'>简介</a></li>
</ul>
</li>
<li>
<a class='a_node_root anchor_btn' anchor='a_c'>[基础]STNode</a>
<ul>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_d'>创建节点</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_e'>STNodeOption</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_f'>STNodeOption.Empty</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_g'>STNode.AutoSize</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_h'>案例 - ClockNode</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_i'>STNode.SetOptionXXX</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_j'>关于数据传递</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_k'>STNodeHub</a></li>
</ul>
</li>
<li>
<a class='a_node_root anchor_btn' anchor='a_l'>[基础]STNodeControl</a>
<ul>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_m'>添加一个控件</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_n'>自定义一个 Button</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_o'>案例 - 图像信息获取</a></li>
</ul>
</li>
<li>
<a class='a_node_root anchor_btn' anchor='a_p'>[基础]STNodeEditor</a>
<ul>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_q'>保存画布</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_r'>加载画布</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_s'>常用事件</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_t'>常用方法</a></li>
</ul>
</li>
<li>
<a class='a_node_root anchor_btn' anchor='a_u'>STNodePropertyGrid</a>
<ul>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_v'>如何使用</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_w'>STNodePropertyAttribute</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_x'>STNodeAttribute</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_y'>查看帮助</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='a_z'>案例 - 两数相加</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_a'>ReadOnlyModel</a></li>
</ul>
</li>
<li>
<a class='a_node_root anchor_btn' anchor='b_b'>STNodePropertyDescriptor</a>
<ul>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_c'>失败案例 - ColorTestNode</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_d'>关于属性描述器</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_e'>GetValueFromString()</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_f'>GetStringFromValue()</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_g'>成功案例 - ColorTestNode</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_h'>高级案例 - ColorTestNode</a></li>
</ul>
</li>
<li>
<a class='a_node_root anchor_btn' anchor='b_i'>STNodeTreeView</a>
<ul>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_j'>使用方式</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_k'>加载程序集</a></li>
</ul>
</li>
<li>
<a class='a_node_root anchor_btn' anchor='b_l'>持久化保存</a>
<ul>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_m'>保存</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_n'>OnSaveNode(dic)</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_o'>GetBytesFromValue()</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_p'>OnLoadNode(dic)</a></li>
<li class='li_node_sub'><a class='anchor_btn' anchor='b_q'>OnEditorLoadCompleted</a></li>
</ul>
</li>
<li>
<a class='a_node_root anchor_btn' anchor='b_r'>THE END</a>
<ul>
</ul>
</li>
</ul><span class='span_time'>2021-04-29</span>
</div>
</div>
<div id="div_right">
<h1 class='h_title anchor_point' anchor='a_a'>前言</h1>
<div><h2 class='h_option anchor_point' anchor='a_b'>简介</h2></div>
<p>那是一个冬季 在研究无线电安全的作者接触到了<a target='_bank' href='https://www.gnuradio.org/'>GNURadio</a> 那是作者第一次接触到节点编辑器</p>
<p>-> What? Excuse me... What"s this?.. 这是什么鬼东西?...</p>
<p>那是一个春季 不知道为什么 过完年整个世界都变了 大家被迫窝在家里 无聊至极的作者学起了<a target='_bank' href='https://www.blender.org/'>Blender</a>那是作者第二次接触到节点编辑器</p>
<p>-> Wo...原来这东西可以这么玩...真方便</p>
<p>于是一些想法在作者脑中逐渐诞生 让作者有了想做一个这样的东西的想法</p>
<p>那是一个夏季 不知道为什么 作者又玩起了<a target='_bank' href='http://www.blackmagicdesign.com/cn/products/davinciresolve/'>Davinci</a>那是作者第三次接触到节点编辑器 这一次的接触让作者对节点编辑器的好感倍增 作者瞬间觉得 只要是可以模块化流程化的功能 万物皆可节点化</p>
<p>所以<span class='span_mark'>STNodeEditor</span>就诞生了</p>
<img width=990 src='./images/page_top.png'/>
<hr/>
<h1 class='h_title anchor_point' anchor='a_c'>[基础]STNode</h1>
<p><span class='span_mark'>STNode</span>是整个框架的核心 如果把<span class='span_mark'>STNodeEditor</span>视为<span class='span_mark'>Desktop</span> 那么一个<span class='span_mark'>STNode</span>就可以视为桌面上的一个应用程序 开发一个健壮的<span class='span_mark'>STNode</span>是非常有必要的事情</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_d'>创建节点</h2></div>
<hr/>
<p>节点的基类<span class='span_mark'>STNode</span>被<span class='span_mark'>abstract</span>修饰 无法直接创建节点 所以节点必须继承至<span class='span_mark'>STNode</span></p>
<p><span class='span_mark'>STNode</span>包含了大量的<span class='span_mark'>virtual</span>函数可供开发者重写 对于详细的函数列表请参考<a target='_bank' href='./doc.html'>API文档</a></p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>using</span> ST.Library.UI.NodeEditor;
<span class='span_code_line'></span>
<span class='span_code_line'></span><span class='code_key'>namespace</span> WinNodeEditorDemo
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>MyNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span> {
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_class'>MyNode</span>() { <span class='code_note'>//与OnCreate()等效</span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"TestNode"</span>;
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_note'>//protected override void OnCreate() {</span>
<span class='span_code_line'></span> <span class='code_note'>// base.OnCreate();</span>
<span class='span_code_line'></span> <span class='code_note'>//}</span>
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}
<span class='span_code_line'></span><span class='code_note'>//添加到STNodeEditor中</span>
<span class='span_code_line'></span>stNodeEditor1.Nodes.Add(<span class='code_key'>new</span> <span class='code_class'>MyNode</span>());</pre>
</div>
<img width=208 src='./images/tu_testnode.png'/>
<p>会发现只看到一个标题什么都没有 因为并没有为它添加输入输出选项 所以重新修改代码</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>MyNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"TestNode"</span>;
<span class='span_code_line'></span> <span class='code_note'>//此添加方式会得到添加成功后的 STNodeOption 索引位置</span>
<span class='span_code_line'></span> <span class='code_key'>int</span> nIndex = <span class='code_key'>this</span>.InputOptions.Add(<span class='code_key'>new</span> <span class='code_class'>STNodeOption</span>(<span class='code_string'>"IN_1"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>));
<span class='span_code_line'></span> <span class='code_note'>//此添加方式能直接得到一个构建的 STNodeOption</span>
<span class='span_code_line'></span> <span class='code_class'>STNodeOption</span> op = <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"IN_2"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>int</span>), <span class='code_key'>true</span>);
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"OUT"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=208 src='./images/tu_testnode_adop.png'/>
<p>这样节点上就会出现所添加的选项 但是这样还不够 可以看到一共有两个数据类型<span class='span_mark'>string</span>和<span class='span_mark'>int</span>应该给数据类型赋予颜色信息 来区分不同的数据类型</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>MyNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"TestNode"</span>;
<span class='span_code_line'></span> <span class='code_key'>int</span> nIndex = <span class='code_key'>this</span>.InputOptions.Add(<span class='code_key'>new</span> <span class='code_class'>STNodeOption</span>(<span class='code_string'>"IN_1"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>));
<span class='span_code_line'></span> <span class='code_class'>STNodeOption</span> op = <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"IN_2"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>int</span>), <span class='code_key'>true</span>);
<span class='span_code_line'></span> <span class='code_note'>//this.SetOptionDotColor(op, Color.Red); 优先级最高 如果被设置 会忽略容器中的颜色信息</span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"OUT"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_note'>//当所属容器发生变化时候发生 应当向容器提交自己数据类型期望显示的颜色</span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnOwnerChanged() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnOwnerChanged();
<span class='span_code_line'></span> <span class='code_key'>if</span> (<span class='code_key'>this</span>.Owner == <span class='code_key'>null</span>) <span class='code_key'>return</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.Owner.SetTypeColor(<span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_class'>Color</span>.Yellow);
<span class='span_code_line'></span> <span class='code_note'>//此添加方式 会替换容器中已有的类型颜色信息</span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.Owner.SetTypeColor(<span class='code_key'>typeof</span>(<span class='code_key'>int</span>), <span class='code_class'>Color</span>.DodgerBlue, <span class='code_key'>true</span>);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=208 src='./images/tu_testnode_adopclr.png'/>
<p>这样一个节点就创建完成了 但是这个节点目前不具备任何的功能 接下来的案例中将逐步添加功能</p>
<p class='p_hightlight'>无论如何开发者都应该尽量为扩展的<span class='span_mark'>STNode</span>保留一个空参数构造器 不然在很多功能使用会存在不必要的麻烦</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_e'>STNodeOption</h2></div>
<hr/>
<p>由上面的案例可知<span class='span_mark'>STNodeOption</span>是<span class='span_mark'>STNode</span>的连接选项 连接选项可以是<span class='span_mark'>多连接</span>和<span class='span_mark'>单连接</span>模式</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>MyNode</span> : <span class='code_class'>STNode</span> {
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"MyNode"</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.TitleColor = <span class='code_class'>Color</span>.FromArgb(200, <span class='code_class'>Color</span>.Goldenrod);
<span class='span_code_line'></span> <span class='code_note'>//单连接选项</span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"Single"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>true</span>);
<span class='span_code_line'></span> <span class='code_note'>//多连接选项</span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"Multi"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=208 src='./images/tu_mynode_single.png'/>
<p class='p_hightlight'>在<span class='span_mark'>多连接</span>模式下 一个选项可以被<span class='span_mark'>同数据类型</span>的多个选项连接 以方形绘制</p>
<p class='p_hightlight'>在<span class='span_mark'>单连接</span>模式下 一个选项仅可被<span class='span_mark'>同数据类型</span>的一个选项连接 以圆形绘制</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_f'>STNodeOption.Empty</h2></div>
<hr/>
<p><span class='span_mark'>STNodeOption.Empty</span>是一个静态属性 添加到<span class='span_mark'>STNode</span>中仅用于在自动布局时候占位使用 不参与绘制与事件触发</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>MyNode</span> : <span class='code_class'>STNode</span> {
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"MyNode"</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.TitleColor = <span class='code_class'>Color</span>.FromArgb(200, <span class='code_class'>Color</span>.Goldenrod);
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.InputOptions.Add(<span class='code_class'>STNodeOption</span>.Empty);
<span class='span_code_line'></span> <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"IN_1"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"IN_2"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"OUT_1"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_class'>STNodeOption</span>.Empty);
<span class='span_code_line'></span> <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_class'>STNodeOption</span>.Empty);
<span class='span_code_line'></span> <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"OUT_2"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=208 src='./images/tu_mynode_empty.png'/>
<p>每一个<span class='span_mark'>STNodeOption</span>的高度由<span class='span_mark'>STNode.ItemHeight(protected)</span>决定</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_g'>STNode.AutoSize</h2></div>
<hr/>
<p><span class='span_mark'>AutoSize</span>默认为<span class='span_mark'>true</span> 在此状态下节点的<span class='span_mark'>Width</span><span class='span_mark'>Height</span>等属性无法被设置</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>MyNode</span> : <span class='code_class'>STNode</span> {
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"MyNode"</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.TitleColor = <span class='code_class'>Color</span>.FromArgb(200, <span class='code_class'>Color</span>.Goldenrod);
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"IN"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"OUT"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> <span class='code_note'>//需要先设置AutoSize=false 才能设置STNode大小</span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.AutoSize = <span class='code_key'>false</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.Size = <span class='code_key'>new</span> <span class='code_class'>Size</span>(100, 100);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=208 src='./images/tu_mynode_autosize.png'/>
<p>可以看到<span class='span_mark'>MyNode</span>的大小不再自动计算 可<span class='span_mark'>STNodeOption</span>的位置依旧会进行自动计算 如果想修改<span class='span_mark'>STNodeOption</span>的位置可以重写<span class='span_mark'>OnSetOptionXXX</span></p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>MyNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_in;
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_out;
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"MyNode"</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.TitleColor = <span class='code_class'>Color</span>.FromArgb(200, <span class='code_class'>Color</span>.Goldenrod);
<span class='span_code_line'></span>
<span class='span_code_line'></span> m_op_in = <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"IN"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> m_op_out = <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"OUT"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>string</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> <span class='code_note'>//需要先设置AutoSize=false 才能设置STNode大小</span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.AutoSize = <span class='code_key'>false</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.Size = <span class='code_key'>new</span> <span class='code_class'>Size</span>(100, 100);
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_note'>//无论AutoSize为何值 都可以对选项连接点位置进行修改</span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_class'>Point</span> OnSetOptionDotLocation(<span class='code_class'>STNodeOption</span> op, <span class='code_class'>Point</span> pt, <span class='code_key'>int</span> nIndex) {
<span class='span_code_line'></span> <span class='code_key'>if</span> (op == m_op_in) <span class='code_key'>return</span> <span class='code_key'>new</span> <span class='code_class'>Point</span>(pt.X, pt.Y + 20);
<span class='span_code_line'></span> <span class='code_key'>return</span> <span class='code_key'>base</span>.OnSetOptionDotLocation(op, pt, nIndex);
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_note'>//无论AutoSize为何值 都可以对选项文本显示区区域进行修改</span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_class'>Rectangle</span> OnSetOptionTextRectangle(<span class='code_class'>STNodeOption</span> op, <span class='code_class'>Rectangle</span> rect, <span class='code_key'>int</span> nIndex) {
<span class='span_code_line'></span> <span class='code_key'>if</span> (op == m_op_out) <span class='code_key'>return</span> <span class='code_key'>new</span> <span class='code_class'>Rectangle</span>(rect.X, rect.Y + 20, rect.Width, rect.Height);
<span class='span_code_line'></span> <span class='code_key'>return</span> <span class='code_key'>base</span>.OnSetOptionTextRectangle(op, rect, nIndex);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=208 src='./images/tu_mynode_autosize_onset.png'/>
<p>可以看到代码中修改<span class='span_mark'>STNodeOption</span>连线的点和文本区域是通过重写函数去修改的 为什么不设计成<span class='span_mark'>STNodeOption.DotLeft=xxx</span>的方式是因为作者认为这样反而会更加的麻烦</p>
<p>重写函数中所传入的<span class='span_mark'>pt</span><span class='span_mark'>rect</span>都是自动计算过后的数据 这样开发者在修改位置的时候会有一定的参照 如果通过<span class='span_mark'>STNodeOption.DotLeft=xxx</span>这样的方式 那么开发者无法得到一个参照位置需要全部自己计算</p>
<p>而且还需要绑定<span class='span_mark'>STNode.Resize</span>等事件来监听<span class='span_mark'>STNode</span>大小的变化来重新计算位置 所以相比之下<span class='span_mark'>OnSetOptionXXX</span>的方式反而相对没那么繁琐</p>
<p>目前为止出现的所有案例都不具备数据的传递与接收 在接下来的案例中将开始添加功能</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_h'>案例 - ClockNode</h2></div>
<hr/>
<p><span class='span_mark'>STNodeOption</span>通过对<span class='span_mark'>DataTransfer</span>事件的绑定可以获取到此选项的所有数据输入</p>
<p><span class='span_mark'>STNodeOption.TransferData(object)</span>函数可以向此选项上的所有连线传递数据</p>
<p>接下来实现一个具备一定功能的节点 目前而言能想到的最好的实现就是定义一个时钟节点</p>
<p>因为目前介绍到的内容还不足以能够自由的向节点提供任意数据 所以需要一个能自己产生数据的节点</p>
<p>该节点每秒钟向外输出一次当前系统时间</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>ClockNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>Thread</span> m_thread;
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_out_time;
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"ClockNode"</span>;
<span class='span_code_line'></span> m_op_out_time = <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"Time"</span>, <span class='code_key'>typeof</span>(<span class='code_class'>DateTime</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_note'>//当所属容器发生变化时候发生</span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnOwnerChanged() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnOwnerChanged();
<span class='span_code_line'></span> <span class='code_key'>if</span> (<span class='code_key'>this</span>.Owner == <span class='code_key'>null</span>) { <span class='code_note'>//当容器被置空时停止线程</span>
<span class='span_code_line'></span> <span class='code_key'>if</span> (m_thread != <span class='code_key'>null</span>) m_thread.Abort();
<span class='span_code_line'></span> <span class='code_key'>return</span>;
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_key'>this</span>.Owner.SetTypeColor(<span class='code_key'>typeof</span>(<span class='code_class'>DateTime</span>), <span class='code_class'>Color</span>.DarkCyan);
<span class='span_code_line'></span> m_thread = <span class='code_key'>new</span> <span class='code_class'>Thread</span>(() => {
<span class='span_code_line'></span> <span class='code_key'>while</span> (<span class='code_key'>true</span>) {
<span class='span_code_line'></span> <span class='code_class'>Thread</span>.Sleep(1000);
<span class='span_code_line'></span> <span class='code_note'>//STNodeOption.TransferData(object) 会自动的向选项上所有连接投递数据</span>
<span class='span_code_line'></span> <span class='code_note'>//STNodeOption.TransferData(object) 会自动设置STNodeOption.Data</span>
<span class='span_code_line'></span> m_op_out_time.TransferData(<span class='code_class'>DateTime</span>.Now);
<span class='span_code_line'></span> <span class='code_note'>//与WinForm一样 在线程中如果需要跨UI线程操作 节点提供了 Begin/Invoke() 可完成对应操作</span>
<span class='span_code_line'></span> <span class='code_note'>//this.BeginInvoke(new MethodInvoker(() => m_op_out_time.TransferData(DateTime.Now)));</span>
<span class='span_code_line'></span> }
<span class='span_code_line'></span> }) { IsBackground = <span class='code_key'>true</span> };
<span class='span_code_line'></span> m_thread.Start();
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<p>当然上面的节点我们可以直接显示时间 但是为了演示数据的传递 还需要一个作为接受数据的节点</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>ShowClockNode</span> : <span class='code_class'>STNode</span> {
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_time_in;
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"ShowTime"</span>;
<span class='span_code_line'></span> <span class='code_note'>//由于此节点仅能显示一个时间 选项应当采用"单连接"模式 仅接受一个时间数据</span>
<span class='span_code_line'></span> m_op_time_in = <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"--"</span>, <span class='code_key'>typeof</span>(<span class='code_class'>DateTime</span>), <span class='code_key'>true</span>);
<span class='span_code_line'></span> <span class='code_note'>//监听事件 当有数据被传输到m_op_time_in时 会触发此事件</span>
<span class='span_code_line'></span> m_op_time_in.DataTransfer += <span class='code_key'>new</span> STNodeOptionEventHandler(op_DataTransfer);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>void</span> op_DataTransfer(<span class='code_key'>object</span> sender, <span class='code_class'>STNodeOptionEventArgs</span> e) {
<span class='span_code_line'></span> <span class='code_note'>//不仅仅是有数据传入时才会触发此事件 当有连接或断开时 同样触发此事件 所以需要判断连接状态</span>
<span class='span_code_line'></span> <span class='code_key'>if</span> (e.Status != <span class='code_class'>ConnectionStatus</span>.Connected || e.TargetOption.Data == <span class='code_key'>null</span>) {
<span class='span_code_line'></span> <span class='code_note'>//当STNode.AutoSize=true时 并不建议使用STNode.SetOptionText</span>
<span class='span_code_line'></span> <span class='code_note'>//因为在AutoSize下Text每发生一次变化STNode就会重新计算一次布局 应当通过添加控件方式来显示</span>
<span class='span_code_line'></span> <span class='code_note'>//由于当前还并未讲解到STNodeControl 所以这里暂时先采用当前设计</span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.SetOptionText(m_op_time_in, <span class='code_string'>"--"</span>);
<span class='span_code_line'></span> } <span class='code_key'>else</span> {
<span class='span_code_line'></span> <span class='code_key'>this</span>.SetOptionText(m_op_time_in, ((<span class='code_class'>DateTime</span>)e.TargetOption.Data).ToString());
<span class='span_code_line'></span> }
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<p>添加到STNodeEditor中的效果如下</p>
<img src='./images/tu_clockshowtime.gif'/>
<p>可以看到<span class='span_mark'>ShowClockNode</span>每秒都在刷新时间 同样通过上面代码可以看到<span class='span_mark'>ShowClockNode</span>并没有重写<span class='span_mark'>OnOwnerChanged()</span>函数向容器添加数据类型颜色信息</p>
<p>如果先把<span class='span_mark'>ShowClockNode</span>添加到<span class='span_mark'>STNodeEditor</span>中会发现节点选项确实没有颜色 但若<span class='span_mark'>ClockNode</span>被添加时候 会发现<span class='span_mark'>ShowClockNode</span>的选项立马就有了颜色</p>
<p>因为<span class='span_mark'>ClockNode</span>在添加的时候 向容器提交了数据类型的颜色 而<span class='span_mark'>ShowClockNode</span>选项的数据类型和<span class='span_mark'>ClockNode</span>一样 所以自然在绘制的时候具备了颜色数据</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_i'>STNode.SetOptionXXX</h2></div>
<hr/>
<p>在上面和之前的案例中可以看到需要修改<span class='span_mark'>STNodeOption</span>一些属性的时候并不是<span class='span_mark'>STNodeOption.XXX=XXX</span>方式去修改的 之所以这样设计是为了处于安全考虑</p>
<p>作者认为一个<span class='span_mark'>STNodeOption</span>只能被它的所有者所修改 而<span class='span_mark'>STNodeOption.XXX=XXX</span>的方式无法确定是被谁修改的 而<span class='span_mark'>STNode.SetOptionXXX()</span>被<span class='span_mark'>protected</span>所标记只能在内部被调用且在函数内部会判断<span class='span_mark'>STNodeOption.Owner</span>是否是当前类 以确保安全</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_j'>关于数据传递</h2></div>
<hr/>
<p class='p_hightlight'>并不是一定要<span class='span_mark'>STNodeOption.TransferData(object)</span>才能向下面的选项传递数据<span class='span_mark'>TransferData(object)</span>仅仅是主动向下<span class='span_mark'>更新数据</span></p>
<p>当一个新的连接刚被建立的时候 会被动传递一次数据 下面将<span class='span_mark'>ClockNode</span>的代码修改一下</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>ClockNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>Thread</span> m_thread;
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_out_time;
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"ClockNode"</span>;
<span class='span_code_line'></span> m_op_out_time = <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"Time"</span>, <span class='code_key'>typeof</span>(<span class='code_class'>DateTime</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> <span class='code_note'>//对选项的数据赋值</span>
<span class='span_code_line'></span> m_op_out_time.Data = <span class='code_class'>DateTime</span>.Now;
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img src='./images/tu_clocknode_data.gif'/>
<p>可以看到<span class='span_mark'>ShowClockNode</span>依然显示了时间 只是数据不再变化 因为在建立连接的同时也会触发<span class='span_mark'>DataTransfer</span>事件 在事件中<span class='span_mark'>ShowClockNode</span>通过<span class='span_mark'>e.TargetOption.Data</span>获取到了<span class='span_mark'>ClockNode</span>选项的数据</p>
<p>当一个连接被建立与断开时事件触发顺序如下</p>
<p><span class='span_mark'>Connecting</span>-<span class='span_mark'>Connected</span>-<span class='span_mark'>DataTransfer</span> | <span class='span_mark'>DisConnecting</span>-<span class='span_mark'>DataTransfer</span>-<span class='span_mark'>DisConnected</span></p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_k'>STNodeHub</h2></div>
<hr/>
<img src='./images/stnodehub.gif'/>
<p><span class='span_mark'>STNodeHub</span>是一个内置的节点 其主要作用分线 可以将一个输出分散到多个输入或多个输出集中到一个输入点上以避免重复布线 也可在节点布线复杂时用于绕线</p>
<h1 class='h_title anchor_point' anchor='a_l'>[基础]STNodeControl</h1>
<p><span class='span_mark'>STNodeControl</span>作为<span class='span_mark'>STNode</span>控件的基类 有着许多与<span class='span_mark'>System.Windows.Forms.Control</span>同名的属性及事件 使得开发者可以像开发<span class='span_mark'>WinForm</span>程序一样去开发一个节点</p>
<p class='p_hightlight'>在此版本(2.0)中 并没有提供任何一个可用控件 仅<span class='span_mark'>STNodeControl</span>基类 需开发者继承进行扩展 若后期有空作者再来完善</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_m'>添加一个控件</h2></div>
<hr/>
<p>与<span class='span_mark'>System.Windows.Forms.Control</span>一样 <span class='span_mark'>STNode</span>拥有<span class='span_mark'>Controls</span>集合 其数据类型为<span class='span_mark'>STNodeControl</span></p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>MyNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"MyNode"</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.TitleColor = <span class='code_class'>Color</span>.FromArgb(200, <span class='code_class'>Color</span>.Goldenrod);
<span class='span_code_line'></span> <span class='code_note'>//需要先设置AutoSize=false 才能设置STNode大小</span>
<span class='span_code_line'></span> <span class='code_key'>this</span>.AutoSize = <span class='code_key'>false</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.Size = <span class='code_key'>new</span> <span class='code_class'>Size</span>(100, 100);
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>var</span> ctrl = <span class='code_key'>new</span> <span class='code_class'>STNodeControl</span>();
<span class='span_code_line'></span> ctrl.Text = <span class='code_string'>"Button"</span>;
<span class='span_code_line'></span> ctrl.Location = <span class='code_key'>new</span> <span class='code_class'>Point</span>(10, 10);
<span class='span_code_line'></span> <span class='code_key'>this</span>.Controls.Add(ctrl);
<span class='span_code_line'></span> ctrl.MouseClick += <span class='code_key'>new</span> <span class='code_class'>MouseEventHandler</span>(ctrl_MouseClick);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>void</span> ctrl_MouseClick(<span class='code_key'>object</span> sender, <span class='code_class'>MouseEventArgs</span> e) {
<span class='span_code_line'></span> <span class='code_class'>MessageBox</span>.Show(<span class='code_string'>"MouseClick"</span>);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=273 src='./images/tu_mynode_ctrl.png'/>
<p>可以看到与开发<span class='span_mark'>WinForm</span>程序几乎没有任何区别 唯一不同的是<span class='span_mark'>STNode</span>暂时还不提供所见即所得的UI设计器</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_n'>自定义一个 Button</h2></div>
<hr/>
<p>虽然上面的代码看起来像是添加了一个按钮控件 事实上那仅仅是<span class='span_mark'>STNodeControl</span>的默认绘制样式</p>
<p>下面来自定义一个<span class='span_mark'>Button</span>控件 具备鼠标悬停和点击效果 使其更加像一个按钮</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>STNodeButton</span> : <span class='code_class'>STNodeControl</span> {
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_key'>bool</span> m_b_enter;
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_key'>bool</span> m_b_down;
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnMouseEnter(<span class='code_class'>EventArgs</span> e) {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnMouseEnter(e);
<span class='span_code_line'></span> m_b_enter = <span class='code_key'>true</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.Invalidate();
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnMouseLeave(<span class='code_class'>EventArgs</span> e) {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnMouseLeave(e);
<span class='span_code_line'></span> m_b_enter = <span class='code_key'>false</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.Invalidate();
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnMouseDown(<span class='code_class'>MouseEventArgs</span> e) {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnMouseDown(e);
<span class='span_code_line'></span> m_b_down = <span class='code_key'>true</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.Invalidate();
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnMouseUp(<span class='code_class'>MouseEventArgs</span> e) {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnMouseUp(e);
<span class='span_code_line'></span> m_b_down = <span class='code_key'>false</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.Invalidate();
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnPaint(<span class='code_class'>DrawingTools</span> dt) {
<span class='span_code_line'></span> <span class='code_note'>//base.OnPaint(dt);</span>
<span class='span_code_line'></span> <span class='code_class'>Graphics</span> g = dt.Graphics;
<span class='span_code_line'></span> <span class='code_class'>SolidBrush</span> brush = dt.<span class='code_class'>SolidBrush</span>;
<span class='span_code_line'></span> brush.<span class='code_class'>Color</span> = <span class='code_key'>base</span>.BackColor;
<span class='span_code_line'></span> <span class='code_key'>if</span> (m_b_down) brush.<span class='code_class'>Color</span> = <span class='code_class'>Color</span>.SkyBlue;
<span class='span_code_line'></span> <span class='code_key'>else</span> <span class='code_key'>if</span> (m_b_enter) brush.<span class='code_class'>Color</span> = <span class='code_class'>Color</span>.DodgerBlue;
<span class='span_code_line'></span> g.FillRectangle(brush, 0, 0, <span class='code_key'>this</span>.Width, <span class='code_key'>this</span>.Height);
<span class='span_code_line'></span> g.DrawString(<span class='code_key'>this</span>.Text, <span class='code_key'>this</span>.Font, <span class='code_class'>Brushes</span>.White, <span class='code_key'>this</span>.ClientRectangle, <span class='code_key'>base</span>.m_sf);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img src='./images/tu_mynode_btn.gif'/>
<p>当然为了代码尽可能的简洁 按钮的效果写死在了代码中 上面的代码仅仅是演示如何去构建一个自定义控件 当然在这之前你需要具备一些<span class='span_mark'>GDI</span>相关的知识</p>
<p><GDI+程序设计>是一本不错的树</p>
<img src='./images/gdip.png'/>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_o'>案例 - 图像信息获取</h2></div>
<hr/>
<p>在上述的<span class='span_mark'>ClockNode</span>案例中 对于数据的数据是通过代码写死在节点中的 接下来这个案例通过上面编写的<span class='span_mark'>STNodeButton</span>来获取数据进行输出</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>ImageShowNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_out;
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"ImageShowNode"</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.TitleColor = <span class='code_class'>Color</span>.FromArgb(200, <span class='code_class'>Color</span>.Goldenrod);
<span class='span_code_line'></span> <span class='code_key'>this</span>.AutoSize = <span class='code_key'>false</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.Size = <span class='code_key'>new</span> <span class='code_class'>Size</span>(160, 150);
<span class='span_code_line'></span> m_op_out = <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>""</span>, <span class='code_key'>typeof</span>(<span class='code_class'>Image</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>var</span> ctrl = <span class='code_key'>new</span> <span class='code_class'>STNodeButton</span>();
<span class='span_code_line'></span> ctrl.Text = <span class='code_string'>"Open Image"</span>;
<span class='span_code_line'></span> ctrl.Location = <span class='code_key'>new</span> <span class='code_class'>Point</span>(5, 0);
<span class='span_code_line'></span> ctrl.Size = <span class='code_key'>new</span> <span class='code_class'>Size</span>(150, 20);
<span class='span_code_line'></span> <span class='code_key'>this</span>.Controls.Add(ctrl);
<span class='span_code_line'></span> ctrl.MouseClick += <span class='code_key'>new</span> <span class='code_class'>MouseEventHandler</span>(ctrl_MouseClick);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>void</span> ctrl_MouseClick(<span class='code_key'>object</span> sender, <span class='code_class'>MouseEventArgs</span> e) {
<span class='span_code_line'></span> OpenFileDialog ofd = <span class='code_key'>new</span> OpenFileDialog();
<span class='span_code_line'></span> ofd.Filter = <span class='code_string'>"*.png|*.png|*.jpg|*.jpg"</span>;
<span class='span_code_line'></span> <span class='code_key'>if</span> (ofd.ShowDialog() != DialogResult.OK) <span class='code_key'>return</span>;
<span class='span_code_line'></span> m_op_out.TransferData(<span class='code_class'>Image</span>.FromFile(ofd.FileName), <span class='code_key'>true</span>);
<span class='span_code_line'></span> <span class='code_key'>this</span>.Invalidate();
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnDrawBody(<span class='code_class'>DrawingTools</span> dt) {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnDrawBody(dt);<span class='code_note'>//当然用户可以通过扩展"STNodeControl"编写一个"STNodePictureBox"控件来显示图片</span>
<span class='span_code_line'></span> <span class='code_class'>Graphics</span> g = dt.<span class='code_class'>Graphics</span>;
<span class='span_code_line'></span> <span class='code_class'>Rectangle</span> rect = <span class='code_key'>new</span> <span class='code_class'>Rectangle</span>(<span class='code_key'>this</span>.Left + 5, <span class='code_key'>this</span>.Top + <span class='code_key'>this</span>.TitleHeight + 20, 150, 105);
<span class='span_code_line'></span> g.FillRectangle(<span class='code_class'>Brushes</span>.Gray, rect);
<span class='span_code_line'></span> <span class='code_key'>if</span> (m_op_out.Data != <span class='code_key'>null</span>)
<span class='span_code_line'></span> g.DrawImage((<span class='code_class'>Image</span>)m_op_out.Data, rect);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<p>接下来需要一个数据接收节点 比如获取图像大小</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>ImageSizeNode</span> : <span class='code_class'>STNode</span> {
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_in;
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"ImageSize"</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.TitleColor = <span class='code_class'>Color</span>.FromArgb(200, <span class='code_class'>Color</span>.Goldenrod);
<span class='span_code_line'></span> m_op_in = <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"--"</span>, <span class='code_key'>typeof</span>(<span class='code_class'>Image</span>), <span class='code_key'>true</span>);
<span class='span_code_line'></span> m_op_in.DataTransfer += <span class='code_key'>new</span> STNodeOptionEventHandler(m_op_in_DataTransfer);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>void</span> m_op_in_DataTransfer(<span class='code_key'>object</span> sender, <span class='code_class'>STNodeOptionEventArgs</span> e) {
<span class='span_code_line'></span> <span class='code_key'>if</span> (e.Status != <span class='code_class'>ConnectionStatus</span>.Connected || e.TargetOption.Data == <span class='code_key'>null</span>) {
<span class='span_code_line'></span> <span class='code_key'>this</span>.SetOptionText(m_op_in, <span class='code_string'>"--"</span>);
<span class='span_code_line'></span> } <span class='code_key'>else</span> {
<span class='span_code_line'></span> <span class='code_class'>Image</span> img = (<span class='code_class'>Image</span>)e.TargetOption.Data;
<span class='span_code_line'></span> <span class='code_key'>this</span>.SetOptionText(m_op_in, <span class='code_string'>"W:"</span> + img.Width + <span class='code_string'>" H:"</span> + img.Height);
<span class='span_code_line'></span> }
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=418 src='./images/tu_imagenode.png'/>
<p>通过点击<span class='span_mark'>Open Image</span>按钮可以选择一张图片并在节点中显示 在<span class='span_mark'>ImageSizeNode</span>连接后 就获取到了图像的大小</p>
<p>在上图中<span class='span_mark'>ImageChannel</span>节点代码这里并没有给出 代码在<span class='span_mark'>WinNodeEditorDemo</span>工程中 其作用为提取一张图像的RGB通道 对于<span class='span_mark'>ImageShowNode</span>而言它仅仅是提供了数据源并显示 对于<span class='span_mark'>ImageSizeNode</span>和<span class='span_mark'>ImageChannel</span>节点而言 它们并不知道会被什么节点连接 它们仅仅是完成了各自的功能并将结果传递给了输出选项 等待被下一个节点连接</p>
<p>而对于执行逻辑完全是由用户布线将它们的功能串在了一起 开发期间节点与节点之间没有任何的交互 唯一将它们牵扯在一起的是一个<span class='span_mark'>Image</span>的数据类型 这样一来节点与节点之间几乎不存在耦合关系</p>
<h1 class='h_title anchor_point' anchor='a_p'>[基础]STNodeEditor</h1>
<p><span class='span_mark'>STNodeEditor</span>作为<span class='span_mark'>STNode</span>的容器 同样提供了大量的属性与事件供开发者使用 关于<span class='span_mark'>STNodeEditor</span>更为详细的API列表 请参考<a target='_bank' href='./doc.html'>帮助文档</a></p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_q'>保存画布</h2></div>
<hr/>
<p>对于<span class='span_mark'>STNodeEditor</span>中的节点以及连线的关系是可以文件持久化保存的</p>
<p>通过<span class='span_mark'>STNodeEditor.SaveCanvas(string strFileName)</span>函数可以将画布中的内容持久化保存</p>
<p class='p_hightlight'>需要注意的是<span class='span_mark'>SaveCanvas()</span>系列函数会调用<span class='span_mark'>internal byte[] STNode.GetSaveData()</span>函数来获取每个节点的二进制数据</p>
<p>而<span class='span_mark'>GetSaveData()</span>函数并非将节点自身序列化 <span class='span_mark'>GetSaveData()</span>函数会将节点自身的基础数据及原始属性进行二进制化 然后调用<span class='span_mark'>virtual OnSaveNode(Dictionary<string, byte[]> dic)</span>向扩展节点索要节点需要保存的数据</p>
<p class='p_hightlight'>所以若有保存需求 节点开发者可能需要通过重写<span class='span_mark'>OnSaveNode()</span>函数 来确保一些需要的数据能够被保存</p>
<p>关于更多节点保存的内容将会在后面的内容中介绍</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_r'>加载画布</h2></div>
<hr/>
<p>通过<span class='span_mark'>STNodeEditor.LoadCanvas(string strFileName)</span>函数可以从文件中加载保存的数据</p>
<p class='p_hightlight'>若<span class='span_mark'>STNodeEditor</span>存在其他程序集中的节点 则需要通过调用<span class='span_mark'>STNodeEditor.LoadAssembly(string strFile)</span>来加载程序集以确保文件中的节点能够被正确还原</p>
<p>因为还原过程并非序列化 而是通过<span class='span_mark'>(STNode)Activator.CreateInstance(stNodeType)</span>方式动态创建一个节点然后调用<span class='span_mark'>virtual OnSaveNode(Dictionary<string, byte[]> dic)</span>对数据进行还原 而<span class='span_mark'>dic</span>则为<span class='span_mark'>OnSaveNode()</span>所保存的数据</p>
<p class='p_hightlight'>因为还原节点是通过反射动态创建节点的 所以扩展的<span class='span_mark'>STNode</span>中必须提供空参数构造器</p>
<p>关于更多节点加载的内容将会在后面的内容中介绍</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_s'>常用事件</h2></div>
<hr/>
<p><span class='span_mark'>ActiveChanged</span>,<span class='span_mark'>SelectedChanged</span> 可以监控控件中节点被选中的变化情况</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span>stNodeEditor1.ActiveChanged += (s, e) => <span class='code_class'>Console</span>.WriteLine(stNodeEditor1.ActiveNode.Title);
<span class='span_code_line'></span>
<span class='span_code_line'></span>stNodeEditor1.SelectedChanged += (s, e) => {
<span class='span_code_line'></span> <span class='code_key'>foreach</span>(<span class='code_key'>var</span> n <span class='code_key'>in</span> stNodeEditor1.GetSelectedNode()){
<span class='span_code_line'></span> <span class='code_class'>Console</span>.WriteLine(n.Title);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>};</pre>
</div>
<p>如果希望在画布每次缩放后在编辑器上提示缩放比可以通过<span class='span_mark'>CanvasScaled</span>事件获取</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span>stNodeEditor1.CanvasScaled += (s, e) => {
<span class='span_code_line'></span> stNodeEditor1.ShowAlert(stNodeEditor1.CanvasScale.ToString(<span class='code_string'>"F2"</span>),
<span class='span_code_line'></span> <span class='code_class'>Color</span>.White, <span class='code_class'>Color</span>.FromArgb(127, 255, 255, 0));
<span class='span_code_line'></span>};</pre>
</div>
<p>如果希望画布中有节点连线时 提示连线状态可以通过<span class='span_mark'>OptionConnected</span>事件获取状态</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span>stNodeEditor1.OptionConnected += (s, e) => {
<span class='span_code_line'></span> stNodeEditor1.ShowAlert(e.Status.ToString(), <span class='code_class'>Color</span>.White,
<span class='span_code_line'></span> <span class='code_class'>Color</span>.FromArgb(125, e.Status <span class='code_class'>ConnectionStatus</span>.Connected ? <span class='code_class'>Color</span>.Lime : <span class='code_class'>Color</span>.Red));
<span class='span_code_line'></span>};</pre>
</div>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_t'>常用方法</h2></div>
<hr/>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_note'>/// <summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>移动画布原点坐标到指定的控件坐标位置 (当不存在 Node 时候 无法移动)</span></span>
<span class='span_code_line'></span><span class='code_note'>/// </summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"x"</span>><span class='code_note_1'>X 坐标</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"y"</span>><span class='code_note_1'>Y 坐标</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"bAnimation"</span>><span class='code_note_1'>移动过程中是否启动动画效果</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"ma"</span>><span class='code_note_1'>指定需要修改的坐标参数</span></param></span>
<span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>void</span> MoveCanvas(<span class='code_key'>float</span> x, <span class='code_key'>float</span> y, <span class='code_key'>bool</span> bAnimation, <span class='code_class'>CanvasMoveArgs</span> ma);</pre>
</div>
<hr/>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_note'>/// <summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>缩放画布(当不存在 Node 时候 无法缩放)</span></span>
<span class='span_code_line'></span><span class='code_note'>/// </summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"f"</span>><span class='code_note_1'>缩放比例</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"x"</span>><span class='code_note_1'>缩放中心X位于控件上的坐标</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"y"</span>><span class='code_note_1'>缩放中心Y位于控件上的坐标</span></param></span>
<span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>void</span> ScaleCanvas(<span class='code_key'>float</span> f, <span class='code_key'>float</span> x, <span class='code_key'>float</span> y);</pre>
</div>
<hr/>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_note'>/// <summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>向编辑器中添加默认数据类型颜色</span></span>
<span class='span_code_line'></span><span class='code_note'>/// </summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"t"</span>><span class='code_note_1'>数据类型</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"clr"</span>><span class='code_note_1'>对应颜色</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"bReplace"</span>><span class='code_note_1'>若已经存在是否替换颜色</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <returns><span class='code_note_1'>被设置后的颜色</span></returns></span>
<span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_class'>Color</span> SetTypeColor(<span class='code_class'>Type</span> t, <span class='code_class'>Color</span> clr, <span class='code_key'>bool</span> bReplace);</pre>
</div>
<hr/>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_note'>/// <summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>在画布中显示提示信息</span></span>
<span class='span_code_line'></span><span class='code_note'>/// </summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"strText"</span>><span class='code_note_1'>要显示的信息</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"foreColor"</span>><span class='code_note_1'>信息前景色</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"backColor"</span>><span class='code_note_1'>信息背景色</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"nTime"</span>><span class='code_note_1'>信息持续时间</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"al"</span>><span class='code_note_1'>信息要显示的位置</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"bRedraw"</span>><span class='code_note_1'>是否立即重绘</span></param></span>
<span class='span_code_line'></span><span class='code_key'>void</span> ShowAlert(<span class='code_key'>string</span> strText, <span class='code_class'>Color</span> foreColor, <span class='code_class'>Color</span> backColor, <span class='code_key'>int</span> nTime, <span class='code_class'>AlertLocation</span> al, <span class='code_key'>bool</span> bRedraw);
<span class='span_code_line'></span><span class='code_note'>//e.g.</span>
<span class='span_code_line'></span>stNodeEditor1.ShowAlert(<span class='code_string'>"this is test info"</span>, <span class='code_class'>Color</span>.White, <span class='code_class'>Color</span>.FromArgb(200, <span class='code_class'>Color</span>.Yellow));</pre>
</div>
<img width=208 src='./images/tu_editor_alert.png'/>
<p>关于<span class='span_mark'>STNodeEditor</span>更多的<span class='span_mark'>属性</span><span class='span_mark'>函数</span><span class='span_mark'>事件</span>请参考<a target='_bank' href='./doc.html'>API文档</a> 此文档更注重于与<span class='span_mark'>STNode</span>相关的内容及演示</p>
<h1 class='h_title anchor_point' anchor='a_u'>STNodePropertyGrid</h1>
<p><span class='span_mark'>STNodePropertyGrid</span>是随着类库一起发布的另一个控件 可以与<span class='span_mark'>STNodeEditor</span>结合使用</p>
<img width=462 src='./images/tu_stnodepropertygrid.png'/>
<p><span class='span_mark'>STNodePropertyGrid</span>一共有两个面板 通过右上角的按钮可以进行切换 分别是<span class='span_mark'>属性面板</span>与<span class='span_mark'>节点信息面板</span></p>
<p class='p_hightlight'>仅存在<span class='span_mark'>属性</span>与<span class='span_mark'>节点信息</span>才会显示对应面板</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_v'>如何使用</h2></div>
<hr/>
<p><span class='span_mark'>STNodePropertyGrid</span>的核心方法为<span class='span_mark'>SetNode(STNode)</span>通常与<span class='span_mark'>STNodeEditor</span>绑定使用</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span>stNodeEditor1.ActiveChanged += (s, e) => stNodePropertyGrid1.SetNode(stNodeEditor1.ActiveNode);</pre>
</div>
<p>既然叫做属性编辑器必然和<span class='span_mark'>STNode</span>的属性相关 <span class='span_mark'>STNode</span>作为一个普通的<span class='span_mark'>class</span>当然也可以是拥有属性的 而<span class='span_mark'>STNodePropertyGrid</span>就是展示并修改它们 就像在<span class='span_mark'>WinForm</span>开发时候UI设计器中所看到的一样</p>
<p>接下来编写一个节点试一下</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>PropertyTestNode</span> : <span class='code_class'>STNode</span> {
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_key'>int</span> _Number;
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_key'>int</span> Number {
<span class='span_code_line'></span> <span class='code_key'>get</span> { <span class='code_key'>return</span> _Number; }
<span class='span_code_line'></span> <span class='code_key'>set</span> { _Number = <span class='code_key'>value</span>; }
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"PropertyTest"</span>;
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=462 src='./images/tu_property_1.png'/>
<p>发现并没有看到想象中的画面 并没有显示<span class='span_mark'>Number</span>属性</p>
<p>与<span class='span_mark'>System.Windows.Forms.PropertyGrid</span>不同的是<span class='span_mark'>PropertyGrid</span>会显示<span class='span_mark'>class</span>中的所有属性 而<span class='span_mark'>STNodePropertyGrid</span>并没有采用这样的设计方案</p>
<p>S T N O D E PropertyGrid 这个是<span class='span_mark'>STNodePropertyGrid</span>并不会随便的显示一个属性 因为作者认为开发者可能并不希望<span class='span_mark'>STNode</span>中的所有属性都被显示出来 即使需要显示出来开发者可能也并不希望在属性窗口看到的是<span class='span_mark'>Number</span>而是一个别的名字 毕竟<span class='span_mark'>Number</span>是写代码时候用的</p>
<p class='p_hightlight'>只有被<span class='span_mark'>STNodePropertyAttribute</span>特性所标记的属性才会被<span class='span_mark'>STNodePropertyGrid</span>显示</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_w'>STNodePropertyAttribute</h2></div>
<hr/>
<p><span class='span_mark'>STNodePropertyAttribute</span>拥有三个属性<span class='span_mark'>Name</span><span class='span_mark'>Description</span><span class='span_mark'>DescriptorType</span></p>
<p><span class='span_mark'>Name</span> - 对于此属性希望在<span class='span_mark'>STNodePropertyGrid</span>上显示的名称</p>
<p><span class='span_mark'>Description</span> - 当<span class='span_mark'>鼠标左键</span>在<span class='span_mark'>STNodePropertyGrid</span>上<span class='span_mark'>长按</span>属性名称时候希望显示的描述信息</p>
<p><span class='span_mark'>DescriptorType</span> - 决定属性如何与属性窗口进行数据交互 将在稍后讲解此属性</p>
<p><span class='span_mark'>STNodePropertyAttribute</span>构造函数为<span class='span_mark'>STNodePropertyAttribute(string strName,string strDescription)</span></p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>PropertyTestNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_key'>int</span> _Number;
<span class='span_code_line'></span> [<span class='code_class'>STNodeProperty</span>(<span class='code_string'>"Name"</span>, <span class='code_string'>"Description for this property"</span>)]
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_key'>int</span> Number {
<span class='span_code_line'></span> <span class='code_key'>get</span> { <span class='code_key'>return</span> _Number; }
<span class='span_code_line'></span> <span class='code_key'>set</span> { _Number = <span class='code_key'>value</span>; }
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"PropertyTest"</span>;
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=462 src='./images/tu_property_2.png'/>
<p>此时就能看到属性被正确的显示了 并且可以被设置 而且左键长按属性名称会显示描述信息</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_x'>STNodeAttribute</h2></div>
<hr/>
<p>若希望显示节点信息 则<span class='span_mark'>STNode</span>需要被<span class='span_mark'>STNodeAttribute</span>特性标记</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span>[<span class='code_class'>STNode</span>(<span class='code_string'>"AA/BB"</span>, <span class='code_string'>"Author"</span>, <span class='code_string'>"Mail"</span>, <span class='code_string'>"Link"</span>, <span class='code_string'>"Description"</span>)]
<span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>PropertyTestNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_key'>int</span> _Number;
<span class='span_code_line'></span> [<span class='code_class'>STNodeProperty</span>(<span class='code_string'>"Name"</span>, <span class='code_string'>"Description for this property"</span>)]
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_key'>int</span> Number {
<span class='span_code_line'></span> <span class='code_key'>get</span> { <span class='code_key'>return</span> _Number; }
<span class='span_code_line'></span> <span class='code_key'>set</span> { _Number = <span class='code_key'>value</span>; }
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"PropertyTest"</span>;
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<p>通过右上角的按钮可进行面板切换</p>
<p class='p_hightlight'>其中<span class='span_mark'>AA/BB</span>用于在<span class='span_mark'>STNodeTreeView</span>中构建路径使用</p>
<img width=462 src='./images/tu_property_5.png'/>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span>stNodePropertyGrid1.SetInfoKey(<span class='code_string'>"Author"</span>, <span class='code_string'>"Mail"</span>, <span class='code_string'>"Link"</span>, <span class='code_string'>"Show Help"</span>);</pre>
</div>
<p><span class='span_mark'>信息面板</span>内容的key可通过<span class='span_mark'>SetInfoKey()</span>函数来设置语言 默认简体中文显示</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_y'>查看帮助</h2></div>
<hr/>
<p>上面一个<span class='span_mark'>信息面板</span>案例中可以看到<span class='span_mark'>查看帮助</span>按钮不可用 若希望可用则需要提供<span class='span_mark'>魔术方法</span></p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span>[<span class='code_class'>STNode</span>(<span class='code_string'>"AA/BB"</span>, <span class='code_string'>"Author"</span>, <span class='code_string'>"Mail"</span>, <span class='code_string'>"Link"</span>, <span class='code_string'>"Description"</span>)]
<span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>PropertyTestNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_key'>int</span> _Number;
<span class='span_code_line'></span> [<span class='code_class'>STNodeProperty</span>(<span class='code_string'>"Name"</span>, <span class='code_string'>"Description for this property"</span>)]
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_key'>int</span> Number {
<span class='span_code_line'></span> <span class='code_key'>get</span> { <span class='code_key'>return</span> _Number; }
<span class='span_code_line'></span> <span class='code_key'>set</span> { _Number = <span class='code_key'>value</span>; }
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"PropertyTest"</span>;
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_note'>/// <summary></span>
<span class='span_code_line'></span> <span class='code_note'>/// <span class='code_note_1'>此方法为魔术方法</span></span>
<span class='span_code_line'></span> <span class='code_note'>/// <span class='code_note_1'>若存在 static void ShowHelpInfo(string) 且此类被STNodeAttribute标记</span></span>
<span class='span_code_line'></span> <span class='code_note'>/// <span class='code_note_1'>则此方法将作为属性编辑器上 查看帮助 功能</span></span>
<span class='span_code_line'></span> <span class='code_note'>/// </summary></span>
<span class='span_code_line'></span> <span class='code_note'>/// <param name=<span class='code_string'>"strFileName"</span>><span class='code_note_1'>此类所在的模块所在的文件路径</span></param></span>
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_key'>static</span> <span class='code_key'>void</span> ShowHelpInfo(<span class='code_key'>string</span> strFileName) {
<span class='span_code_line'></span> <span class='code_class'>MessageBox</span>.Show(<span class='code_string'>"this is -> ShowHelpInfo(string);\r\n"</span> + strFileName);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=462 src='./images/tu_property_6.png'/>
<p>此时发现<span class='span_mark'>查看帮助</span>按钮变成了启用状态 STNodeAttribute还提供了两个<span class='span_mark'>static</span>函数</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_note'>/// <summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>获取类型的帮助函数</span></span>
<span class='span_code_line'></span><span class='code_note'>/// </summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"stNodeType"</span>><span class='code_note_1'>节点类型</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <returns><span class='code_note_1'>函数信息</span></returns></span>
<span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>static</span> <span class='code_class'>MethodInfo</span> GetHelpMethod(<span class='code_class'>Type</span> stNodeType);
<span class='span_code_line'></span><span class='code_note'>/// <summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>执行对应节点类型的帮助函数</span></span>
<span class='span_code_line'></span><span class='code_note'>/// </summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"stNodeType"</span>><span class='code_note_1'>节点类型</span></param></span>
<span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>static</span> <span class='code_key'>void</span> ShowHelp(<span class='code_class'>Type</span> stNodeType);</pre>
</div>
<hr/>
<div><h2 class='h_option anchor_point' anchor='a_z'>案例 - 两数相加</h2></div>
<hr/>
<p>既然<span class='span_mark'>STNodePropertyGrid</span>可以显示并修改属性 那么接下来这个案例将通过属性窗口来提供一个数据的输入</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>NumberInputNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_key'>int</span> _Number;
<span class='span_code_line'></span> [<span class='code_class'>STNodeProperty</span>(<span class='code_string'>"Input"</span>, <span class='code_string'>"Input number"</span>)]
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_key'>int</span> Number {
<span class='span_code_line'></span> <span class='code_key'>get</span> { <span class='code_key'>return</span> _Number; }
<span class='span_code_line'></span> <span class='code_key'>set</span> {
<span class='span_code_line'></span> _Number = <span class='code_key'>value</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.SetOptionText(m_op_out, <span class='code_key'>value</span>.ToString());
<span class='span_code_line'></span> m_op_out.TransferData(<span class='code_key'>value</span>);
<span class='span_code_line'></span> }
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_out;
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"NumberInput"</span>;
<span class='span_code_line'></span> m_op_out = <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"0"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>int</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>NumberAddNode</span> : <span class='code_class'>STNode</span> {
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_in_1;
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_in_2;
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>STNodeOption</span> m_op_out;
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"NumberAdd"</span>;
<span class='span_code_line'></span> m_op_in_1 = <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"0"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>int</span>), <span class='code_key'>true</span>);
<span class='span_code_line'></span> m_op_in_2 = <span class='code_key'>this</span>.InputOptions.Add(<span class='code_string'>"0"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>int</span>), <span class='code_key'>true</span>);
<span class='span_code_line'></span> m_op_out = <span class='code_key'>this</span>.OutputOptions.Add(<span class='code_string'>"0"</span>, <span class='code_key'>typeof</span>(<span class='code_key'>int</span>), <span class='code_key'>false</span>);
<span class='span_code_line'></span>
<span class='span_code_line'></span> m_op_in_1.DataTransfer += <span class='code_key'>new</span> STNodeOptionEventHandler(m_op_in_DataTransfer);
<span class='span_code_line'></span> m_op_in_2.DataTransfer += <span class='code_key'>new</span> STNodeOptionEventHandler(m_op_in_DataTransfer);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>void</span> m_op_in_DataTransfer(<span class='code_key'>object</span> sender, <span class='code_class'>STNodeOptionEventArgs</span> e) {
<span class='span_code_line'></span> <span class='code_key'>if</span> (e.Status != <span class='code_class'>ConnectionStatus</span>.Connected || e.TargetOption == <span class='code_key'>null</span>) {
<span class='span_code_line'></span> <span class='code_key'>if</span> (sender == m_op_in_1) m_op_in_1.Data = 0;
<span class='span_code_line'></span> <span class='code_key'>if</span> (sender == m_op_in_2) m_op_in_2.Data = 0;
<span class='span_code_line'></span> } <span class='code_key'>else</span> {
<span class='span_code_line'></span> <span class='code_key'>if</span> (sender == m_op_in_1) m_op_in_1.Data = e.TargetOption.Data;
<span class='span_code_line'></span> <span class='code_key'>if</span> (sender == m_op_in_2) m_op_in_2.Data = e.TargetOption.Data;
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_key'>if</span> (m_op_in_1.Data == <span class='code_key'>null</span>) m_op_in_1.Data = 0;
<span class='span_code_line'></span> <span class='code_key'>if</span> (m_op_in_2.Data == <span class='code_key'>null</span>) m_op_in_2.Data = 0;
<span class='span_code_line'></span> <span class='code_key'>int</span> nResult = (<span class='code_key'>int</span>)m_op_in_1.Data + (<span class='code_key'>int</span>)m_op_in_2.Data;
<span class='span_code_line'></span> <span class='code_key'>this</span>.SetOptionText(m_op_in_1, m_op_in_1.Data.ToString());
<span class='span_code_line'></span> <span class='code_key'>this</span>.SetOptionText(m_op_in_2, m_op_in_2.Data.ToString());
<span class='span_code_line'></span> <span class='code_key'>this</span>.SetOptionText(m_op_out, nResult.ToString());
<span class='span_code_line'></span> m_op_out.TransferData(nResult);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=462 src='./images/tu_property_3.png'/>
<p>通过<span class='span_mark'>Number</span>属性的<span class='span_mark'>set</span>访问器将输入的数字向下传递</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='b_a'>ReadOnlyModel</h2></div>
<hr/>
<p>某些情况下并不希望<span class='span_mark'>STNodePropertyGrid</span>对属性进行设置 仅仅希望展示属性 则可以启用<span class='span_mark'>ReadOnlyModel</span></p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span>stNodePropertyGrid1.ReadOnlyModel = <span class='code_key'>true</span>;</pre>
</div>
<img width=462 src='./images/tu_property_4.png'/>
<p>在<span class='span_mark'>ReadOnlyModel</span>下 属性无法通过<span class='span_mark'>STNodePropertyGrid</span>被设置</p>
<h1 class='h_title anchor_point' anchor='b_b'>STNodePropertyDescriptor</h1>
<p>上面介绍了<span class='span_mark'>STNodePropertyAttribute</span>的<span class='span_mark'>Name</span>和<span class='span_mark'>Description</span>属性 还有第三个属性<span class='span_mark'>DescriptorType</span>其数据类型为<span class='span_mark'>Type</span> 默认值为<span class='span_mark'>typeof(STNodePropertyDescriptor)</span></p>
<p>虽然从目前的案例来看 上面的操作没有任何问题 但是并不是所有数据类型的属性都能够正确的被<span class='span_mark'>STNodePropertyGrid</span>所支持 默认的<span class='span_mark'>STNodePropertyDescriptor</span>仅支持下列数据类型</p>
<p><span class='span_mark'>int</span><span class='span_mark'>float</span><span class='span_mark'>double</span><span class='span_mark'>bool</span><span class='span_mark'>string</span><span class='span_mark'>Enum</span>以及它们的<span class='span_mark'>Array</span></p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='b_c'>失败案例 - ColorTestNode</h2></div>
<hr/>
<p>下面创建一个节点添加一个<span class='span_mark'>Color</span>类型的属性</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>ColorTestNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> [<span class='code_class'>STNodeProperty</span>(<span class='code_string'>"TitleColor"</span>, <span class='code_string'>"Get or set the node TitleColor"</span>)]
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_class'>Color</span> ColorTest {
<span class='span_code_line'></span> <span class='code_key'>get</span> { <span class='code_key'>return</span> <span class='code_key'>this</span>.TitleColor; }
<span class='span_code_line'></span> <span class='code_key'>set</span> { <span class='code_key'>this</span>.TitleColor = <span class='code_key'>value</span>; }
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"ColorNode"</span>;
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=462 src='./images/tu_colornode_1.png'/>
<p>运行上述代码 会发现通过<span class='span_mark'>STNodePropertyGrid</span>对属性进行进行设置会出现错误 而且<span class='span_mark'>STNodePropertyGrid</span>中对于属性的值显示也很奇怪</p>
<p>即便<span class='span_mark'>System.Windows.Forms.PropertyGrid</span>能支持很多数据类型 但也并非万能 比如当属性类型是用户自定义类型时候属性编辑器根本无法知道要如何与图形化界面上的属性交互</p>
<p>对于<span class='span_mark'>System.Windows.Forms.PropertyGrid</span>的解决方案便是提供<span class='span_mark'>TypeConverter</span>将目标类型用<span class='span_mark'>TypeConverter</span>标记并实现重写 这样一来<span class='span_mark'>PropertyGrid</span>通过<span class='span_mark'>TypeConverter</span>就能够知道如何与图形化界面进行交互</p>
<p>而<span class='span_mark'>STNodePropertyGrid</span>提供的解决方案便是<span class='span_mark'>STNodePropertyDescriptor</span></p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='b_d'>关于属性描述器</h2></div>
<hr/>
<p class='p_hightlight'>之所以<span class='span_mark'>STNodePropertyGrid</span>能够正确的获取以及修改<span class='span_mark'>STNode</span>属性的值 全是依靠<span class='span_mark'>STNodePropertyDescriptor</span>在中间做交互 进行数据的转换以及响应属性窗口上的一些行为操作</p>
<p class='p_hightlight'>被<span class='span_mark'>STNodePropertyAttribute</span>标记的属性都会包装成<span class='span_mark'>STNodePropertyDescriptor</span>传递给<span class='span_mark'>STNodePropertyGrid</span> 一个<span class='span_mark'>STNodePropertyDescriptor</span>包含了<span class='span_mark'>STNodePropertyAttribute</span>中的<span class='span_mark'>Name</span><span class='span_mark'>Description</span>以及属性将会在<span class='span_mark'>STNodePropertyGrid</span>上显示的位置信息等 以及如何响应鼠标事件或键盘事件等</p>
<p>可以理解为<span class='span_mark'>STNodePropertyDescriptor</span>是每个被<span class='span_mark'>STNodePropertyAttribute</span>所标记属性的图形化界面接口 而主要作用则为在图形界面和真实属性之间传递数据</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='b_e'>GetValueFromString()</h2></div>
<hr/>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_note'>/// <summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>将字符串形式的属性值转换为属性目标类型的值</span></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>默认只支持 int float double string bool 以及上述类型的Array</span></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>若目标类型不在上述中 请重写此函数自行转换</span></span>
<span class='span_code_line'></span><span class='code_note'>/// </summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <param name=<span class='code_string'>"strText"</span>><span class='code_note_1'>字符串形式的属性值</span></param></span>
<span class='span_code_line'></span><span class='code_note'>/// <returns><span class='code_note_1'>属性真实目标类型的值</span></returns></span>
<span class='span_code_line'></span><span class='code_key'>protected</span> <span class='code_key'>internal</span> <span class='code_key'>virtual</span> <span class='code_key'>object</span> GetValueFromString(<span class='code_key'>string</span> strText);</pre>
</div>
<p class='p_hightlight'><span class='span_mark'>GetValueFromString()</span>函数主要负责将用户在<span class='span_mark'>STNodePropertyGrid</span>中输入字符串转换为对应属性需要的真实值</p>
<p>如: 假若属性是<span class='span_mark'>int</span>类型值 而用户在<span class='span_mark'>STNodePropertyGrid</span>中只能输入字符串的<span class='span_mark'>123</span>那么默认的<span class='span_mark'>GetValueFromString()</span>函数内部则会<span class='span_mark'>int.Parse(strText)</span>这样<span class='span_mark'>string</span>类型的<span class='span_mark'>123</span>就变成了<span class='span_mark'>int</span>类型的<span class='span_mark'>123</span></p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='b_f'>GetStringFromValue()</h2></div>
<hr/>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_note'>/// <summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>将属性目标类型的值转换为字符串形式的值</span></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>默认对类型值进行 ToString() 操作</span></span>
<span class='span_code_line'></span><span class='code_note'>/// <span class='code_note_1'>如需特殊处理 请重写此函数自行转换</span></span>
<span class='span_code_line'></span><span class='code_note'>/// </summary></span>
<span class='span_code_line'></span><span class='code_note'>/// <returns><span class='code_note_1'>属性值的字符串形式</span></returns></span>
<span class='span_code_line'></span><span class='code_key'>protected</span> <span class='code_key'>internal</span> <span class='code_key'>virtual</span> <span class='code_key'>string</span> GetStringFromValue();</pre>
</div>
<p class='p_hightlight'><span class='span_mark'>GetStringFromValue()</span>函数主要负责将属性值转换为字符串的方式在<span class='span_mark'>STNodePropertyGrid</span>中显示 默认的<span class='span_mark'>GetStringFromValue()</span>内部仅仅是对属性的值<span class='span_mark'>ToString()</span>操作</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='b_g'>成功案例 - ColorTestNode</h2></div>
<hr/>
<p>对上面失败案例的<span class='span_mark'>ColorTestNode.ColorTest</span>的<span class='span_mark'>STNodePropertyDescriptor</span>进行扩展</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>ColorTestNode</span> : <span class='code_class'>STNode</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_note'>//指定使用扩展的 STNodePropertyDescriptor 以便完成对 Color 类型的支持</span>
<span class='span_code_line'></span> [<span class='code_class'>STNodeProperty</span>(<span class='code_string'>"TitleColor"</span>, <span class='code_string'>"Get or set the node TitleColor"</span>, DescriptorType = <span class='code_key'>typeof</span>(<span class='code_class'>ColorDescriptor</span>))]
<span class='span_code_line'></span> <span class='code_key'>public</span> <span class='code_class'>Color</span> ColorTest {
<span class='span_code_line'></span> <span class='code_key'>get</span> { <span class='code_key'>return</span> <span class='code_key'>this</span>.TitleColor; }
<span class='span_code_line'></span> <span class='code_key'>set</span> { <span class='code_key'>this</span>.TitleColor = <span class='code_key'>value</span>; }
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnCreate() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnCreate();
<span class='span_code_line'></span> <span class='code_key'>this</span>.Title = <span class='code_string'>"ColorNode"</span>;
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}
<span class='span_code_line'></span>
<span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>ColorDescriptor</span> : <span class='code_class'>STNodePropertyDescriptor</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>object</span> GetValueFromString(<span class='code_key'>string</span> strText) {
<span class='span_code_line'></span> <span class='code_key'>string</span>[] strs = strText.Split(<span class='code_string'>','</span>);
<span class='span_code_line'></span> <span class='code_key'>return</span> <span class='code_class'>Color</span>.FromArgb(<span class='code_key'>int</span>.Parse(strs[0]), <span class='code_key'>int</span>.Parse(strs[1]), <span class='code_key'>int</span>.Parse(strs[2]), <span class='code_key'>int</span>.Parse(strs[3]));
<span class='span_code_line'></span> <span class='code_note'>//return base.GetValueFromString(strText);</span>
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>string</span> GetStringFromValue() {
<span class='span_code_line'></span> <span class='code_key'>var</span> v = (<span class='code_class'>Color</span>)<span class='code_key'>this</span>.GetValue(<span class='code_key'>null</span>);<span class='code_note'>//获取当前属性值</span>
<span class='span_code_line'></span> <span class='code_key'>return</span> v.A + <span class='code_string'>","</span> + v.R + <span class='code_string'>","</span> + v.G + <span class='code_string'>","</span> + v.B;
<span class='span_code_line'></span> <span class='code_note'>//return base.GetStringFromValue();</span>
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=462 src='./images/tu_colornode_2.png'/>
<p>此时就能够正确的显示与设置属性值了 因为<span class='span_mark'>GetValueFromString()</span>与<span class='span_mark'>GetStringFromValue()</span>正确的完成了属性值与字符串之间的转换</p>
<hr/>
<div><h2 class='h_option anchor_point' anchor='b_h'>高级案例 - ColorTestNode</h2></div>
<hr/>
<p>上面的案例虽然可以通过设置<span class='span_mark'>Color</span>的ARGB来完成对属性值的设置 但是明显这样的方式并不够友好</p>
<img width=462 src='./images/tu_colornode_3.png'/>
<p>是否能够像<span class='span_mark'>System.Windows.Forms.PropertyGrid</span>一样 能够让用户进行可视化的选择 <span class='span_mark'>STNodePropertyDescriptor</span>当然也能办到</p>
<p class='p_hightlight'><span class='span_mark'>STNodePropertyDescriptor</span>可以视作是一个显示属性的自定义控件 既然是自定义控件那么就具备了鼠标事件的响应</p>
<p>下面对<span class='span_mark'>ColorDescriptor</span>增加一些代码</p>
<div class='div_code'>
<pre class='pre_code'><span class='span_code_line'></span><span class='code_key'>public</span> <span class='code_key'>class</span> <span class='code_class'>ColorDescriptor</span> : <span class='code_class'>STNodePropertyDescriptor</span>
<span class='span_code_line'></span>{
<span class='span_code_line'></span> <span class='code_key'>private</span> <span class='code_class'>Rectangle</span> m_rect;<span class='code_note'>//此区域用作 属性窗口上绘制颜色预览</span>
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>object</span> GetValueFromString(<span class='code_key'>string</span> strText) {
<span class='span_code_line'></span> <span class='code_key'>string</span>[] strs = strText.Split(<span class='code_string'>','</span>);
<span class='span_code_line'></span> <span class='code_key'>return</span> <span class='code_class'>Color</span>.FromArgb(<span class='code_key'>int</span>.Parse(strs[0]), <span class='code_key'>int</span>.Parse(strs[1]), <span class='code_key'>int</span>.Parse(strs[2]), <span class='code_key'>int</span>.Parse(strs[3]));
<span class='span_code_line'></span> <span class='code_note'>//return base.GetValueFromString(strText);</span>
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>string</span> GetStringFromValue() {
<span class='span_code_line'></span> <span class='code_key'>var</span> v = (<span class='code_class'>Color</span>)<span class='code_key'>this</span>.GetValue(<span class='code_key'>null</span>);
<span class='span_code_line'></span> <span class='code_key'>return</span> v.A + <span class='code_string'>","</span> + v.R + <span class='code_string'>","</span> + v.G + <span class='code_string'>","</span> + v.B;
<span class='span_code_line'></span> <span class='code_note'>//return base.GetStringFromValue();</span>
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_note'>//当此属性在属性窗口中被确定位置时候发生</span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnSetItemLocation() {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnSetItemLocation();
<span class='span_code_line'></span> <span class='code_class'>Rectangle</span> rect = <span class='code_key'>base</span>.RectangleR;
<span class='span_code_line'></span> m_rect = <span class='code_key'>new</span> <span class='code_class'>Rectangle</span>(rect.Right - 25, rect.Top + 5, 19, 12);
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_note'>//绘制属性窗口值区域时候调用</span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnDrawValueRectangle(<span class='code_class'>DrawingTools</span> dt) {
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnDrawValueRectangle(dt);<span class='code_note'>//先采用默认的绘制 再绘制颜色预览</span>
<span class='span_code_line'></span> dt.SolidBrush.<span class='code_class'>Color</span> = (<span class='code_class'>Color</span>)<span class='code_key'>this</span>.GetValue(<span class='code_key'>null</span>);
<span class='span_code_line'></span> dt.<span class='code_class'>Graphics</span>.FillRectangle(dt.SolidBrush, m_rect);<span class='code_note'>//填充颜色</span>
<span class='span_code_line'></span> dt.<span class='code_class'>Graphics</span>.DrawRectangle(<span class='code_class'>Pens</span>.Black, m_rect); <span class='code_note'>//绘制边框</span>
<span class='span_code_line'></span> }
<span class='span_code_line'></span>
<span class='span_code_line'></span> <span class='code_key'>protected</span> <span class='code_key'>override</span> <span class='code_key'>void</span> OnMouseClick(<span class='code_class'>MouseEventArgs</span> e) {
<span class='span_code_line'></span> <span class='code_note'>//如果用户点击在 颜色预览区域 则弹出系统颜色对话框</span>
<span class='span_code_line'></span> <span class='code_key'>if</span> (m_rect.Contains(e.Location)) {
<span class='span_code_line'></span> ColorDialog cd = <span class='code_key'>new</span> ColorDialog();
<span class='span_code_line'></span> <span class='code_key'>if</span> (cd.ShowDialog() != DialogResult.OK) <span class='code_key'>return</span>;
<span class='span_code_line'></span> <span class='code_key'>this</span>.SetValue(cd.<span class='code_class'>Color</span>, <span class='code_key'>null</span>);
<span class='span_code_line'></span> <span class='code_key'>this</span>.Invalidate();
<span class='span_code_line'></span> <span class='code_key'>return</span>;
<span class='span_code_line'></span> }
<span class='span_code_line'></span> <span class='code_note'>//否则其他区域将采用默认处理方式 弹出字符串输入框</span>
<span class='span_code_line'></span> <span class='code_key'>base</span>.OnMouseClick(e);
<span class='span_code_line'></span> }
<span class='span_code_line'></span>}</pre>
</div>
<img width=462 src='./images/tu_colornode_4.png'/>
<p>此时可以看到与上一个案例相比 多了一个颜色预览区域 并且鼠标点击预览区域会弹出系统颜色对话框对属性值进行设置 如果在非预览区域点击 则会使用默认操作方式 手动输入ARGB</p>
<p class='p_hightlight'>关于<span class='span_mark'>STNodePropertyDescriptor</span>还有两个重要虚函数<span class='span_mark'>GetBytesFromValue()</span><span class='span_mark'>SetValue(byte[])</span>将会在后面<span class='span_mark'>保存画布</span>中进行介绍</p>
<h1 class='h_title anchor_point' anchor='b_i'>STNodeTreeView</h1>
<p><span class='span_mark'>STNodeTreeView</span>与<span class='span_mark'>STNodePropertyGrid</span>一样 是随着类库一起发布的另一个控件 可以与<span class='span_mark'>STNodeEditor</span>结合使用</p>
<img src='./images/stnodetreeview.gif'/>
<p><span class='span_mark'>STNodeTreeView</span>中的节点可直接拖拽进<span class='span_mark'>STNodeEditor</span>中 并且提供预览和检索功能</p>