當前位置:編程學習大全網 - 編程語言 - 如何用C#寫所見即所得的設計器

如何用C#寫所見即所得的設計器

隨著計算機信息系統不斷深入發展,其系統結構要求越來越靈活,這種靈活性就是表現為程序的高度可配置性,可能應用程序的工作流程可以隨便改變,用戶界面也可以隨便改變,面對這種不斷增強的靈活,是不可能通過修改程序代碼來實現的,應用系統本身需要發生深刻變化,需要實現很強的擴展性和靈活性。此外z專門用於修改系統配置的外圍工具也是非常重要的。這些系統外圍定制工具很大壹部分就是壹些所見即所得的設計器。比如工作流編制工具,WinForm或WebForm界面設計器,而報表設計器也是典型的外圍定制工具。

總所周知,所見即所得的設計器是個相當復雜的程序,首先它需要復雜的圖形化用戶界面編程,包括圖形的繪制,鼠標鍵盤事件的處理,還要抗屏幕閃爍。其次還有它後臺的數據維護處理,包括用戶界面和數據的同步,數據的組織安排,以及加載和保存文檔的處理。而且這些處理過程可以算是糾纏在壹起,需要非常認真小心的分析設計,仔細編碼。

本文就是探討如何實現壹個所見即所得的設計器。 關於本文,可以參考作者的另壹篇文章-如何使用C#編寫文本編輯器。

設計器類型

設計器按照用戶界面和使用體驗,可以分為兩種模式,壹種是基於直角坐標方式,另壹種是基於流式排版方式。微軟的Visio就是典型的直角坐標方式,而Word則是流式排版方式,而VS.NET的WebForm窗體設計器就是這兩者的結合。

在直角坐標方式的設計器中,設計元素是使用XY坐標來在設計視圖中定位的,對於矩形元素壹般指定它的左上角的位置來定位,設計者需要指定設計元素的位置,有時還要設置它的大小。對於線段需要指定兩個端點的XY坐標。設計者只要設置好了各個元素的位置大小就完成了設計文檔的基本結構,剩下的就是設置各個元素各自的內容了。

在流式排版設計器中,設計元素是不需要指定位置的,是根據壹般根據從左到右,從上到下的排列原則填充到設計視圖中(但有時會變成其他排列原則)。設計元素的位置是動態計算的。流式設計器可能還要使用鍵盤直接輸入文本,需要顯示光標。流式排版設計器可以看作文字處理器。

這兩種設計器用戶界面和使用體驗不壹樣,因此其程序處理的方式也不壹樣,直角坐標設計器存在設計元素間相互覆蓋,這影響繪圖,此外還需要大量的鼠標拖拽操作,需要認真處理鼠標事件,但鍵盤事件處理得不多。而流式排版設計器中元素不會相互覆蓋,因此繪制起來方便點,鼠標事件處理不多,但鍵盤事件處理的多,此外還需要處理光標。但這兩種設計器它的文檔對象模型有比較大的類似性。

在本文中,以下只討論直角坐標方式的設計器。

設計器的功能

個人認為壹個設計器應當實現的功能有

設計文檔的加載和保存,設計器可以將當前設計的內容保存到壹個文檔中,這個文檔可以保存到文件中,也可保存到數據庫或某個服務器中。設計器可以加載文檔來完全重現上次的設計結果。

設計器可以快速準確的繪制文檔視圖,當視圖大小超過設計區域時,用戶界面應當出現滾動條來進行滾動操作。

當前有互換式設計體驗,用戶可以使用鼠標拖拽操作來改變元素的位置大小等布局設置,用戶改變了元素的布局或某些屬性時,必須立即更新文檔視圖,而且更新區域應當盡量小。

支持所見即所得的設計體驗,當設計器需要進行圖形輸出,例如輸出圖片或打印時,用戶在設計器中的設計視圖應當和輸出的圖形保持壹致。

盡量減少屏幕閃爍。這需要繪制圖形或更新視圖時需要進行優化,盡快完成繪制操作。

若設計器需要進行擴展時,設計器應當提供足夠的擴展能力,開發人員可以在這個設計器的基礎上添加新的特性,使得設計器能顯示新樣式的文檔視圖。並且加載和保存文檔時也能處理新的文檔結構。

若需要可以支持VBA腳本,用戶可以編寫VBA腳本來控制設計器,包括其設計的文檔內容。

文檔對象模型

對於計算機程序,後臺決定前臺,而設計器的後臺就是文檔對象模型。相信大家對文檔對象模型有所了解,我們在WEB頁面中使用JAVASCRIPT腳本時就是訪問了HTML文檔對象模型,我們操作XML文檔就是訪問XML文檔對象模型。

W3C國際組織對文檔對象模型是這樣定義的(摘自 http://www.w3.org/DOM/ )

The Document Object Model is a platform- and language-neutral interface that will allow programs and scripts to dynamically access and update the content, structure and style of documents. The document can be further processed and the results of that processing can be incorporated back into the presented page. This is an overview of DOM-related materials here at W3C and around the web.

以我個人的英文水平翻譯如下

文檔對象模型是壹種語言中立的接口或平臺,程序或腳本能利用它來訪問和更新結構化的文檔。這些文檔可以被進壹步的處理,處理結果可以組成壹個有效頁面。這是W3C對web上的對文檔對象模型原理的壹般看法。

我個人認為,對於編程,文檔對象模型其主要內容就是,面對比較復雜的文檔,使用面向對象的編程思想,使用壹個個程序世界中的對象來映射文檔中的每壹個特定的部分。加載文檔時,可以解析文檔,並把其表示的內容映射為壹個個對象,此時應用程序可以修改這些對象的數據,當保存文檔時,可以將這些對象數據組織起來按照特定的格式保存到文檔中。這樣程序就通過訪問文檔對象來訪問文檔,也可以修改文檔對象來修改文檔,如此實現了對復雜文檔的處理。文檔對象模型是處理復雜文檔的標準操作模式。

設計器處理的是復雜的文檔,因此也需要使用文檔對象模型。文檔對象模型可分為三大部分:文檔基本元素,文檔對象和各種類型的從文檔基本元素派生出的文檔元素。

文檔基本元素是整個文檔對象模型的最基礎的對象(就像Object類型是.NET對象集團的基礎壹樣),它定義了文檔元素的通用接口,壹般定義為抽象類,類型名稱可以為DesignElement 。

文檔對象是文檔對象模型的頂級對象,它包含了整個文檔的內容,其類型名稱可以為 DesignDocument 。

各種類型的文檔元素,它是派生自文檔基本元素類型,用於描述文檔中各種實際存在的元素。其中可以定義壹種文檔元素,它們可以容納其他的文檔元素,這些元素就是容器元素。實際上文檔對象就是最大的容器元素。由於文檔對象模型中存在容器元素,因此所有的對象都組成壹個樹狀結構,稱為文檔對象樹,其中根節點就是文檔對象。各種文檔元素是文檔對象模型的活躍分子,擴展文檔對象模型大部分工作就是擴展這些文檔元素,擴展文檔元素需要擴展它們的兩個功能,壹個是文檔的加載和保存,壹個就是文檔本身保存的數據。

文檔對象模型可以和用戶界面相關,也可以不相關,例如XML文檔對象模型是無用戶界面的。設計器的文檔對象模型是和用戶界面相關的,對此,擴展設計文檔對象模型的文檔元素時還需要擴展它們的繪制圖形的能力以便設計器能繪制新型的文檔元素圖形。

對於設計文檔對象模型,其文檔基礎元素可以定義的內容有三個方面,文檔的加載和保存,用戶界面相關的接口,維護文檔對象樹的接口。

文檔的加載和保存

設計文檔可以保存為二進制文檔,純文本文檔和其他格式,在此推薦使用XML文檔格式。其好處是

設計文檔對象模型和XML文檔對象模型都屬於文檔對象模型,兩者原理和結構上都有著很大的相似性,設計文檔元素和XML文檔元素可以存在壹壹對應的關系。因此使用XML文檔加載和保存設計文檔對象是很自然的,實現起來比較簡單。

XML文檔是國際標準的文檔格式,非常開方,其他應用程序很容易利用設計器生成的文件,簡化了設計器和其他應用系統的數據接口。

已經存在標準的XML文檔解析器和XML文檔對象模型,因此不需自己處理XML文檔,只需調用標準庫加載XML文檔對象模型,然後按照壹壹對應的關系來生成設計文檔對象模型。

使用XML文檔有利於保持設計器的各個版本間的兼容性。只要XML文檔結構不發生大變化,低版本的設計器可以加載高版本的設計器生成的文檔,同樣高版本的設計器也很容易加載低版本的設計器生成的文檔。若使用二進制文件格式,則設計器需要編寫對於不同版本的設計文檔的預處理器,比較麻煩而且很難做到向上兼容。

在保存對象數據到XML文檔時,保存方式有兩種,保存到XML屬性和保存到XML元素。當指定某個XML元素用於保存對象數據時,若使用保存到XML屬性時,會對對象每壹個屬性,將其數據保存到指定名稱的XML屬性中,而保存到XML元素時,會在當前的XML節點下新增壹個指定名稱的XML子元素。然後將屬性值保存到XML子元素中。這兩種方式生成的XML片斷為

<element attributename1="value1" attributename2="value2" />

<element >

<attributename1>value1</attributename1>

<attributename2>value2</attributename2>

</element>

面對這兩種方式,我建議選擇第二種,其原因有

若保存到XML屬性,則當對象屬性比較多是,使用縮進方式輸出的XML文檔將比較寬,在查看是會出現橫向滾動條,不利於閱讀。而保存到XML元素時,XML文檔不會很寬,便於閱讀。

若多行文本保存到XML屬性,則壹般不會以多行文本的方式保存,不利於閱讀。而保存到XML元素時,則保存的文本和實際的文本比較接近,便於閱讀。

若保存到XML屬性,則保存方式只能是壹個屬性字符串,而保存到XML元素時則保存的方式很容易進行擴展。

雖然保存到XML屬性方式生成的XML文檔比保存為XML元素的方式要小,但XML文檔格式的設計目標是方便保存數據和交換數據,而不在乎文檔是否冗余,因此我們選擇保存方式時不必在乎XML文檔的大小。而且壹般設計文檔的內容不很多,以目前計算機硬件條件無須在意XML文檔大小。

當設計器從XML文檔加載設計文檔時, 首先生成XML文檔對象樹, 然後根據壹壹對應的關系來生成設計文檔對象樹,此時需要從XML元素保存的信息來判斷該XML元素是對應於那種設計文檔元素,設計器可以從XML元素名稱來判斷,也可以從某個XML屬性來判斷,在此我使用XML元素名稱來判斷,首先是針對壹個XML元素,獲得其名稱比獲得某個屬性值要方便,其次是XML名稱是必然存在的,肯定不為空,而XML屬性則可能由於某種原因而缺失,XML名稱比XML屬性要穩定。

基於上述的認識,當采用XML文檔作為保存方式時,設計基礎元素需要定義兩個虛函數,壹個用於從XML文檔加載對象屬性數據,另壹個要向XML文檔保存對象數據。而其他文檔元素對象則根據需要重載這兩個函數來實現自己的加載和保存對象屬性的操作,對於容器元素,還需要保存子元素數據到XML文檔和從XML文檔加載子元素。當然在實際應用中還要根據需要定義壹些輔助成員來幫助加載和保存XML文檔。

設計器生成的XML文檔壹般保存為文件形式,當然可以根據需要來保存的數據庫裏或者上傳到各種服務器中。若直接保存到數據庫中,則整個應用系統中所有的設計器編輯的都是同壹個文檔版本,而且壹旦保存便可立即應用。

用戶界面相關的接口

設計器需要繪制文檔視圖,則需要設計文檔對象模型提供支持。因此文檔基本元素需要定義兩類通用接口,壹個是和繪制文檔相關的接口,壹個是處理鼠標鍵盤事件相關的接口。

繪制文檔相關接口

大部分文檔元素需要在文檔視圖中繪制內容,因此它們需要重載繪制文檔的接口,這類接口主要有兩個函數,壹個是計算元素大小的函數,壹般命名為 RefreshSize , 壹個是繪制元素的函數,壹般命名為RefreshView。

壹般設計者指定元素的大小,元素本身不需要計算其大小,但某些元素可能是根據其內容自動設置大小,因此需要重載計算元素大小的函數RefreshSize來自動設置大小。自動設置大小可能只是設置元素的寬度或高度,也可能是同時設置其寬度和高度。同壹個元素,可能在壹種狀態下不會自動設置大小,而在另外壹種狀態下需要自動設置大小。所有的這些操作都需要在RefreshSize函數中完成。

壹般的設計元素都需要在文檔視圖中繪制內容,這時需要重載RefreshView函數,這個函數參數包含了壹個System.Drawing.Graphics對象,元素需要使用這個Graphics對象來繪制自己特定的內容,可能是繪制文本,圖片或其他圖形。

當所有的文檔元素都實現了繪制文檔相當的接口,則在設計器的調度下,壹個完整的設計文檔視圖就繪制出來了。而擴展設計器時,若需要指定新顯示樣式的元素時,需要重載RefreshView和RefreshSize函數來實現新的顯示樣式,此時擴展的設計器就能顯示新樣式的文檔視圖。

處理鼠標鍵盤事件相關接口

設計器中主要處理鼠標事件,文檔基礎元素可以定義壹些處理鼠標事件的虛函數,名稱可以為 HandleMouseDown , HandleMouseMove 和 HandleMouseUp 。

為了方便文檔元素處理鼠標坐標,設計器在調用文檔元素的HandleMouse函數時,首先將鼠標光標坐標進行轉換,要將鼠標光標在視圖區域中的坐標轉換為文檔元素內部的相對坐標,即相對於元素左上角的相對坐標。

設計器要依靠鼠標事件來實現設計元素的拖拽操作以實現互換式設計體驗。關於鼠標拖拽操作典型的應用就是使用8個控制點來編輯元素邊界。當壹個元素邊界是矩形時,會在元素的邊界矩形的四個角和四個邊的中點上分布8個控制點,當鼠標移動到這8個點時會修改鼠標光標樣式,當鼠標光標在某個控制點上時,用戶按下鼠標按鍵則開始進行鼠標拖拽操作,拖拽時會顯示壹個虛線繪制的邊框,當松開鼠標按鍵則拖拽操作結束,此時設計器修改拖拽的元素的矩形邊界。

某些文檔元素並不進行標準的鼠標拖拽操作,例如對於容器元素,其內部的鼠標拖拽不移動對象而是畫出壹個選擇矩形來選擇若幹個子對象;對於表格元素,它的表格線上的鼠標拖拽操作是修改表格行的高度和表格列的寬度;而對於線段則是修改端點位置。

當用戶不小心按下鼠標按鍵,或只是選擇某個元素而並不想進行鼠標拖拽操作,此時可以使用壹個參數 System.Windows.Forms.SystemInformation.DragSize 來判斷是否進行鼠標拖拽。當鼠標按鍵按下時,設計器就鎖定鼠標,若鼠標按鍵按下後鼠標移動距離超出了 DragSize 的範圍時,則表示用戶是想進行鼠標拖拽操作的,此時開始真正的鼠標拖拽操作。若鼠標按鍵從按下到松開時鼠標移動距離始終沒超出 DragSize 的範圍,則表示用戶沒有進行鼠標拖拽操作的意圖。這樣的判斷可以讓設計器容忍用戶的壹些誤操作。

設計器還要處理鼠標雙擊事件處理,對於某些包含文本的元素,用戶雙擊該元素,則在設計視圖中顯示個文本輸入框來直接編輯對象的文本內容。可以定義壹個接口 ILabelEditable , 當用戶雙擊某個元素,設計器發現該元素實現了 ILabelEditable 接口,則在設計視圖中動態的顯示壹個文本輸入框,然後調用該接口的成員來直接編輯對象文本內容。

維護文檔對象樹

文檔基礎元素要定義不少接口來用於維護文檔對象樹。要定義 OwnerDocument 屬性來指定元素所在的文檔對象,要定義 Parent 屬性來指明元素的父節點,定義 Items 屬性來指明該元素的子元素列表。對於容器元素,還要維護它的子元素列表。

設計文檔對象作為文檔樹的根節點,擔負著維護整個對象樹的重任,包括文檔整體的加載保存,文檔整體的繪制,遍歷整個對象樹結構入口,還要為腳本提供接口。它是訪問文檔對象樹的入口點。

壹些比較基礎的文檔元素類型

設計視圖控件

設計視圖控件是設計器在用戶界面上的展示接口。它是壹個標準的Windows控件,該控件派生自System.Windows.Form.UserControl。用戶使用鼠標和鍵盤在這個控件裏面編輯文檔,它重載了OnMouseDown , OnMouseMove 和 OnMouseUp 成員,對鼠標消息進行了壹下包裝後供設計文檔對象使用。重載了OnPaint 成員來更新文檔視圖。重載了 OnDoubleClick 來進行試圖直接編輯文檔元素的文本內容。

當用戶設置某個元素為當前元素,則設計視圖控件將根據需要來進行滾動以便當前元素出現在可視區域中。若當前元素大小小於可視區域大小,則處理比較簡單,只要根據可視區域大小和元素在視圖中的位置就可計算滾動位置。若元素寬度或高度大於可視區域的寬度和高度,則需要進行額外的判斷,以避免滾動時發生跳躍。

本文是XDesigner軟件工作室撰寫

  • 上一篇:我有壹篇玩具作文,500字9篇。
  • 下一篇:考研需要計算機二級證嗎?
  • copyright 2024編程學習大全網