WPF 圖形轉譯概觀

本主題提供 WPF 視覺層的概觀。 其著重于 類別的角色 Visual ,以在 WPF 模型中轉譯支援。

視覺物件的角色

類別 Visual 是每個 FrameworkElement 物件衍生自的基本抽象概念。 它也可做為在 WPF 中撰寫新控制項的進入點,而且在 Win32 應用程式模型中可視為視窗控點 (HWND)。

物件 Visual 是核心 WPF 物件,其主要角色是提供轉譯支援。 使用者介面控制項,例如 ButtonTextBox ,衍生自 Visual 類別,並使用它來保存其轉譯資料。 物件 Visual 支援:

  • 輸出顯示︰轉譯視覺物件已保存、序列化的繪圖內容。

  • 轉換︰執行視覺物件的轉換。

  • 裁剪:提供視覺物件的裁剪區域支援。

  • 點擊測試︰判斷座標或幾何是否包含於視覺物件的範圍內。

  • 週框方塊計算︰判斷視覺物件的週框矩形。

不過, Visual 物件不包含對非轉譯功能的支援,例如:

  • 事件處理

  • 版面配置

  • 樣式

  • 資料繫結

  • 全球化

Visual 公開為必須衍生子類別的公用抽象類別。 下圖顯示 WPF 中公開之視覺物件的階層。

Diagram of classes derived from the Visual object

DrawingVisual 類別

DrawingVisual是輕量型繪圖類別,用來轉譯圖形、影像或文字。 此類別之所以被視為輕量型,是因為它不提供版面配置或事件處理,而這會改善其執行階段效能。 基於此原因,繪圖適合背景或美工圖案。 DrawingVisual可用來建立自訂視覺效果物件。 如需詳細資訊,請參閱使用 DrawingVisual 物件

Viewport3DVisual 類別

Viewport3DVisual提供 2D VisualVisual3D 物件之間的橋接器。 類別 Visual3D 是所有 3D 視覺元素的基類。 Viewport3DVisual需要您定義 Camera 值和 Viewport 值。 相機可讓您檢視場景。 檢視區會建立投影到 2D 平面上的對應位置。 如需 WPF 中 3D 的詳細資訊,請參閱 3D 圖形概觀

ContainerVisual 類別

類別 ContainerVisual 會當做 物件集合的 Visual 容器使用。 類別 DrawingVisual 衍生自 ContainerVisual 類別,允許它包含視覺物件的集合。

視覺物件中的繪圖內容

Visual物件會將其轉譯資料儲存為 向量圖形指令清單 。 指示清單中的每個項目代表一組低階圖形資料和相關聯的序列化格式的資源。 可包含繪圖內容的轉譯資料有四種不同的類型。

繪圖內容類型 描述
向量圖形 表示向量圖形資料,以及任何相關聯的 BrushPen 資訊。
映像 表示 所定義區域內的 Rect 影像。
圖像 表示呈現 的繪圖, GlyphRun 這個繪圖是來自指定字型資源的字元序列。 這是文字的表現方式。
影片 代表轉譯視訊的繪圖。

DrawingContext可讓您使用視覺內容填入 Visual 。 當您使用 DrawingContext 物件的繪製命令時,實際上會儲存圖形系統稍後將使用的一組轉譯資料;您不會即時繪製到螢幕。

當您建立 WPF 控制項,例如 Button 時,控制項會隱含地產生繪製本身的轉譯資料。 例如,設定 ContentButton 屬性會導致 控制項儲存圖像的轉譯表示。

Visual描述其內容為 包含在 內的 DrawingGroup 一或多個 Drawing 物件。 DrawingGroup也描述套用至其內容的不透明度遮罩、轉換、點陣圖效果和其他作業。 DrawingGroup當呈現內容時,作業會依下列順序套用: OpacityMask 、、 OpacityClipGeometryBitmapEffectGuidelineSet 、 和 。 Transform

下圖顯示轉譯順序期間套用作業的順序 DrawingGroup

DrawingGroup order of operations
DrawingGroup 作業的順序

如需詳細資訊,請參閱繪製物件概觀

視覺圖層的繪圖內容

您永遠不會直接具現化 DrawingContext ;不過,您可以從某些方法取得繪圖內容,例如 DrawingGroup.OpenDrawingVisual.RenderOpen 。 下列範例會從 DrawingVisual 擷取 DrawingContext ,並使用它繪製矩形。

// Create a DrawingVisual that contains a rectangle.
private DrawingVisual CreateDrawingVisualRectangle()
{
    DrawingVisual drawingVisual = new DrawingVisual();

    // Retrieve the DrawingContext in order to create new drawing content.
    DrawingContext drawingContext = drawingVisual.RenderOpen();

    // Create a rectangle and draw it in the DrawingContext.
    Rect rect = new Rect(new System.Windows.Point(160, 100), new System.Windows.Size(320, 80));
    drawingContext.DrawRectangle(System.Windows.Media.Brushes.LightBlue, (System.Windows.Media.Pen)null, rect);

    // Persist the drawing content.
    drawingContext.Close();

    return drawingVisual;
}
' Create a DrawingVisual that contains a rectangle.
Private Function CreateDrawingVisualRectangle() As DrawingVisual
    Dim drawingVisual As New DrawingVisual()

    ' Retrieve the DrawingContext in order to create new drawing content.
    Dim drawingContext As DrawingContext = drawingVisual.RenderOpen()

    ' Create a rectangle and draw it in the DrawingContext.
    Dim rect As New Rect(New Point(160, 100), New Size(320, 80))
    drawingContext.DrawRectangle(Brushes.LightBlue, CType(Nothing, Pen), rect)

    ' Persist the drawing content.
    drawingContext.Close()

    Return drawingVisual
End Function

列舉視覺圖層的繪圖內容

除了其他用途之外, Drawing 物件也會提供物件模型來列舉 的內容 Visual

注意

當您列舉視覺效果的內容時,您會擷 Drawing 取物件,而不是轉譯資料的基礎標記法做為向量圖形指令清單。

下列範例會 GetDrawing 使用 方法來擷 DrawingGroup 取 的值 Visual 並加以列舉。

public void RetrieveDrawing(Visual v)
{
    DrawingGroup drawingGroup = VisualTreeHelper.GetDrawing(v);
    EnumDrawingGroup(drawingGroup);
}

// Enumerate the drawings in the DrawingGroup.
public void EnumDrawingGroup(DrawingGroup drawingGroup)
{
    DrawingCollection dc = drawingGroup.Children;

    // Enumerate the drawings in the DrawingCollection.
    foreach (Drawing drawing in dc)
    {
        // If the drawing is a DrawingGroup, call the function recursively.
        if (drawing is DrawingGroup group)
        {
            EnumDrawingGroup(group);
        }
        else if (drawing is GeometryDrawing)
        {
            // Perform action based on drawing type.
        }
        else if (drawing is ImageDrawing)
        {
            // Perform action based on drawing type.
        }
        else if (drawing is GlyphRunDrawing)
        {
            // Perform action based on drawing type.
        }
        else if (drawing is VideoDrawing)
        {
            // Perform action based on drawing type.
        }
    }
}

視覺物件如何用來建置控制項

WPF 中的許多物件是由其他視覺物件所組成,這表示它們可以包含不同子代物件的階層。 WPF 中的許多使用者介面元素,例如控制項,是由多個視覺物件所組成,代表不同類型的轉譯專案。 例如, Button 控制項可以包含一些其他物件,包括 ClassicBorderDecoratorContentPresenterTextBlock

下列程式碼顯示 Button 標記中定義的控制項。

<Button Click="OnClick">OK</Button>

如果您要列舉組成預設 Button 控制項的視覺物件,您會找到下圖所示的視覺物件階層:

Diagram of visual tree hierarchy

控制項 Button 包含 ClassicBorderDecorator 元素,接著會 ContentPresenter 包含 元素。 元素 ClassicBorderDecorator 負責繪製框線和 背景。 Button 專案 ContentPresenter 負責顯示 的內容 Button 。 在此情況下,因為您正在顯示文字,因此 ContentPresenter 元素會 TextBlock 包含 元素。 控制項使用 的事實 Button 表示內容可以由其他專案表示,例如 Image 或 幾何,例如 EllipseGeometryContentPresenter

控制項範本

將控制項擴充至控制項階層的索引鍵是 ControlTemplate 。 控制項範本會指定控制項的預設視覺階層。 當您明確地參考控制項時,您會以隱含方式參考其視覺階層。 您可以覆寫控制項範本的預設值,以針對控制項建立自訂視覺外觀。 例如,您可以修改控制項的背景色彩值,使其使用線性漸層色彩值 Button ,而不是純色值。 如需詳細資訊,請參閱按鈕樣式和範本

使用者介面專案,例如 控制項,包含數個 Button 向量圖形指令清單,描述控制項的整個轉譯定義。 下列程式碼顯示 Button 標記中定義的控制項。

<Button Click="OnClick">
  <Image Source="images\greenlight.jpg"></Image>
</Button>

如果您要列舉組成 Button 控制項的視覺物件和向量圖形指令清單,您會發現下列物件的階層:

Diagram of visual tree and rendering data

控制項 Button 包含 ClassicBorderDecorator 元素,接著會 ContentPresenter 包含 元素。 元素 ClassicBorderDecorator 負責繪製構成按鈕框線和背景的所有離散圖形元素。 專案 ContentPresenter 負責顯示 的內容 Button 。 在此情況下,因為您正在顯示影像,因此 ContentPresenter 元素會 Image 包含 元素。

關於視覺物件的階層和向量圖形指示清單,有幾點需要注意:

  • 階層中的順序代表繪製資訊的轉譯順序。 子元素會由左至右、由上而下從根視覺元素周遊。 如果某元素具有視覺化子元素,則會在元素的同層級元素之前周遊這些子元素。

  • 階層中的非分葉節點元素,例如 ContentPresenter ,是用來包含子項目,它們不包含指令清單。

  • 如果視覺元素同時包含向量圖形指示清單與視覺子元素,父系視覺元素中的指示清單就會在任何視覺子元素物件中的繪圖之前轉譯。

  • 向量圖形指示清單中的項目會由左至右轉譯。

視覺化樹狀結構

視覺化樹狀結構包含應用程式使用者介面中所使用的所有視覺元素。 由於視覺元素包含持續性繪圖資訊,您可以將視覺化樹狀結構想像為場景圖形,其中包含將輸出撰寫至顯示裝置所需的所有轉譯資訊。 此樹狀結構是直接由應用程式所建立 (不論是以程式碼或標記) 的所有視覺元素累積而成。 視覺化樹狀結構也包含由元素 (例如控制項和資料物件) 的範本展開所建立的所有視覺元素。

下列程式碼顯示 StackPanel 標記中定義的專案。

<StackPanel>
  <Label>User name:</Label>
  <TextBox />
  <Button Click="OnClick">OK</Button>
</StackPanel>

如果您要列舉標記範例中組成 StackPanel 元素的視覺物件,您會找到下圖所示的視覺物件階層:

Diagram of visual tree hierarchy of a StackPanel control.

轉譯順序

視覺化樹狀結構會決定 WPF 視覺效果和繪圖物件的轉譯順序。 周遊順序會從根視覺物件開始,這是視覺化樹狀結構的最上層節點。 然後,會由左至右周遊根視覺物件的子系。 如果視覺物件具有子系,則會在該視覺物件的同層級項目之前周遊其子系。 這表示子視覺物件的內容會在視覺物件本身的內容前面轉譯。

Diagram of the visual tree rendering order

根視覺物件

「根視覺物件」是視覺化樹狀結構階層中最上層的元素。 在大部分的應用程式中,根視覺效果的基類為 WindowNavigationWindow 。 不過,如果您已在 Win32 應用程式中裝載視覺物件,根視覺物件是您在 Win32 視窗中裝載的最上層視覺物件。 如需詳細資訊,請參閱教學課程︰在 Win32 應用程式中裝載視覺物件

邏輯樹狀結構的關聯性

WPF 中的邏輯樹狀結構代表應用程式在執行時間的專案。 雖然您不會直接操作此樹狀結構,但應用程式的這個檢視適合用來了解屬性繼承和事件路由。 不同于視覺化樹狀結構,邏輯樹狀結構可以代表非視覺資料物件,例如 ListItem 。 在許多案例中,邏輯樹狀結構會非常密切地對應至應用程式的標記定義。 下列程式碼顯示 DockPanel 標記中定義的專案。

<DockPanel>
  <ListBox>
    <ListBoxItem>Dog</ListBoxItem>
    <ListBoxItem>Cat</ListBoxItem>
    <ListBoxItem>Fish</ListBoxItem>
  </ListBox>
  <Button Click="OnClick">OK</Button>
</DockPanel>

如果您要列舉在標記範例中組成 DockPanel 元素的邏輯物件,您會發現邏輯物件的階層如下所示:

Tree diagram
邏輯樹狀結構的圖表

視覺化樹狀結構和邏輯樹狀結構會與目前的應用程式元素集合同步處理,以反映元素的任何新增、刪除或修改。 不過,樹狀結構會呈現不同的應用程式檢視。 不同于視覺化樹狀結構,邏輯樹狀結構不會展開控制項的 ContentPresenter 元素。 這表示針對相同的物件集合,邏輯樹狀結構和視覺化樹狀結構之間沒有直接的一對一對應。 事實上, 使用與參數相同的元素叫用 LogicalTreeHelper 物件的 GetChildren 方法和 VisualTreeHelper 物件的 GetChild 方法 會產生不同的結果。

如需邏輯樹狀結構的詳細資訊,請參閱 WPF 中的樹狀結構

使用 XamlPad 檢視視覺化樹狀結構

WPF 工具 XamlPad 提供檢視和探索對應至目前定義 XAML 內容之視覺化樹狀結構的選項。 按一下功能表列上的 [顯示視覺化樹狀結構] 按鈕以顯示視覺化樹狀結構。 下列說明 XAML 內容擴充至 XamlPad 的 [視覺化樹狀結構總管] 面板中的視覺化樹 狀結構節點:

Visual Tree Explorer panel in XamlPad

請注意 、 TextBox 和 控制項如何在 Label XamlPad 的 [視覺化樹狀 結構總管] 面板中顯示個別的 Button 視覺物件階層。 這是因為 WPF 控制項有 ControlTemplate ,其中包含該控制項的視覺化樹狀結構。 當您明確地參考控制項時,您會以隱含方式參考其視覺階層。

分析視覺效能

WPF 提供一套效能分析工具,可讓您分析應用程式的執行時間行為,並判斷您可以套用的效能優化類型。 Visual Profiler 工具透過直接對應至應用程式的視覺化樹狀結構,提供豐富的效能資料的圖形化檢視。 在此螢幕擷取畫面中, Visual Profiler 的 [CPU 使用量 ] 區段提供物件使用 WPF 服務的精確細目,例如轉譯和配置。

Visual Profiler display output
Visual Profiler 顯示輸出

Visual 轉譯行為

WPF 引進數個會影響視覺物件轉譯行為的功能:保留模式圖形、向量圖形和裝置獨立圖形。

保留模式圖形

了解視覺物件角色的其中一個關鍵就是要了解「直接模式」和「保留模式」圖形系統之間的差異。 以 GDI 或 GDI+ 為基礎的標準 Win32 應用程式使用直接模式圖形系統。 這表示應用程式負責重新繪製因為像是重新調整視窗大小或者物件正在變更其視覺外觀等動作而無效的用戶端區域部分。

Diagram of Win32 rendering sequence

相反地,WPF 會使用保留模式系統。 這表示具有視覺外觀的應用程式物件會定義一組序列化繪圖資料。 一旦定義繪圖資料,系統此後就會負責回應轉譯應用程式物件的所有重新繪製要求。 即使在執行階段,您也可以修改或建立應用程式物件,並仍需依賴系統以回應繪製要求。 保留模式圖形系統的能力在於繪製資訊一律由應用程式以序列化狀態持續保存,但是轉譯的責任屬於系統。 下圖顯示應用程式如何依賴 WPF 來回應繪製要求。

Diagram of WPF rendering sequence

智慧型重新繪製

使用保留模式圖形的最大優點之一是 WPF 可以有效率地優化應用程式中需要重繪的內容。 即使您有一個具有不同層級不透明度的複雜場景,通常也不需要撰寫特殊用途程式碼來最佳化重新繪製作業。 與 Win32 程式設計比較,在 Win32 程式設計中您可能會花費大量精力,透過將更新區域中重新繪製的次數降至最低,來最佳化您的應用程式上。 如需在 Win32 應用程式中最佳化重新繪製所牽涉之複雜性類型的範例,請參閱在更新區域中重新繪製

向量圖形

WPF 會使用 向量圖形 作為轉譯資料格式。 向量圖形 (包含可縮放向量圖形 (SVG)、Windows 中繼檔案 (.wmf) 和 TrueType 字型) 會儲存轉譯資料並將它以說明如何使用圖形基元重新建立影像的指示清單來傳輸。 例如,TrueType 字型是邊框字型,可描述一組線條、曲線和命令,而非像素陣列。 向量圖形的其中一個主要優點就是能夠縮放為任何大小和解析度。

與向量圖形不同,點陣圖圖形將轉譯資料儲存為影像的逐像素 (pixel-by-pixel) 表示法,針對特定解析度預先轉譯。 點陣和向量圖形格式之間的其中一個主要差異是原始來源影像的逼真度。 例如,當來源影像的的大小經過修改,點陣圖圖形系統會伸展影像,而向量圖形系統則是縮放影像,因此保留影像逼真度。

下圖顯示已將大小調整為 300% 的來源影像。 請注意,當來源影像是以點陣圖圖形影像伸展而非以向量圖形影像縮放時,會出現扭曲。

Differences between raster and vector graphics

下列標記顯示已定義的兩 Path 個元素。 第二個元素會使用 ScaleTransform ,將第一個專案的繪圖指令大小調整為 300%。 請注意,元素中的 Path 繪圖指令保持不變。

<Path
  Data="M10,100 C 60,0 100,200 150,100 z"
  Fill="{StaticResource linearGradientBackground}"
  Stroke="Black"
  StrokeThickness="2" />

<Path
  Data="M10,100 C 60,0 100,200 150,100 z"
  Fill="{StaticResource linearGradientBackground}"
  Stroke="Black"
  StrokeThickness="2" >
  <Path.RenderTransform>
    <ScaleTransform ScaleX="3.0" ScaleY="3.0" />
  </Path.RenderTransform>
</Path>

有關解析度和裝置獨立圖形

決定螢幕上文字和圖形大小的系統因素有兩個:解析度和 DPI。 解析度描述螢幕上顯示的像素數目。 解析度越高,像素越小,使得圖形和文字看起來比較小。 顯示在設定為 1024 x 768 的監視器上的圖形,當解析度變更為 1600 x 1200 時,看起來會更小。

另一個系統設定 DPI 則描述以像素為單位的畫面英吋大小。 大部分的 Windows 系統都有 96 DPI,這表示螢幕英寸為 96 圖元。 提高 DPI 設定會讓畫面英吋更大;降低 DPI 則讓畫面英吋更小。 這表示畫面英吋與實際英吋不相同,在大部分的系統上,通常都不相同。 當您提高 DPI,DPI 感知的圖形和文字會變得更大,因為您已經提高畫面英吋的大小。 提高 DPI 可讓文字更方便閱讀,特別是在高解析度時。

並非所有應用程式都是 DPI 感知:某些應用程式使用硬體像素作為主要度量單位;變更系統 DPI 不會影響這些應用程式。 許多其他的應用程式會使用 DPI 感知單位來描述字型大小,但是使用像素來描述所有其他項目。 DPI 太小或太大可能造成這些應用程式的版面配置問題,因為應用程式的文字是隨著系統的 DPI 設定縮放,但應用程式的 UI 則否。 針對使用 WPF 開發的應用程式,已排除此問題。

WPF 支援使用裝置獨立圖元作為測量的主要單位,而不是硬體圖元來自動調整;圖形和文字會正確調整,而不需要應用程式開發人員執行任何額外的工作。 下圖顯示 WPF 文字和圖形在不同 DPI 設定中的顯示方式範例。

Graphics and text at different DPI settings
不同 DPI 設定時的圖形和文字

VisualTreeHelper 類別

類別 VisualTreeHelper 是靜態協助程式類別,可為視覺物件層級的程式設計提供低階功能,這在非常特定的案例中很有用,例如開發高效能自訂控制項。 在大部分情況下,較高層級的 WPF 架構物件,例如 CanvasTextBlock ,可提供更大的彈性和便於使用。

點擊測試

當預設點擊測試支援不符合您的需求時,類別 VisualTreeHelper 會提供視覺物件點擊測試的方法。 您可以使用 HitTest 類別中的 VisualTreeHelper 方法來判斷幾何或點座標值是否在指定物件的界限內,例如控制項或圖形專案。 例如,您可以使用點擊測試來判斷物件的週框矩形內的滑鼠點擊是否落於圓形的幾何內。您也可以選擇覆寫預設點擊測試實作,以執行您的自訂點擊測試計算。

如需點擊測試的詳細資訊,請參閱視覺分層中的點擊測試

列舉視覺化樹狀結構

類別 VisualTreeHelper 提供列舉視覺化樹狀結構成員的功能。 若要擷取父代,請呼叫 GetParent 方法。 若要擷取視覺物件的子系或直接子系,請呼叫 GetChild 方法。 這個方法會傳回指定索引處父系的子 Visual 系。

下列範例示範如何列舉視覺物件的所有子系,如果您對將視覺物件階層的所有轉譯資訊序列化感興趣,這也會是您想要使用的技術。

// Enumerate all the descendants of the visual object.
static public void EnumVisual(Visual myVisual)
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)
    {
        // Retrieve child visual at specified index value.
        Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i);

        // Do processing of the child visual object.

        // Enumerate children of the child visual object.
        EnumVisual(childVisual);
    }
}
' Enumerate all the descendants of the visual object.
Public Shared Sub EnumVisual(ByVal myVisual As Visual)
    For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(myVisual) - 1
        ' Retrieve child visual at specified index value.
        Dim childVisual As Visual = CType(VisualTreeHelper.GetChild(myVisual, i), Visual)

        ' Do processing of the child visual object.

        ' Enumerate children of the child visual object.
        EnumVisual(childVisual)
    Next i
End Sub

在大部分情況下,邏輯樹狀結構是 WPF 應用程式中元素的更實用標記法。 雖然您不會直接修改邏輯樹狀結構,但應用程式的這個檢視適合用來了解屬性繼承和事件路由。 不同于視覺化樹狀結構,邏輯樹狀結構可以代表非視覺資料物件,例如 ListItem 。 如需邏輯樹狀結構的詳細資訊,請參閱 WPF 中的樹狀結構

類別 VisualTreeHelper 提供傳回視覺物件周框的方法。 您可以呼叫 GetContentBounds 傳回視覺物件的周框。 您可以呼叫 GetDescendantBounds 來傳回視覺物件的所有子系周框,包括視覺物件本身。 下列程式碼示範如何計算視覺物件及其所有子系的週框矩形。

// Return the bounding rectangle of the parent visual object and all of its descendants.
Rect rectBounds = VisualTreeHelper.GetDescendantBounds(parentVisual);
' Return the bounding rectangle of the parent visual object and all of its descendants.
Dim rectBounds As Rect = VisualTreeHelper.GetDescendantBounds(parentVisual)

另請參閱