程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> UI前沿技術: 不局限於網格

UI前沿技術: 不局限於網格

編輯:關於.NET

畫布是 Windows Presentation Foundation (WPF) 和 Silverlight 中提供的若干布局選項之一,它是其中與傳統布局傳承最為緊密的。在使用子元素填充畫布時,通過使用 Canvas.Left 和 Canvas.Top 附加屬性指定坐標可以定位每個子元素。此模式與其他面板有顯著區別,其他面板是基於簡單的算法來布置子元素的,無需程序員指出實際位置。

聽到“畫布”一詞時,您可能會想到繪圖和繪畫。因此,使用 WPF 和 Silverlight 的程序員可能傾向於將畫布歸類為用於顯示矢量圖形。但是,當使用畫布顯示線、折線、多邊形和路徑元素時,這些元素本身就包含用於在畫布中定位自身的坐標點。因此,不需要使用 Canvas.Left 和 Canvas.Top 附加屬性。

如果不需要畫布提供的附加屬性,那麼為什麼還要使用畫布呢?是否有更好的方法?

畫布與網格

這些年來,我越來越不願意使用畫布顯示矢量圖形,更傾向於使用單個單元格網格。單個單元格網格就像常規網格一樣,只是沒有定義任何行或列而已。如果網格只有一個單元格,則可以將多個元素放入該網格單元格中,無需使用網格的任何附加屬性來指定行或列。

最初,使用畫布或單個單元格網格看上去非常類似。無論對矢量圖形使用哪種布局方式,線、折線、多邊形和路徑元素都基於其坐標點並相對於容器的左上角確定其位置。

畫布與單個單元格網格的區別在於容器向布局系統的其余部分的顯示方式。WPF 和 Silverlight 合並了一個雙步的自上而下的布局,其中每個元素會詢問其子元素的大小,然後負責相對於自身來組織其子元素。在此布局系統中,畫布與單個單元格網格有很大區別:

對於子元素,網格具有的維度與其父元素的維度相同。這些維度通常是有限維度,但畫布始終向其子元素顯示具有無限維度。

網格向其父元素報告其子元素的總大小。而畫布始終有一個明顯的零大小,與其包含的子元素無關。

假設有一組多邊形元素構成某種類似於卡通的矢量圖像。如果將這些多邊形元素全部放入單個單元格網絡,則該網格的大小將由多邊形的最大橫坐標和縱坐標決定。然後可以將網格視為布局系統中具有有限大小的普通元素,因為其大小正確反映了組合圖像的大小。(實際上,只有圖像的左上角位於坐標點 (0, 0) 並且沒有負坐標時才會正確顯示。)

但是,如果將這些多邊形全部放入畫布,畫布會向布局系統報告其大小為零。通常,在將一個組合矢量圖像集成到應用程序時,幾乎可以肯定的是您希望看到單個單元格網格的行為而不是畫布的行為。

那麼畫布是否毫無用處?完全不是。竅門在於巧妙利用畫布的特性。實際上,畫布不參與布局。因此,您可以在需要超越布局時使用畫布,從而顯示超出布局系統邊界的圖形以及在布局系統邊界外浮動的圖形。默認情況下,畫布不修剪其子元素,因此,即使畫布非常小,仍可以在其邊界外部容納子對象。畫布更像是一個顯示元素或圖形的參考點,而不是一個容器。

我認為畫布是一種“突破框架思考”的優秀技術。盡管我將在 Silverlight 中顯示代碼示例,但您仍可以在 WPF 中使用這一技術。本文附帶的可下載源代碼是一個名為 ThinkingOutsideTheGrid 的 Visual Studio 解決方案,您可以在 charlespetzold.com/silverlight/ThinkingOutsideTheGrid 運行該實例。

控件的可視化鏈接

假設在 Silverlight 或 WPF 應用程序中有一組控件,並且需要在兩個或多個控件之間提供某種可視化鏈接。也許您希望從一個控件到另一個控件繪制一條線,並且這條線可能會穿過這兩個控件之間的其他控件。

當然,這條線必須對布局中的變化予以反應,例如當用戶調整窗口或頁面時。能夠在布局更新時得到通知是 LayoutUpdated 事件的很好應用,在研究本文描述的問題之前我還從來沒有使用過該事件。LayoutUpdated 在 WPF 中由 UIElement 定義,在 Silverlight 中由 FrameworkElement 定義。顧名思義,當布局過程在屏幕上重新布置元素之後將觸發該事件。

在處理 LayoutUpdated 事件時,您不希望執行會導致另一布局過程的操作,從而陷入無窮遞歸操作之中。這時畫布就派上了用場:由於畫布始終向其父元素報告零大小,因此可以更改畫布中的元素而不影響布局。

ConnectTheElements 程序的 XAML 文件結構如下所示:

<UserControl ... >
  <Grid ... >
   <local:SimpleUniformGrid ... >
    <Button ... />
    <Button ... />
    ...
   </local:SimpleUniformGrid>

   <Canvas>
    <Path ... />
    <Path ... />
    <Path ... />
   </Canvas>
  </Grid>
</UserControl>

網格包含一個計算行數和列數的 SimpleUniformGrid,以便基於網格總大小和長寬比顯示其子元素。當更改窗口的大小時,行數和列數將發生變化,並且單元格也會隨之改變。在此 SimpleUniformGrid 的 32 個按鈕中,其中有兩個按鈕名為 btnA 和 btnB。畫布占據的區域與 SimpleUniformGrid 相同,但位於它的上面。此畫布包含路徑元素,程序使用這些元素圍繞這兩個指定按鈕繪制橢圓,並在這兩個橢圓之間繪制一條線。

在 LayoutUpdated 事件中,代碼隱藏文件執行其所有操作。該文件需要找到這兩個指定按鈕相對於畫布的位置,畫布還可以方便地與 SimpleUniformGrid、Grid 和 MainPage 本身對齊。

要在同一可視化樹中查找任一元素相對於某個其他元素的位置,請使用 TransformToVisual 方法。此方法在 WPF 中由 Visual 類定義,在 Silverlight 中由 UIElement 定義,但它在這兩個環境中的工作方式是相同的。假設元素 el1 位於 el2 所占區域中的某個位置。(在 ConnectTheElements 中,el1 是一個 Button,el2 是一個 MainPage。)以下方法調用將返回一個類型為 GeneralTransform 的對象,該對象是所有其他圖形轉換類的抽象父類:

el1.TransformToVisual(el2)

除調用 GeneralTransform 的 Transform 方法(該方法可以將一個點從一個坐標空間轉變為另一坐標空間)外,您不能使用它執行任何實際操作。

假設您希望在 el2 的坐標空間中查找 el1 的中心。代碼如下:

Point el1Center = new Point( 
  el1.ActualWidth / 2, el1.ActualHeight / 2);
Point centerInEl2 = 
  el1.TransformToVisual(el2).Transform(el1Center);

如果 el2 是畫布或與畫布對齊,則可以使用該 centerInEl2 點在畫布中設置一個圖形,使該圖形似乎在 el1 的中心。

ConnectTheElements 在其 WrapEllipseAroundElement 方法中執行此轉換,圍繞這兩個指定按鈕繪制橢圓,然後基於按鈕中心之間連線的交點計算兩個橢圓之間連線的坐標。結果如圖 1 所示。

圖 1 ConnectTheElements 顯示

如果在 WPF 中嘗試此程序,請將 SimpleUniformGrid 更改為 WrapPanel,以便在調整程序窗口時能夠在布局中更動態地進行更改。

跟蹤滑塊

更改圖形和其他畫面以便響應滾動條或滑塊的更改非常簡單,在 WPF 和 Silverlight 中,您可以使用代碼或 XAML 綁定來執行此操作。但是,如果希望將圖形與實際滑動的滑塊精確吻合該怎麼辦?

這是 TriangleAngles 項目背後的理念,我將其構思為一種交互式三角演示。我安排了兩個滑塊,一個垂直滑塊和一個水平滑塊,二者相交為直角。這兩個滑塊定義直角三角形的兩個頂點,如圖 2 所示。

圖 2 TriangleAngles 顯示

注意半透明的三角形在兩個滑塊頂部的放置方式。當移動滑塊時,三角形的邊將改變大小和比例,如三角形的內接角和兩個直角邊的標簽所示。

這顯然是畫布覆蓋的另一任務,但增加了復雜性,這是因為該程序需要訪問滑塊。該滑塊是控制模板的一部分:滑塊在模板中分配了名稱,但遺憾的是,您不能在模板外部訪問這些名稱。

這時,我們通常使用基本的 VisualTreeHelper 靜態類來解決這一問題。使用此類,您可以通過 GetParent、GetChildenCount 和 GetChild 方法浏覽(確切地說是攀爬)任何可視化樹。為了推廣定位特定類型的子元素這一過程,我編寫了一個小的遞歸泛型方法:

T FindChild<T>(DependencyObject parent)
  where T : DependencyObject

我按如下方式調用了該方法:

Thumb vertThumb = FindChild<Thumb>(vertSlider);
Thumb horzThumb = FindChild<Thumb>(horzSlider);

此時,我可以在兩個滑塊上使用 TransformToVisual 獲取它們相對於畫布覆蓋的坐標。

現在,該代碼段對一個滑塊起作用,但對另一個滑塊無效,我想了想才知道滑塊的控制模板包含兩個 滑塊:一個用於水平方向,另一個用於垂直方向。根據滑塊的方向設置,一半模板將其 Visibility 屬性設置為 Collapsed。我向 FindChild 方法添加了另一個名為 mustBeVisible 的參數,並使用該參數放棄向下搜索所有元素不可視的子分支。

在構成三角形的多邊形上將 HitTestVisible 設置為 false,可以防止它干擾鼠標向滑塊的輸入。

在 ItemsControl 外滾動

假設您在使用帶 DataTemplate 的 ItemsControl 或 ListBox 在控件的集合中顯示對象。您是否可以在該 DataTemplate 中包括畫布,以便有關特定項目的信息可以顯示在控件外部,但在滾動控件時好像是在跟蹤該項目?

我還沒有找到精確執行此操作的好辦法。最大的問題似乎是 ScrollViewer 限定的剪切區域。此 ScrollViewer 會剪切偶然顯示在其外部的任何畫布,然後該畫布上的所有內容也都被剪切。

不過,如果更深入地了解 ItemsControl 內部工作方式後,執行操作的結果就會更接近您想要的效果。

我將此功能視為彈出功能,它與 ItemsControl 中某個項目相關,但實際上超出 ItemsControl 本身的范圍。ItemsControlPopouts 項目演示了該技術。為了提供一些內容供 ItemsControl 顯示,我創建了一個名為 ProduceItems.xml 的小數據庫,該數據庫位於 ClientBin 的 Data 子目錄中。ProduceItems 由許多標簽名為 ProduceItem 的元素構成,其中每個元素包含一個 Name 屬性、一個引用項目位圖的 Photo 屬性和一個可選的 Message(該消息超出 ItemsControl 之外顯示)。(照片和其他藝術品是 Microsoft Office 剪貼畫。)

ProduceItem 和 ProduceItems 類為 XML 文件提供代碼支持,ProduceItemsPresenter 讀取 XML 文件並將其反序列化為 ProduceItems 對象。該對象被設置為包含 ScrollViewer 和 ItemsControl 的可視化樹的 DataContext 屬性。ItemsControl 包含一個用於顯示項目的簡單 DataTemplate。

現在,您可能會發現一些問題。該程序有效地將類型 ProduceItem 的業務對象插入 ItemsControl。在內部,ItemsControl 將基於 DataTemplate 為每個項目構建一個可視化樹。要跟蹤這些項目的移動,您需要訪問該項目的內部可視化樹,以了解項目與程序的其余部分具體在何處關聯。

現已提供此信息。ItemsControl 定義一個名為 ItemContainerGenerator 的只讀屬性,它返回一個類型為 ItemContainerGenerator 的對象。該類負責生成與 ItemsControl 中每個項目關聯的可視化樹,並且包含一些便利的方法,例如為控件中的每個項目提供容器(實際上是 ContentPresenter)的 ContainerFromItem。

與其他兩個程序類似,ItemsControlPopouts 程序覆蓋帶有畫布的整個頁面。LayoutUpdated 事件允許該程序再次檢查畫布中的項目是否需要更改。此程序中的 LayoutUpdated 處理程序枚舉 ItemsControl 中的 ProduceItem 對象,並檢查非 Null 和非空的 Message 屬性。這些 Message 屬性中的每個屬性都應與畫布中類型為 PopOut 的一個對象對應。PopOut 只是從 ContentControl 派生的一個小類,並使用模板顯示一行和一個消息文本。如果 PopOut 不存在,則創建該類並將其添加到畫布。如果存在,則只需重用該類。

然後必須在畫布中定位該 PopOut。該程序獲取與數據對象對應的容器,並相對於畫布轉換其位置。如果該位置在 ScrollViewer 的頂部和底部之間,則 PopOut 的 Visibility 屬性設置為 Visible。否則,PopOut 將隱藏。

斷開單元格

顯然,WPF 和 Silverlight 在布局中提供了很大的便利。網格和其他面板將元素整齊地放入單元格中,並確保它們所在的位置正確。如果您隨後假定這種便利是對自由放置元素的必要限制,將是很遺憾的事。

下載代碼示例:http://code.msdn.microsoft.com/mag201005UIFrontiers

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved