開源純C#工控網關+組態軟件(九)定製Visual Studio

 1、   引子html

由於最近很忙(lan),好久沒發博了。很多朋友對那個右鍵彈出菜單和連線的功能很感興趣,由於VS自己是不包含這種功能的。git

 

 你們想這是什麼鬼,怎麼個人設計器沒有,其實這是一個微軟黑科技,若是用好,VS能夠打造爲你專用的神兵利器。github

爲何我要擴展Visual Studio的界面設計器?當時我在設計組態軟件的時候面臨最大的困難大概就是設計器了。一套成熟的組態設計器包括:界面設計器(包括工具欄、設計器、屬性管理器)、腳本編輯器(各類語法高亮、語法檢查、自動完成等等等等)、編譯(解釋)調試器解決方案管理器(如何組織項目、導入/導出文件、添加資源、添加引用等等等等),說出來嚇死人,這些功能絕對不是我這類單兵做戰人員能搞定的。那是微軟、西門子這種級別的巨型公司以按人年計算的成本完成的。也曾經想過套用網上開源設計器,搜了半天,得出一個結論:網上的都是一些簡單的DEMO或者原型設計,和我想實現的目標還差的太遠,完善的好東西通常是不會開源的。sql

可是仔細想一下我上面列舉的功能,不就是Visual Studio現成的功能嗎?放着這個宇宙第一IDE不用,想本身從新造輪子,估計寫到老都沒有什麼結果。因而我想能不能經過擴展VS,去實現一些組態軟件的特殊要求功能,好比經常使用的變量組態編輯器、連線這類的功能?萬能的谷歌讓我找到了我想要的技術: WPF(含Blend) 設計器擴展。windows

2、   什麼是WPF設計器擴展架構

WPF設計器,常規的界面就是 工具欄+XAML編輯器+界面設計器。界面設計器包括右鍵編輯菜單、設計器裝飾(如錨點進行縮放、旋轉),屬性編輯器等。這些功能已經很強大,完善了;但考慮到用戶的特殊需求,VS提供了強大的擴展功能,參考https://msdn.microsoft.com/zh-cn/library/windows/desktop/bb675306(v=vs.90).aspx 的介紹:框架

WPF 設計器基於一個具備可擴展的體系結構的框架,用戶能夠擴展這種框架以建立本身的自定義設計體驗。編輯器

經過擴展 WPF 設計器對象模型,能夠在很大程度上自定義 WPF 內容的設計時外觀和行爲。例如,能夠經過下列方式擴展 WPF 設計器:ide

  • 利用加強的圖形自定義移動並調整標誌符號的大小。
  • 向設計圖面添加一個標誌符號,在鼠標移動時該標誌符號可使所選控件傾斜。
  • 在不一樣工具之間修改控件的設計時外觀和行爲。
  • WPF 設計器 體系結構支持 WPF 的全部表現力。這樣即可以建立不少之前不可能擁有的可視化設計體驗。

也就是說,WPF設計器擴展提供了一套API,能夠自定義裝飾器(如點選控件出現的旋轉、拖放、拉伸、定位錨點)、右鍵菜單(如編輯、排序、對齊、剪切)、屬性編輯器,並控制它們的行爲;甚至能夠改變設計器的外觀。是否是很強大?然而這一黑科技不多人知道,並且爲了實現設計器擴展,你必須嚴格遵照一些特殊的規則,並且設計器擴展的調試方式也很特殊。同時,在WPF設計器的擴展基本能夠不修改就移植到Blend。工具

3、   如何實現設計器擴展

  • API整體架構

 

VS的狀態分爲設計時運行時。設計時就是你打開VS,拖拽控件,界面佈局,屬性設置,代碼編寫,打交道的對象是Visual Studio;運行時就是你編譯運行本身的exe文件。

WPF的界面設計器,其核心目標就是對控件(Control)的控制,包括對控件的拖放、旋轉、移動、屬性編輯等。而在設計時若是要操做控件,首先要在設計、編輯過程當中經過一些API「發現」要操做的控件,並使其能與VS設計器互動。API這裏使用了一個 「提供者模式」來實現:對裝飾器、菜單、屬性編輯器等的操做功能,提供了相應的Provider來實現,如裝飾器的AdornerProvider,右鍵菜單的ContextMenuProvider 等。全部的Provider都遵循這樣的場景:當你作了一個「選擇」的動做(好比拖動一個控件旋轉-對應AdornerProviderActive事件;或點了某個右鍵菜單-對應ContextMenuProviderExecute事件),進而經過動做事件的PrimarySelection參數獲取相對應的ModelItem-控件在設計時的「馬甲」,進而經過ModelItemGetCurrentValue方法找到你選擇的對象。你們也許會問,設計器擴展爲什麼要畫蛇添足的對控件加一層外殼ModelItem,直接操做控件不就好了嗎?回答是,你對控件的設計時操做,例如對控件的激活,使之成爲設計器選中的控件,這一行爲在控件自己並無定義;而設計器也要經過本身「理解」的上下文才能與控件交互。ModelItem將用戶對控件的操做反饋給設計器,或者將設計的動做告知用戶,起了關鍵的中介做用。而設計器自己的「馬甲」是DesignerView,能夠經過這個類獲取設計器當前設置,如當前界面大小、縮放比例等。

  • 如何實現

要實現一個完整的設計器擴展,要經歷如下過程:

定義元數據,設計器須要知道哪些控件具備哪些擴展。這是經過Metadata 類來實現的:Metadata 類有一個AttributeTable屬性,在其中構建了控件和功能(即相應的Provider)的映射關係。

  1. 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中已經命中斷點了。

 

 

4、   組態定製需求的實現

根據組態軟件的特殊需求,有兩個重要功能是經過WPF設計器擴展實現的:控件連線和右鍵彈出表達式編輯器,具體代碼在LinkableControlDesign項目中。

  • 界面連線的實現

設計目標:實現兩個HMI控件的連線。每一個控件最多有上下左右四個位置(即錨點,也能夠少於四個甚至沒有),連線從A控件任一位置引出,自動尋找路徑,連到B控件的任一位置;路徑不能穿越其餘控件,而應自動繞開。連線均爲直線,不能爲圓弧線或斜線;在控件位置改變時,連線從新計算並繪製。

設計過程:具備錨點的控件均繼承LinkableControl類。錨點裝飾器類爲ControlAdorner,是一個控件容器,包含上下左右四個錨點,每一個錨點由PinAdorner 定義,包含錨點的外形、自動生成路徑等功能。路徑發現由PathFinder類實現。與設計器交互經過繼承AdornerProvider 類實現。

運行過程:經過AdornerProvider 類的Activate事件,獲取當前點擊(激活)的控件並轉換爲LinkableControl,並找到控件的父容器Panel、控件的裝飾器ControlAdorner及其包含的每一個PinAdorner、設計器包裝DesignerView。在每一個PinAdorner的鼠標點擊和拖放事件內,可探索到其餘控件的錨點、規劃路徑、生成連線LinkLine

同時要考慮設計器進行縮放時路徑的變化,在DesignerViewZoomLevelChanged事件中處理。

  • 右鍵菜單的實現

設計目標:組態軟件通常都有本身的變量表達式編輯器,用來實現對界面控件的動畫效果。若是要求設計者手工輸入表達式,容易出錯,也沒有語法檢查,很麻煩。但VS並無提供這個功能,所以我想到了點選控件,彈出的右鍵菜單加上一個編輯項。這就要用到ContextMenuProvider的功能。

設計過程TagComplexContextMenuProvider 繼承了ContextMenuProvider,若是菜單「ComplexEditor」被激活,觸發Exeute事件,則彈出窗體TagComplexEditor,以設置控件的動畫關聯的變量表達式;操做結果將寫回控件的TagReadText 屬性。

  • 將來改進

編輯器改進:支持命令自動完成、語法高亮、更完善的語法檢查。。

快捷鍵編輯:目前的右鍵彈出編輯器菜單方式操做還能夠進一步改進爲快捷鍵方式。但彷佛WPF擴展沒有提供快捷鍵彈出的API,期待進一步完善。

5、   下面的計劃

寫一系列帖子,把架構、原理講清楚。大體以下:

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

相關文章
相關標籤/搜索