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


 一、   引子

因為最近很忙(lan),很久沒發博了。不少朋友對那個右鍵彈出菜單和連線的功能很感興趣,因為VS本身是不包含這種功能的。

 

 大家想這是什么鬼,怎么我的設計器沒有,其實這是一個微軟黑科技,如果用好,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總體架構

 

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

 

 

四、   組態定制需求的實現

根據組態軟件的特殊需求,有兩個重要功能是通過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,期待進一步完善。

五、   下面的計划

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM