开源纯C#工控网关+组态软件(九)定制Visual Studio

一、   引子

因为最近很忙(lan),很久没发博了。不少朋友对那个右键弹出菜单和连线的功能很感兴趣,因为VS本身是不包含这种功能的。

640?wx_fmt=png&wxfrom=5&wx_lazy=1 640?wx_fmt=png

 大家想这是什么鬼,怎么我的设计器没有,其实这是一个微软黑科技,如果用好,VS可以打造为你专用的神兵利器。

为什么我要扩展Visual Studio的界面设计器?当时我在设计组态软件的时候面临最大的困难大概就是设计器了。一套成熟的组态设计器包括:界面设计器(包括工具栏、设计器、属性管理器)、脚本编辑器(各种语法高亮、语法检查、自动完成等等等等)、编译(解释)器、调试器、解决方案管理器(如何组织项目、导入/导出文件、添加资源、添加引用等等等等),说出来吓死人,这些功能绝对不是我这类单兵作战人员能搞定的。那是微软、西门子这种级别的巨型公司以按人年计算的成本完成的。也曾经想过套用网上开源设计器,搜了半天,得出一个结论:网上的都是一些简单的DEMO或者原型设计,和我想实现的目标还差的太远,完善的好东西一般是不会开源的。

但是仔细想一下我上面列举的功能,不就是Visual Studio现成的功能吗?放着这个宇宙第一IDE不用,想自己重新造轮子,估计写到老都没有什么结果。于是我想能不能通过扩展VS,去实现一些组态软件的特殊要求功能,比如常用的变量组态编辑器、连线这类的功能?万能的谷歌让我找到了我想要的技术: WPF(含Blend) 设计器扩展。

二、   什么是WPF设计器扩展

WPF设计器,常规的界面就是 工具栏+XAML编辑器+界面设计器。界面设计器包括右键编辑菜单、设计器装饰(如锚点进行缩放、旋转),属性编辑器等。这些功能已经很强大,完善了;但考虑到用户的特殊需求,VS提供    了强大的扩展功能,参考https://msdn.microsoft.com/zh-cn/library/windows/desktop/bb675306(v=vs.90).aspx 的介绍:

WPF 设计器基于一个具有可扩展的体系结构的框架,用户可以扩展这种框架以创建自己的自定义设计体验。

通过扩展 WPF 设计器对象模型,可以在很大程度上自定义 WPF 内容的设计时外观和行为。例如,可以通过下列方式扩展 WPF 设计器:

  • 利用增强的图形自定义移动并调整标志符号的大小。

  • 向设计图面添加一个标志符号,在鼠标移动时该标志符号可以使所选控件倾斜。

  • 在不同工具之间修改控件的设计时外观和行为。

  • WPF 设计器 体系结构支持 WPF 的所有表现力。这样便可以创建很多以前不可能拥有的可视化设计体验。

也就是说,WPF设计器扩展提供了一套API,可以自定义装饰器(如点选控件出现的旋转、拖放、拉伸、定位锚点)、右键菜单(如编辑、排序、对齐、剪切)、属性编辑器,并控制它们的行为;甚至可以改变设计器的外观。是不是很强大?然而这一黑科技很少人知道,而且为了实现设计器扩展,你必须严格遵守一些特殊的规则,而且设计器扩展的调试方式也很特殊。同时,在WPF设计器的扩展基本可以不修改就移植到Blend。

三、   如何实现设计器扩展

  • API总体架构

 640?wx_fmt=png

VS的状态分为设计时和运行时。设计时就是你打开VS,拖拽控件,界面布局,属性设置,代码编写,打交道的对象是Visual Studio;运行时就是你编译运行自己的exe文件。

WPF的界面设计器,其核心目标就是对控件(Contorl)的控制,包括对控件的拖放、旋转、移动、属性编辑等。而在设计时如果要操作控件,首先要在设计、编辑过程中通过一些API“发现”要操作的控件,并使其能与VS设计器互动。API这里使用了一个 “提供者模式”来实现:对装饰器、菜单、属性编辑器等的操作功能,提供了相应的Provider来实现,如装饰器的AdornerProvider,右键菜单的ContextMenuProvider 等。所有的Provider都遵循这样的场景:当你做了一个“选择”的动作(比如拖动一个控件旋转-对应AdornerProvider的Active事件;或点了某个右键菜单-对应ContextMenuProvider的Execute事件),进而通过动作事件的PrimarySelection参数获取相对应的ModelItem-控件在设计时的“马甲”,进而通过ModelItem的GetCurrentValue方法找到你选择的对象。大家也许会问,设计器扩展为何要多此一举的对控件加一层外壳ModelItem,直接操作控件不就行了吗?回答是,你对控件的设计时操作,例如对控件的激活,使之成为设计器选中的控件,这一行为在控件本身并没有定义;而设计器也要通过自己“理解”的上下文才能与控件交互。ModelItem将用户对控件的操作反馈给设计器,或者将设计的动作告知用户,起了关键的中介作用。而设计器本身的“马甲”是DesignerView,可以通过这个类获取设计器当前设置,如当前界面大小、缩放比例等。

  • 如何实现

要实现一个完整的设计器扩展,要经历以下过程:

定义元数据,设计器需要知道哪些控件具有哪些扩展。这是通过Metadata 类来实现的:Metadata 类有一个AttributeTable方法,在其中构建了控件和功能(即相应的Provider)的映射关系。

using Microsoft.Windows.Design.Features;

using Microsoft.Windows.Design.Metadata;

 

 

[assembly: ProvideMetadata(typeof(HMIControl.VisualStudio.Design.Metadata))]

namespace HMIControl.VisualStudio.Design

{

    internal class Metadata : IProvideAttributeTable

    {

        // Accessed by the designer to register any design-time metadata.

        public AttributeTable AttributeTable

        {

            get

            {

                AttributeTableBuilder builder = new AttributeTableBuilder();

                //InitializeAttributes(builder);

                // Add the adorner provider to the design-time metadata.

                builder.AddCustomAttributes(

                    typeof(LinkableControl),

                    new FeatureAttribute(typeof(ControlAdornerProvider))

                    //new FeatureAttribute(typeof(TagComplexContextMenuProvider))

                    );

                builder.AddCustomAttributes(

                  typeof(HMIControlBase),

                  //new FeatureAttribute(typeof(LinkLineAdornerProvider)),

                  new FeatureAttribute(typeof(TagComplexContextMenuProvider)));

                builder.AddCustomAttributes(

                    typeof(LinkLine),

                    new FeatureAttribute(typeof(LinkLineAdornerProvider)),

                    new FeatureAttribute(typeof(TagComplexContextMenuProvider)));

                builder.AddCustomAttributes(

                    typeof(ButtonBase),

                    new FeatureAttribute(typeof(TagWriterContextMenuProvider)));

                builder.AddCustomAttributes(

                  typeof(HMIButton),

                  new FeatureAttribute(typeof(TagWindowContextMenuProvider)),

                  new FeatureAttribute(typeof(TagComplexContextMenuProvider)),

                  new FeatureAttribute(typeof(TagWriterContextMenuProvider)));

                builder.AddCustomAttributes(

                  typeof(FromTo),

                  new FeatureAttribute(typeof(TagWindowContextMenuProvider)));

                return builder.CreateTable();

            }

        }

    }

}

定义具体的Provider,所有的Provider都执行如下次序:根据用户选择,找到相关控件,并进行操作,将操作结果反馈给设计器。

根据设计器扩展的默认规则,在正确的位置使用正确的命名方式,否则你的扩展不会出现在设计器。这些默认规则包括:

命名空间规则:将设计器扩展项目的命名空间设置为HMIControl.VisualStudio.Design(HMIControl即控件库的命名空间),以便设计器能够发现元数据。

项目路径规则:将项目的输出路径设置为“..\HMIControl\bin\”(HMIControl即控件库的项目路径)。 使控件的程序集与元数据程序集位于同一文件夹中,从而可为设计器启用元数据发现。

  • 如何调试

一段不能加断点调试的代码会给编写者带来很大困扰。但设计器扩展有一个特殊性:没法在运行时加断点。好在微软早就为我们安排好了一切。具体可参考https://msdn.microsoft.com/zh-cn/sqlserver/bb514636

即调试时需要更改项目的属性,设置启动程序为VS的可执行文件: devenv.exe.相当于再打开一个新的VS作为运行时。调试时打开你的设计器操作,会发现第一个打开的VS中已经命中断点了。

 640?wx_fmt=png

 

四、   组态定制需求的实现

根据组态软件的特殊需求,有两个重要功能是通过WPF设计器扩展实现的:控件连线和右键弹出表达式编辑器,具体代码在LinkableControlDesign项目中。

  • 界面连线的实现

设计目标:实现两个HMI控件的连线。每个控件最多有上下左右四个位置(即锚点,也可以少于四个甚至没有),连线从A控件任一位置引出,自动寻找路径,连到B控件的任一位置;路径不能穿越其他控件,而应自动绕开。连线均为直线,不能为圆弧线或斜线;在控件位置改变时,连线重新计算并绘制。

设计过程:具有锚点的控件均继承LinkableControl类。锚点装饰器类为ControlAdorner,是一个控件容器,包含上下左右四个锚点,每个锚点由PinAdorner 定义,包含锚点的外形、自动生成路径等功能。路径发现由PathFinder类实现。与设计器交互通过继承AdornerProvider 类实现。

运行过程:通过AdornerProvider 类的Activate事件,获取当前点击(激活)的控件并转换为LinkableControl,并找到控件的父容器Panel、控件的装饰器ControlAdorner及其包含的每个PinAdorner、设计器包装DesignerView。在每个PinAdorner的鼠标点击和拖放事件内,可探索到其他控件的锚点、规划路径、生成连线LinkLine。

同时要考虑设计器进行缩放时路径的变化,在DesignerView的ZoomLevelChanged事件中处理。

  • 右键菜单的实现

设计目标:组态软件一般都有自己的变量表达式编辑器,用来实现对界面控件的动画效果。如果要求设计者手工输入表达式,容易出错,也没有语法检查,很麻烦。但VS并没有提供这个功能,因此我想到了点选控件,弹出的右键菜单加上一个编辑项。这就要用到ContextMenuProvider的功能。

设计过程:TagComplexContextMenuProvider 继承了ContextMenuProvider,如果菜单“ComplexEditor”被激活,触发Exeute事件,则弹出窗体TagComplexEditor,以设置控件的动画关联的变量表达式;操作结果将写回控件的TagReadText 属性。

  • 未来改进

编辑器改进:支持命令自动完成、语法高亮、更完善的语法检查。。

快捷键编辑:目前的右键弹出编辑器菜单方式操作还可以进一步改进为快捷键方式。但似乎WPF扩展没有提供快捷键弹出的API,期待进一步完善。


相关文章: 

github地址:https://github.com/GavinYellow/SharpSCADA。QQ群:102486275

原文地址:http://www.cnblogs.com/evilcat/p/8859223.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321613.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P2805-[NOI2009]植物大战僵尸【网络流,最大权闭合图】

正题 题目链接:https://www.luogu.com.cn/problem/P2805 题目大意 n∗mn*mn∗m的格子,攻击这个格子(x,y)(x,y)(x,y)可以获得价值cx,yc_{x,y}cx,y​,攻击一个格子(x,y)(x,y)(x,y)前要攻击(x,y1)(x,y1)(x,y1)。 对于有的格子(x,y)(x,y)(x,y)会保护些格子…

19、mysql中定时器的创建和使用

mysql中可以使用定时器,用来进行计划的调度,在mysql中定时器通过事件的形式存在,接下来介绍一下定时器的使用 创建定时器 CREATE EVENT IF NOT EXISTS 计划名-- 计划频率和开启计划时间或者是计划执行的时间-- 前一个可以实现持续的计划调度…

使用C#实现适配器模式 (Adapter Pattern) 和外观模式 (Facade Pattern)

本文的概念内容来自深入浅出设计模式一书现实世界中的适配器(模式)我带着一个国标插头的笔记本电脑, 来到欧洲, 想插入到欧洲标准的墙壁插座里面, 就需要用中间这个电源适配器.面向对象的适配器你有个老系统, 现在来了个新供应商的类, 但是它们的接口不同, 如何使用这个新供应商…

jzoj3852-单词接龙【0/1分数规划,负环】

正题 题目链接:https://jzoj.net/senior/#main/show/3852 题目大意 nnn个单词串,头尾有两个相同单词就可以连在一起,求一个最长的环使得平均单词长度最长。 解题思路 其实总共26∗2626*2626∗26个点,然后求一个回路使得平均边长最长 就是0…

20、mysql中触发器的使用

是什么 触发器不同于定时器,触发器用在表中的记录上,每当表中的记录进行增、删、改操作时,则绑定在表上的对应的触发器被触发,进行对应的操作。(有点类似于java中的swing中的监听器) 怎么用 可以表中每一…

在 .NET Core 中使用 DiagnosticSource 记录跟踪信息

前言最新一直在忙着项目上的事情,很久没有写博客了,在这里对关注我的粉丝们说声抱歉,后面我可能更多的分享我们在微服务落地的过程中的一些经验。那么今天给大家讲一下在 .NET Core 2 中引入的全新 DiagnosticSource 事件机制,为什…

jzoj3854-分组【树状数组,线段树】

正题 题目链接:https://jzoj.net/senior/#contest/show/2990/2 题目大意 一个小队满足要求 队长的地位最高所有队员和队长的年龄差不超过kkk 给出nnn个人的地位和年龄,qqq个询问 每次询问一组(x,y)(x,y)(x,y)求若(x,y)(x,y)(x,y)在同一个队里那这个队的最多人数…

.NET:持续进化的统一开发平台

标题使用的是进化这个词语,是因为 .NET 在不断的努力,也在不断的重构。这篇文章的更多目的和意义在于科普,俗称“传教”。持续进化的 .NET上图即是一个学习的路线图同样他也是 .NET 平台的进化图。也是代表着 未来.NET的发展方向。今天的故事…

21、mysql修改密码的方法总结

修改mysql服务的连接密码其原理在于修改mysql服务自带mysql数据库下user表中的数据,下边三种修改方式和使用场景不同,不过最终的原理一致,前两种需要连接mysql服务之后修改,第三种方式无需连接服务即可修改。 1.强制修改root密码…

jzoj3853-帮助Bsny【dp】

正题 题目链接:https://jzoj.net/senior/#main/show/3853 题目大意 nnn个数字,每次可以将一个数字拿出并插入任意位置,操作kkk次求最少段连续相同的数。 解题思路 先将相同的缩在一起 设fi,j,s,zf_{i,j,s,z}fi,j,s,z​表示到第iii个,拿出了…

linux下jdk的安装和配置

1.将jdk的rpm包上传到服务器 2.使用rpm命令安装jdk rpm -ivh ***.rpm 3.配置环境变量 cd /usr/java目录下查看是否有jdk安装后的目录(一般都在这个目录下) vi /etc/profile,将以下代码添加到文件中 export JAVA_HOME/usr/java/jdk1.8.0_…

容器化的 DevOps 工作流

对于 devops 来说,容器技术绝对是我们笑傲江湖的法宝。本文通过一个小 demo 来介绍如何使用容器技术来改进我们的 devops 工作流。devops 的日常工作中难免会有一些繁琐的重复性劳动。比如管理 Azure 上的各种资源,我们会使用 Azure CLI 工具。同时我们也…

Summer Training day6 coseforces339D 线段树、位操作

D. Xenia and Bit Operationstime limit per test2 secondsmemory limit per test256 megabytesinputstandard inputoutputstandard outputXenia the beginner programmer has a sequence a, consisting of 2n non-negative integers: a1, a2, ..., a2n. Xenia is currently…

U102380-简单数据结构题【Trie】

前言 %%%\%\%\%%%%北大爷的题目 正题 题目链接:https://www.luogu.com.cn/problem/U102380 题目大意 nnn个数,求一个数kkk使得max{aixork}max\{a_i\ xor\ k\}max{ai​ xor k}最小。 解题思路 我们对每一个数按位建到一个TrieTrieTrie里,然后对于每个节…

linux下redis的安装和配置

以下介绍的是使用源码包的方式安装redis 1.创建安装目录 cd /usr/local mkdir redis 2.上传包到此目录下,并解压 tar -zxvf ****.tar.gz 3.使用make命令进行redis安装 cd到解压后的目录下,执行如下命令 编译命令:make,如果执行make命…

.NET Core 2.1 Preview 2发布 - April 10, 2018

我们今天宣布发布 .NET Core 2.1 Preview 2。这也是我们在接下来的两到三个月内接近最终发布的版本,该版本现已准备好进行广泛的测试。我们希望您有任何反馈意见。ASP.NET Core 2.1 Preview 2和Entity Framework 2.1 Preview 2也在今天发布。您可以在Windows&#x…

UOJ#244-[UER#7]短路【贪心】

正题 题目链接:http://uoj.ac/problem/244 题目大意 n1n1n1个矩阵如下图所示 每一层的格子有相同的延时,现在加一些平行于坐标轴的导线,求左上到右下的最小延迟。 解题思路 可以知道最优解一点是走到某个矩阵的左上角然后走这个矩阵到右下角然后到终点…

使用 C#/.NET Core 实现单体设计模式

本文的概念内容来自深入浅出设计模式一书由于我在给公司做内培, 所以最近天天写设计模式的文章....单体模式 Singleton单体模式的目标就是只创建一个实例.实际中有很多种对象我们可能只需要它们的一个实例, 例如: 线程池,缓存, 弹出的对话框, 用于保存设置的类, 用于logging的类…

linux下离线安装gcc

有时服务无法连接网络,此时可以使用源码包的方式安装gcc工具 1.上传安装包并解压 创建目录 cd /home/software/ mkdir gcc_bak 上传到此目录下 tar -zxvf gcc_rpm.tar.gz 2.安装gcc 进入到解压后的目录,执行命令 rpm -Uvh *.rpm --nodeps --…

P3462-[POI2007]ODW-Weights【贪心】

正题 题目链接:https://www.luogu.com.cn/problem/P3462 题目大意 nnn个容器容量不同,mmm个物品,对于一两个物品i,ji,ji,j,若wi≤wjw_i\leq w_jwi​≤wj​那么有wi∣wjw_i|w_jwi​∣wj​。 求能够放下的最多物品。 解题思路 显然就是一个进…