开源纯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,一经查实,立即删除!

相关文章

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

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

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

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

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

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

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

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

容器化的 DevOps 工作流

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

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的类…

用C#(.NET Core) 实现简单工厂和工厂方法设计模式

本文源自深入浅出设计模式. 只不过我是使用C#/.NET Core实现的例子.前言当你看见new这个关键字的时候, 就应该想到它是具体的实现.这就是一个具体的类, 为了更灵活, 我们应该使用的是接口(interface).有时候, 你可能会写出这样的代码:这里有多个具体的类被实例化了, 是根据不同…

TypeScript 2.8引入条件类型

最新发布的TypeScript 2.8包含了若干主要特性和一些问题修复,其中最为重要的是新增了条件类型,开发人员可以根据其他类型的特征为变量选择适当的类型。条件类型最适合与泛型组合在一起使用。如果一个框架总是重复相同的选择性代码,就会变得很…

用分布式缓存提升ASP.NET Core性能

得益于纯净、轻量化并且跨平台支持的特性,ASP.NET Core作为热门Web应用开发框架,其高性能传输和负载均衡的支持已广受青睐。实际上,10-20台Web服务器还是轻松驾驭的。有了多服务器负载的支持,使得Web应用层在业务增长时随时采用水…

123记住密码设置

1、打开123所在安装目录 2、进入config目录 3、找到.ovpn文件 4、找到auth-user-pass,没有的话在最后填写即可 在.ovpn同级目录下创建一个.txt文件,然后在这里设置一下 例如:auth-user-pass pass.txt 5、创建.txt文件,填写用户名…

Visual Studio 2017 15.7预览版发布

Visual Studio 2017已经发布一年多了,微软一直持续定期推出更新。第7个预览版也已发布,这一版本继续带来大量的改进。首先是增加了对TypeScript 2.8的支持。TypeScript 2.8带来了条件类型、JSX Pragma和映射类型标识符的可控性。VS2017的其他改进还包括在…

2018年4月更新70多个公司dnc招聘职位

2018年4月更新70多个公司dnc招聘职位请在本页回复,补充dnc招聘信息、公司案例dnc简介dnc .NET Core、dotnet Core简写dnc是微软新一代主力编程平台,开源、免费、跨平台、轻量级、高性能,可部署到Linux、Docker、k8s等环境,适合开…

把旧系统迁移到.Net Core 2.0 日记(1) - Startup.cs 解析

因为自己到开发电脑转到Mac Air,之前的Webform/MVC应用在Mac 跑不起来,而且.Net Core 2.0 已经比较稳定了。1. 为什么会有跨平台的.Net Core 近年来,我们已经进入云计算时代,在云平台的PaSS和SaSS上也是发生了大幅度的进化&#x…

ASP.NET Core 2.0 : 图说管道,唐僧扫塔的故事

本文通过一张GIF动图来继续聊一下ASP.NET Core的请求处理管道,从管道的配置、构建以及请求处理流程等方面做一下详细的研究。(ASP.NET Core系列目录)一、概述上文说到,请求是经过 Server监听>处理成httpContext>Application…

ASP.NET MVC应用迁移到ASP.NET Core及其异同简介

ASP.NET Core是微软新推出支持跨平台、高性能、开源的开发框架,相比起原有的ASP.NET来说,ASP.NET Core更适合开发现代应用程序,如跨平台、Dorker的支持、集成现代前端开发框架(如npm、bower、gulp等等)。另外相比ASP.NET它的性能更好&#xf…

使用 C# (.NET Core) 实现模板方法模式 (Template Method Pattern)

本文的概念内容来自深入浅出设计模式一书.项目需求有一家咖啡店, 供应咖啡和茶, 它们的工序如下:咖啡:茶:可以看到咖啡和茶的制作工序是差不多的, 都是有4步, 其中有两步它们两个是一样的, 另外两步虽然具体内容不一样, 但是都做做的同一类工作.现在问题也有了, 当前的设计两个…

.NET Core Community 首个千星项目诞生:CAP

项目简介在我们构建 SOA 或者 微服务系统的过程中,我们通常需要使用事件来对各个服务进行集成,在这过程中简单的使用消息队列并不能保证数据的最终一致性, CAP 采用的是和当前数据库集成的本地消息表的方案来解决在分布式系统互相调用的各个环…