WPF 入門筆記 - 01 - 入門基礎以及常用佈局

BoiledYakult發表於2023-05-20

?本篇為學習部落格園大佬聖殿騎士的《WPF基礎到企業應用系列》以及部分DotNet菜園的《WPF入門教程系列》所作筆記,對應聖殿騎士《WPF基礎到企業應用系列》第 1 - 6 章之間內容,包括 WPF 專案結構、程式的啟動和關閉、程式的生命週期、繼承關係以及常見的佈局控制元件及其應用。先上鍊接:

聖殿騎士博文索引

DotNet菜園

WPF核心程式集

建立一個WPF後,專案引用中會預設引用PresentationCorePresentationFrameworkWindowsBase三個WPF核心程式集:

image-20230516115833980

  1. PresentationCore:定義了WPF中基本的UI元素和呈現系統的核心功能,包括佈局、渲染、輸入、事件等。它包含了許多基本的類和介面,如UIElementVisualDispatcherObjectFreezable等。
  2. PresentationFramework:這是WPF中的應用程式框架,提供了一些高階UI控制元件和應用程式級別的功能,如WindowsPagesNavigationApplicationWindow等。此外,PresentationFramework還定義了WPF的名稱空間、樣式和主題等。
  3. WindowsBase:它包含了一些基礎類和介面,用於支援PresentationCorePresentationFramework,如DependencyObjectDependencyPropertyRoutedEventFrameworkElement等。

檔案結構

預設生成的檔案結構如圖:

image-20230516143847692

App.xaml 中,可以指定專案執行時啟動的窗體,預設是主窗體:MainWindow, 此外還可以還可以定義需要的系統資源以及引入程式集等操作 - App.xaml:

image-20230519101618613

MainWindow.xaml中設計主窗體樣式:修改標題為:XAMLWithScript,而後新增一個Button按鈕,並進行一些對按鈕做一些簡單的”初始化“ - MainWindow.xaml:

image-20230519103947108

當前XAML樣式呈現的是一個標題為XAMLWithScript,有一個內容為OnClickButton按鈕:

image-20230519104204005

樣式中定義的事件在當前頁面的後臺頁面MainWindow.xaml.cs中:

image-20230519104350444

WPF 和 Winform 案例

Application

WPF 和 傳統的 WinForm 類似, WPF 同樣需要一個 Application 來統領一些全域性的行為和操作,並且每個 Domain (應用程式域)中只能有一個 Application 例項存在。和 WinForm 不同的是 WPF Application 預設由兩部分組成 : App.xamlApp.xaml.cs,這有點類似於 Delphi Form(我對此只是瞭解,並沒有接觸過 Delphi ),將定義和行為程式碼相分離。當然,這個和 WebForm 也比較類似。XAML 從嚴格意義上說並不是一個純粹的 XML 格式檔案,它更像是一種 DSL(Domain Specific Language,領域特定語言),它的所有定義都直接對映成某些程式碼,只是具體的翻譯工作交給了編譯器完成而已。WPF 應用程式由 System.Windows.Application 類來進行管理。

WPF 程式啟動項

之前章節有說過WPF程式的啟動項預設是透過StartupUri來確定開啟哪個窗體,我的理解是它和Winform一樣也是透過入口函式來控制開啟哪個窗體,只不是預設情況下需要透過StartupUri間接來確定具體開啟誰:

image-20230519113150068

新建一個類檔案WPFStartupItem.cs重新定義程式的入口,在專案屬性中將啟動物件修改為自定義的類:

using System;
using System.Windows;

namespace WpfApp2
{
  class WPFStartupItem : Application
  {
    [STAThread]
    static void Main()
    {
      // Method 1 : 建立 Application 物件作為程式入口
      Application app = new Application();
      Window window = new Window();   // 空窗體 僅做說明
      app.Run(window);

      // Method 2 : 指定 Application 物件的 MainWindow 屬性,呼叫無引數的 Run 方法
      Window window1 = new Window();
      app.MainWindow = window1;
      window1.Show(); // 必須呼叫 Show 方法,否則無法顯示窗體
      app.Run();

      // Method 3 : 透過 URL 的方式啟動
      app.StartupUri = new Uri("MainWindow.xaml", UriKind.Relative);
      app.Run();
    }
  }
}

WPFStartupItem是一個WPF應用程式的啟動程式碼示例,演示了三種不同的方式來啟動應用程式。

?[STAThread] 是一個執行緒特性(thread attribute),用於指定應用程式的主執行緒型別。

在Windows應用程式中,特別是涉及到圖形使用者介面(GUI)的應用程式中,使用了單執行緒單元 (STA) 模型。STA模型要求應用程式的主執行緒(也稱為訊息迴圈執行緒)是單執行緒的,並且使用了單執行緒單元的COM元件和功能。

在.NET應用程式中,預設情況下,主執行緒被標記為多執行緒單元 (MTA) 模型。但是,對於大多數GUI應用程式,特別是WPF和WinForms應用程式,必須將主執行緒標記為STA模型,以確保與COM元件和其他GUI相關的功能的相容性。

因此,為了確保應用程式的主執行緒被標記為STA模型,需要在主執行緒的入口方法(例如 Main() 方法)前新增 [STAThread] 特性。

在給定的示例中,[STAThread] 特性被應用於 Main() 方法,用於指定主執行緒的模型為STA模型,以確保與GUI和COM元件的相容性。

?Application 類是WPF應用程式的核心類之一,它繼承自System.Windows.Application。它提供了管理和控制WPF應用程式的功能。

Application 類的主要職責包括:

  1. 提供應用程式的入口點:Application 類定義了一個靜態的 Main() 方法,作為應用程式的入口點。在 Main() 方法中,可以建立一個 Application 物件,並呼叫 Run() 方法來啟動應用程式。
  2. 管理應用程式的生命週期:Application 類負責處理應用程式的啟動、關閉和退出過程。它提供了事件和方法,用於在應用程式的不同生命週期階段執行相應的操作,如 Startup 事件用於處理應用程式啟動時的邏輯,Exit 事件用於處理應用程式退出時的邏輯。
  3. 管理應用程式的資源:Application 類允許您定義和訪問應用程式級別的資源,如樣式、模板、資源字典等。這些資源可以在整個應用程式中共享和重用。
  4. 處理全域性異常:Application 類提供了一個 DispatcherUnhandledException 事件,用於捕獲和處理應用程式中未處理的異常。您可以訂閱該事件,並在發生異常時執行自定義的異常處理邏輯。
  5. 管理應用程式的視窗和導航:Application 類提供了管理應用程式視窗和頁面導航的功能。您可以使用 MainWindow 屬性設定應用程式的主視窗,使用 NavigationService 屬性進行頁面之間的導航。

image-20230519151822700

WPF 程式關閉

WPF程式一般透過呼叫Shutdown()方法關閉程式,當然也可以透過Close()或者Application.Exit()實現。預設WPF專案中,Shutdown()方法是隱式發生的,可以透過在App.xaml中顯示呼叫:

image-20230519115355970

ShutdownMode 引數 作用
OnLastWindowClose(預設值) 最後一個窗體關閉或呼叫 Application 物件的 Shutdown() 方法時,應用程式關閉。
OnMainWindowClose 啟動窗體關閉或呼叫 Application 物件的 Shutdown() 方法時,應用程式關閉。(和 C# 的 Windows 應用程式的關閉模式比較類似)
OnExplicitShutdown 只有在呼叫 Application 物件的 Shutdown() 方法時,應用程式才會關閉。

同樣你也可以在程式碼檔案(App.xaml.cs)中進行更改,但必須注意這個設定寫在app.Run() 方法之前 ,如下程式碼:

app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
app.Run(win);

Application 物件事件

image-20230519142432615

窗體 Window

窗體均繼承自System.Windows.Window基類,前文說過在WPF中,一個窗體通常被分成XAML UI檔案和後臺.cs程式碼檔案,最早的XAMLWithScript

// MainWindow.xaml
<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="XAMLWithScript" Height="450" Width="800" Loaded="Window_Loaded">
    <Grid>
        <Button Name="button1" Height="23" Width="75" Click="button1_Click">OnClick</Button>
    </Grid>
</Window>
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp2
{
  /// <summary>
  /// MainWindow.xaml 的互動邏輯
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {

    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {

    }
  }
}

事件等程式碼邏輯內容是寫在後臺程式碼檔案中的,也可以透過x:Code內部XAML型別在XAML生產環境中放置程式碼:

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="XAMLWithScript" Height="450" Width="800" Loaded="Window_Loaded">
    <Grid>
        <Button Name="button1" Height="23" Width="75" Click="button1_Click">OnClick</Button>
        <x:Code>
            <![CDATA[void button1_Click(object sender,System.Windows.RoutedEventArgs e)
            {
                button1.Content="Hello there.";
            }
            ]]>
        </x:Code>
    </Grid>
</Window>

記得把後臺.cs檔案中關於點選事件註釋掉,否則會造成衝突:

image-20230519145125864

上述XAML程式碼在點選按鈕後,按鈕文字會變成Hello there.:

image-20230519145231572

窗體生命週期

image-20230519150701844

?第一次開啟視窗時,只有當引發 Activated 後才會引發 LoadedContentRendered 事件。 記住這一點,在引發 ContentRendered 時,實際上就可認為視窗已開啟。

WPF 窗體的詳細的屬性、方法、事件請參考 MSDN,有很多的屬性、方法、事件與Windows應用程式中System.Windows.Forms.Form類頗為相似,其中常用的一些屬性、方法、事件有:

窗體邊框模式(WindowStyle 屬性)和是否允許更改窗體大小(ResizeMode 屬性)。

窗體啟動位置(WindowStartupLocation 屬性)和啟動狀態(WindowState 屬性)等。

窗體標題(Title 屬性)及圖示 。

是否顯示在工作列(ShowInTaskbar

始終在最前(TopMost 屬性)

Dispatcher 多執行緒

微軟在WPF引入了Dispatcher,不管是WinForm應用程式還是WPF應用程式,實際上都是一個程式,一個程式可以包含多個執行緒,其中有一個是主執行緒,其餘的是子執行緒。在WPF或WinForm應用程式中,主執行緒負責接收輸入、處理事件、繪製螢幕等工作,為了使主執行緒及時響應,防止假死,在開發過程中對一些耗時的操作、消耗資源比較多的操作,都會去建立一個或多個子執行緒去完成操作,比如大資料量的迴圈操作、後臺下載。這樣一來,由於UI介面是主執行緒建立的,所以子執行緒不能直接更新由主執行緒維護的UI介面。

Dispatcher的作用是用於管理執行緒工作項佇列,類似於Win32中的訊息佇列,Dispatcher的內部函式,仍然呼叫了傳統的建立視窗類,建立視窗,建立訊息泵等操作。Dispatcher本身是一個單例模式,建構函式私有,暴露了一個靜態的CurrentDispatcher方法用於獲得當前執行緒的Dispatcher。對於執行緒來說,它對Dispatcher是一無所知的,Dispatcher內部維護了一個靜態的 List<Dispatcher> _dispatchers, 每當使用CurrentDispatcher方法時,它會在這個_dispatchers中遍歷,如果沒有找到,則建立一個新的Dispatcher物件,加入到_dispatchers中去。Dispatcher內部維護了一個Thread的屬性,建立Dispatcher時會把當前執行緒賦值給這個 Thread的屬性,下次遍歷查詢的時候就使用這個欄位來匹配是否在_dispatchers中已經儲存了當前執行緒的Dispatcher

繼承關係

image-20230519154334057

  • System.Object 類:基類。
  • System.Windows.Threading.DispatcherObject 類:從圖中看WPF 中的使用到的大部分控制元件與其他類大多是繼承 DispatcherObject 類,它提供了用於處理併發和執行緒的基本構造。
  • System.Windows.DependencyObject類:對WPF中的依賴項屬性承載支援與 附加屬性承載支援,表示參與 依賴項屬性 系統的物件。
  • System.Windows.Media.Visual類:為 WPF 中的呈現提供支援,其中包括命中測試、座標轉換和邊界框計算等。
  • System.Windows.UIElement 類:UIElement 是 WPF 核心級實現的基類,該類是 Windows Presentation Foundation (WPF) 中具有可視外觀並可以處理基本輸入的大多數物件的基類。
  • System.Windows.FrameworkElement類:為 Windows Presentation Foundation (WPF) 元素提供 WPF 框架級屬性集、事件集和方法集。此類表示附帶的 WPF 框架級實現,它是基於由UIElement定義的 WPF 核心級 API 構建的。
  • System.Windows.Controls.Control 類:表示 使用者介面 (UI) 元素的基類,這些元素使用 ControlTemplate 來定義其外觀。
  • System.Windows.Controls.ContentControl類:表示沒有任何型別的內容表示單個控制元件。
    • WPF的絕大部分的控制元件,還包括視窗本身都是繼承自ContentControl的:

image-20230519154643586

  • System.Windows.Controls.ItemsControl 類:表示可用於提供專案的集合的控制元件。

    image-20230519154822147

  • System.Windows.Controls.Panel類:為所有 Panel 元素提供基類。 使用 Panel 元素定位和排列在 Windows Presentation Foundation (WPF) 應用程式的子物件。

  • System.Windows.Sharps.Sharp類:為 Ellipse、Polygon 和 Rectangle 之類的形狀元素提供基類。

走進Dispatcher

WPF 執行緒分配系統提供一個Dispatcher 屬性、VerifyAccess CheckAccess方法來操作執行緒。執行緒分配系統位於所有WPF類中基類,大部分WPF元素都派生於此類,如下圖的Dispatcher類:

image-20230519151455027

Dispatcher排程物件想對應的就是DispatcherObject,在WPF中絕大部分控制元件都繼承自 DispatcherObject,甚至包括 Application。這些繼承自 DispatcherObject 的物件具有執行緒關聯特徵,也就意味著只有建立這些物件例項,且包含了Dispatcher的執行緒 (通常指預設UI執行緒)才能直接對其進行更新操作。

image-20230519152039341

我們宣告一個文字Label並嘗試在程式執行過程中更新其顯示內容:

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="XAMLWithScript" Height="450" Width="800" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded">
    <Grid>
        <Button Name="button1" Height="23" Width="75" Click="button1_Click">OnClick</Button>
        <Label Name="lbl_Hello">Hello World!</Label>
    </Grid>
</Window>

後臺程式碼:

using System;
using System.Threading;
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Thread thread = new Thread(ModifyUI);
            thread.Start();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            button1.Content = "Hello there.";
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {

        }
        private void ModifyUI()
        {
            // 模擬一些工作
            Thread.Sleep(TimeSpan.FromSeconds(5));
            lbl_Hello.Content = "Hello,Dispatcher";
        }
    }
}

在程式執行五秒後就會報錯,System.InvalidOperationException:“呼叫執行緒無法訪問此物件,因為另一個執行緒擁有該物件。”

image-20230519152724976

這和Winform跨執行緒更新UI是類似的,我們一般會使用委託完成對執行緒UI的更新。在WPF中,按照DispatcherObject的限制原則,我們改用 Window.Dispatcher.Invoke() 即可順利完成這個更新操作。

image-20230519153215035

如果在其他工程或者類中,我們可以用Application.Current.Dispatcher.Invoke方法來完成同樣的操作,它們都指向UI Thread Dispatcher這個唯一的物件。Dispatcher 同時還支援BeginInvoke非同步呼叫,如下程式碼:

private void btnHello_Click(object sender, RoutedEventArgs e)
{
    new Thread(() =>
    {
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
            new Action(() =>
            {
                Thread.Sleep(TimeSpan.FromSeconds(5));
                this.lblHello.Content = DateTime.Now.ToString();
            }));
    }).Start();
}

佈局

雖然UI很重要,但不能為了UIUI

WPF 的佈局控制元件都在System.Windows.Controls.Panel這個基類下面,使用Panel元素在WPF應用程式中放置和排列子物件。

image-20230519155904993

一個Panel的呈現是測量和排列Children子元素、然後在螢幕上繪製它們的過程。所以在佈局的過程中會經過一系列的計算,那麼Children越多,執行的計算次數就越多。如果不需要較為複雜的 Panel(如Grid和自定義複雜的 Panel),則可以使用構造相對簡單的佈局(如 CanvasUniformGrid等),這種佈局可帶來更好的效能。如果有可能,我們應儘量避免不必要地呼叫UpdateLayout方法。

每當Panel內的子元素改變其位置時,佈局系統就可能觸發一個新的處理過程。對此,瞭解哪些事件會呼叫佈局系統就很重要,因為不必要的呼叫可能導致應用程式效能變差。

換句話說,佈局是一個遞迴系統,實現在螢幕上對元素進行大小調整、定位和繪製,然後進行呈現。具體如下圖,要實現控制元件 0 的佈局, 那麼先要實現 0 的子控制元件 01,02... 的佈局, 要實現 01 的佈局, 那麼得實現 01 的子控制元件 001,002... 的佈局, 如此迴圈直到子控制元件的佈局完成後, 再完成父控制元件的佈局, 最後遞迴回去直到遞迴結束, 這樣整個佈局過程就完成了。

image-20230519160518655

佈局系統為Panel中的每個子控制元件完成兩個處理過程:測量處理過程(Measure)和排列處理過程(Arrange)。每個子Panel均提供自己的 MeasureOverride ArrangeOverride方法,以實現自己特定的佈局行為。

Canvas

Canvas是最基本的皮膚,只是一個儲存控制元件的容器,它不會自動調整內部元素的排列及大小。不指定元素位置,元素將預設顯示在畫布的左上方。它僅支援用顯式座標定位控制元件,它也允許指定相對任何角的座標,而不僅僅是左上角。可以使用Left、Top、Right、 Bottom附加屬性在Canvas中定位控制元件。透過設定LeftRight屬性的值表示元素最靠近的那條邊,應該與Canvas左邊緣或右邊緣保持一個固定的距離,設定TopBottom的值也是類似的意思。實質上,你在選擇每個控制元件停靠的角時,附加屬性的值是作為外邊距使用的。

Canvas的主要用途是用來畫圖。Canvas 預設不會自動裁減超過自身範圍的內容,即溢位的內容會顯示在Canvas外面,這是因為預設 ClipToBounds="False";我們可以透過設定ClipToBounds="True來裁剪多出的內容。

接下來我們來看兩個例項,透過xamlC#實現相同視覺效果:

image-20230519163254269

xaml樣式:

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp2"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="XAMLWithScript"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <Canvas Margin="0,0,0,0"
            Background="White">
        <Rectangle Canvas.Left="210"
                   Canvas.Top="101"
                   Width="250"
                   Height="200"
                   Fill="Blue"
                   Stroke="Azure" />
        <Ellipse Canvas.Left="65"
                 Canvas.Top="45"
                 Width="250"
                 Height="100"
                 Panel.ZIndex="1"
                 Fill="Red"
                 Stroke="Green" />
        <Button Name="btnByCode" Click="btnByCode_Click">後臺程式碼實現</Button>
    </Canvas>
</Window>

C#程式碼實現:

  Canvas canv = new Canvas();
  //把canv新增為窗體的子控制元件
  Content = canv;
  canv.Margin = new Thickness(0, 0, 0, 0);
  canv.Background = new SolidColorBrush(Colors.White);
  //Rectangle
  Rectangle r = new Rectangle();
  r.Fill = new SolidColorBrush(Colors.Red);
  r.Stroke = new SolidColorBrush(Colors.Red);
  r.Width = 200;
  r.Height = 140;
  r.SetValue(Canvas.LeftProperty, (double)200);
  r.SetValue(Canvas.TopProperty, (double)120);
  canv.Children.Add(r);
  //Ellipse
  Ellipse el = new Ellipse();
  el.Fill = new SolidColorBrush(Colors.Blue);
  el.Stroke = new SolidColorBrush(Colors.Blue);
  el.Width = 240;
  el.Height = 80;
  el.SetValue(Canvas.ZIndexProperty, 1);
  el.SetValue(Canvas.LeftProperty, (double)100);
  el.SetValue(Canvas.TopProperty, (double)80);
  canv.Children.Add(el);

?Canvas內的子控制元件不能使用兩個以上的Canvas附加屬性,如果同時設定Canvas.Left和Canvas.Right屬性,那麼後者將會被忽略

StackPanel

堆疊皮膚,水平或垂直放置元素。透過設定皮膚的Orientation屬性設定了兩種排列方式:橫排(Horizontal 預設的)和豎排(Vertical)。縱向的StackPanel預設每個元素寬度與皮膚一樣寬,反之橫向亦然。如果包含的元素超過了皮膚空間,它只會截斷多出的內容。 元素的Margin屬性用於使元素之間產生一定得間隔,當元素空間大於其內容的空間時,剩餘空間將由HorizontalAlignmentVerticalAlignment屬性來決定如何分配。

同樣看例項:

image-20230519170007484
<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp2"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="XAMLWithScript"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <StackPanel Margin="0,0,0,0"
                Background="White"
                Orientation="Horizontal">
        <Button Content="豎排第一個按鈕" />
        <Button Content="豎排第二個按鈕" />
        <Button Content="豎排第三個按鈕" />
        <Button Content="豎排第四個按鈕" />
    </StackPanel>
</Window>
StackPanel sp = new StackPanel();
sp.Orientation = Orientation.Vertical;
//把sp新增為窗體的子控制元件
this.Content = sp;
sp.Margin = new Thickness(0, 0, 0, 0);
sp.Background = new SolidColorBrush(Colors.White);
sp.Orientation = Orientation.Horizontal;
//Button1
Button b1 = new Button();
b1.Content = "豎排第一個按鈕";
sp.Children.Add(b1);
//Button2
Button b2 = new Button();
b2.Content = "豎排第二個按鈕";
sp.Children.Add(b2);
//Button3
Button b3 = new Button();
b3.Content = "豎排第三個按鈕";
sp.Children.Add(b3);
//Button4
Button b4 = new Button();
b4.Content = "豎排第四個按鈕";
sp.Children.Add(b4);

WrapPanel

可換行的行中放置元素,在水平方向上從左向右放置元素,換行後也是從左向右。在垂直方向上,從上到下放置元素,在切換列後也是從上到下。WrapPanel 也提供了Orientation屬性設定排列方式,這跟上面的StackPanel基本相似。不同的是WrapPanel會根據內容自動換行。

ItemHeight - 所有子元素都一致的高度。每個子元素填充高度的方式取決於它的VerticalAlignment屬性、Height屬性等。任何比ItemHeight高的元素都將被截斷。

ItemWidth - 所有子元素都一致的寬度。每個子元素填充高度的方式取決於它的VerticalAlignment屬性、Width屬性等。任何比ItemWidth高的元素都將被截斷。

例項:

image-20230519170838004

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp2"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="XAMLWithScript"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <WrapPanel Margin="0,0,0,0" Background="White">
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
        <Rectangle Width="60" Height="60" Margin="10,10,10,10" Fill="lightgreen" />
    </WrapPanel>
</Window>
WrapPanel wp = new WrapPanel();
//把wp新增為窗體的子控制元件
Content = wp;
wp.Margin = new Thickness(0, 0, 0, 0);
wp.Background = new SolidColorBrush(Colors.White);
//遍歷增加Rectangles
Rectangle r;
for (int i = 0; i <= 10; i++)
{
  r = new Rectangle();
  r.Fill = new SolidColorBrush(Colors.LightGreen);
  r.Margin = new Thickness(10, 10, 10, 10);
  r.Width = 60;
  r.Height = 60;
  wp.Children.Add(r);
}

DockPanel

停靠皮膚,根據容器的整個邊界調整元素,DockPanel定義一個區域,在此區域中,您可以使子元素透過描點的形式排列,這些物件位於Children屬性中。停靠皮膚其實就是在WinForm類似於Dock屬性的元 素。DockPanel會對每個子元素進行排序,並停靠在皮膚的一側,多個停靠在同側的元素則按順序排序。 

如果將LastChildFill屬性設定為 true(預設設定),那麼無論對DockPanel的最後一個子元素設定的其他任何停靠值如何,該子元素都將始終填滿剩餘的空間。若要將子元素停靠在另一個方向,必須將LastChildFill屬性設定為 false,還必須為最後一個子元素指定顯式停靠方向。

預設情況下,皮膚元素並不接收焦點。要強制使皮膚元素接收焦點,請將Focusable屬性設定為 true

image-20230519172923789
<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp2"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="XAMLWithScript"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <DockPanel Width="Auto" Height="Auto" LastChildFill="False">
        <Button Content="1" DockPanel.Dock="Top" />
        <Button Width="40" Content="4" DockPanel.Dock="Left" />
        <Button Width="40" Content="2" DockPanel.Dock="Right" />
        <Button Content="3" DockPanel.Dock="Bottom" />
    </DockPanel>
</Window>
DockPanel dockPanel = new DockPanel();
dockPanel.Width = double.NaN;	// Auto
dockPanel.Height = double.NaN; // Auto
dockPanel.LastChildFill = false;

Button button1 = new Button();
button1.Content = "1";
DockPanel.SetDock(button1, Dock.Top);

Button button2 = new Button();
button2.Width = 40;
button2.Content = "4";
DockPanel.SetDock(button2, Dock.Left);

Button button3 = new Button();
button3.Width = 40;
button3.Content = "2";
DockPanel.SetDock(button3, Dock.Right);

Button button4 = new Button();
button4.Content = "3";
DockPanel.SetDock(button4, Dock.Bottom);

dockPanel.Children.Add(button1);
dockPanel.Children.Add(button2);
dockPanel.Children.Add(button3);
dockPanel.Children.Add(button4);

this.Content = dockPanel;

Grid

表格佈局,在行列表格中排列元素,它的子控制元件被放在一個一個實現定義好的小格子裡面,整齊配列。

Grid和其他各個Panel比較起來,功能最多也最為複雜。要使用Grid,首先要向RowDefinitionsColumnDefinitions屬性中新增一定數量的RowDefinitionsColumnDefinitions元素,從而定義行數和列數。而放置在Grid皮膚中的控制元件元素都必須顯示採用附加屬性語法定義其放置所在的行和列,它們都是以0為基準的整型值,如果沒有顯式設定任何行或列,Grid將會隱式地將控制元件加入在第0行第0列。

由於Grid的組成並非簡單的新增屬性標記來區分行列,這也使得使用者在實際應用中可以具體到某一單 元格中,所以佈局起來就很精細了。

Grid的列寬與行高可採用固定、自動、按比例三種方式定義

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
        <RowDefinition Height="40" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="300" />
    </Grid.ColumnDefinitions>
</Grid>

<Grid.RowDefinitions> 元素來定義行,其中包含的多個 <RowDefinition> 元素來定義每行的高度。

  • 第一行的高度設定為 "Auto",表示該行的高度會根據其內容自動調整。
  • 第二行的高度也設定為 "Auto",表示該行的高度會根據其內容自動調整。
  • 第三行的高度設定為 "*",表示該行的高度將填充 Grid 的剩餘可用空間。
  • 第四行的高度設定為 "40",表示該行的高度固定為 40 個裝置無關單位(Device Independent Units)。

<Grid.ColumnDefinitions> 元素來定義列,其中包含多個 <ColumnDefinition> 元素來定義每列的寬度。

  • 第一列的寬度設定為 "Auto",表示該列的寬度會根據其內容自動調整。
  • 第二列的寬度設定為 "300",表示該列的寬度固定為 300 個裝置無關單位。

上述xaml內容定義了一個四行兩列的Grid佈局,第一行和第二行的高度根據其內容自動調整,第三行填充剩餘的可用空間,第四行的高度固定為 40。第一列的寬度根據其內容自動調整,第二列的寬度固定為 300。

Grid寬高的幾種方式:

  1. 固定大小(Fixed Size): 可以使用具體數值(如畫素、裝置無關單位等)來指定行高度和列寬度,例如 RowDefinition.Height="100"ColumnDefinition.Width="200"。這樣可以使行和列具有固定的大小。
  2. 自動調整(Auto Sizing): 可以使用 Auto 關鍵字來指定行高度和列寬度,例如 RowDefinition.Height="Auto"ColumnDefinition.Width="Auto"。這樣會根據行或列的內容自動調整大小,以適應內容的需求。
  3. 剩餘空間填充(Star Sizing): 可以使用 * 關鍵字來指定行高度和列寬度,例如 RowDefinition.Height="*"ColumnDefinition.Width="*"。這樣會使行或列佔據剩餘可用空間的比例。如果多個行或列都設定為 *,它們將平均分配剩餘空間。

跨越多行多列

在 Grid 佈局中,可以透過合併單元格的方式實現跨越多行和多列的佈局效果。這可以透過使用 Grid.RowSpanGrid.ColumnSpan 屬性來實現。

  • Grid.RowSpan 屬性用於指定一個元素跨越的行數,可以設定為大於 1 的整數值。
  • Grid.ColumnSpan 屬性用於指定一個元素跨越的列數,同樣可以設定為大於 1 的整數值。

以下是一個示例,展示了一個元素跨越兩行三列的佈局:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>

    <!-- 跨越兩行三列的元素 -->
    <TextBlock Text="Spanning across multiple rows and columns"
               Grid.Row="0" Grid.RowSpan="2"
               Grid.Column="0" Grid.ColumnSpan="3" />

    <!-- 其他元素 -->
    <TextBlock Text="Row 2, Column 3" Grid.Row="2" Grid.Column="3" />
    <TextBlock Text="Row 1, Column 2" Grid.Row="1" Grid.Column="2" />
</Grid>

在上述示例中,TextBlock 元素透過設定 Grid.RowSpan="2"Grid.ColumnSpan="3" 屬性跨越了兩行三列。它位於第一行的第一列,並跨越了第一行、第二行和前三列。其他元素則根據指定的行和列進行定位。

透過合併單元格的方式,可以建立更復雜的跨越多行多列的佈局效果,以滿足特定的佈局需求。

分割效果

GridSplitter 用於在 Grid 佈局中建立可調整大小的分割效果。它允許使用者透過拖動分隔條來改變相鄰行或列的大小。

以下是 GridSplitter 的基本用法示例:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>

    <TextBlock Text="Left" Grid.Column="0" />
  
    <GridSplitter Width="5" Grid.Column="1" ResizeBehavior="PreviousAndNext" /> 
  
    <TextBlock Text="Right" Grid.Column="2" />
</Grid>

在上述示例中,我們建立了一個包含三個列的 Grid 佈局。在中間的列,我們新增了一個 GridSplitter 控制元件,並設定其寬度為 5 個裝置無關單位(Device Independent Units)。GridSplitterResizeBehavior 屬性設定為 PreviousAndNext,表示它將影響前一個列和後一個列的大小。

使用者可以在執行時透過拖動 GridSplitter 控制元件來調整左側和右側列的寬度。

GridSplitter 還有其他屬性可用於定製其外觀和行為,例如 BackgroundResizeDirectionResizeCursor 等。您可以根據需要設定這些屬性來滿足特定的佈局要求。

請注意,GridSplitter 只能用於 Grid 佈局,並且需要適當的行和列定義才能正常工作。確保在使用 GridSplitter 時,考慮佈局的其他方面,如最小寬度、最大寬度等,以提供更好的使用者體驗。

混合佈局效果

image-20230520111910809

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp2"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="XAMLWithScript"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <Grid Width="Auto" Height="Auto" ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition Height="61*"/>
            <RowDefinition Height="101*"/>
            <RowDefinition Height="108*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="139"/>
            <ColumnDefinition Width="184*"/>
            <ColumnDefinition Width="45*"/>
            <ColumnDefinition Width="250*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="第一行、第一列,佔1列" Background="LightBlue" HorizontalAlignment="Center"/>
        <Button Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="3" Content="第一行、佔3列"/>
        <Button  Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Content="第3行,第1列開始,佔4列" />
    </Grid>
</Window>

新增子元素時不宣告具體幾行幾列時預設都是0;

Grid grid = new Grid();
grid.Width = double.NaN;
grid.Height = double.NaN;
grid.ShowGridLines = true;

grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(61, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(101, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(108, GridUnitType.Star) });

grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(139) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(184, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(45, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(250, GridUnitType.Star) });

TextBlock textBlock = new TextBlock();
textBlock.Text = "第一行、第一列,佔1列";
textBlock.Background = Brushes.LightBlue;
textBlock.HorizontalAlignment = HorizontalAlignment.Center;

Grid.SetRow(textBlock, 0);
Grid.SetColumn(textBlock, 0);
Grid.SetColumnSpan(textBlock, 1);

Button button1 = new Button();
button1.Content = "第一行、佔3列";

Grid.SetRow(button1, 0);
Grid.SetColumn(button1, 1);
Grid.SetRowSpan(button1, 2);
Grid.SetColumnSpan(button1, 3);

Button button2 = new Button();
button2.Content = "第3行,第1列開始,佔4列";

Grid.SetRow(button2, 2);
Grid.SetColumn(button2, 0);
Grid.SetColumnSpan(button2, 4);

grid.Children.Add(textBlock);
grid.Children.Add(button1);
grid.Children.Add(button2);

this.Content = grid;

?設計的時候看不清楚的話可以透過ShowGridLines屬性把網格線顯示出來

UniformGrid

Grid 簡化版,強制所有單元格具有相同尺寸。每個單元格的大小相同,不用在定義行列集合。均佈網格每個單元格只能容納一個元素,將自動按照定義在其內部的元素個數,自動建立行列,並通常保持相同的行列數。UniformGrid 中沒有Row Column 附加屬性,也沒有空白單元格。

?與Grid佈局控制元件相比,UniformGrid佈局控制元件很少使用。Grid皮膚是用於建立簡單乃至複雜視窗布局的通用工具。UniformGrid皮膚是一個一種更特殊的佈局容器,主要用於在一個刻板的網格中快速地佈局元素。

image-20230520113240551

<Window x:Class="WpfApp2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 		   	     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfApp2" 		xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="XAMLWithScript" Width="800" Height="450"
        Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" mc:Ignorable="d">
    <UniformGrid Columns="2" Rows="2">
        <Button>(0,0)</Button>
        <Button>(0,1)</Button>
        <Button>(1,0)</Button>
        <Button>(1,1)</Button>
    </UniformGrid>
</Window>

C#程式碼:

Grid grid = new Grid();
grid.Width = double.NaN;
grid.Height = double.NaN;
grid.ShowGridLines = true;

grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(61, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(101, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(108, GridUnitType.Star) });

grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(139) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(184, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(45, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(250, GridUnitType.Star) });

TextBlock textBlock = new TextBlock();
textBlock.Text = "第一行、第一列,佔1列";
textBlock.Background = Brushes.LightBlue;
textBlock.HorizontalAlignment = HorizontalAlignment.Center;

Grid.SetRow(textBlock, 0);
Grid.SetColumn(textBlock, 0);
Grid.SetColumnSpan(textBlock, 1);

Button button1 = new Button();
button1.Content = "第一行、佔3列";

Grid.SetRow(button1, 0);
Grid.SetColumn(button1, 1);
Grid.SetRowSpan(button1, 2);
Grid.SetColumnSpan(button1, 3);

Button button2 = new Button();
button2.Content = "第3行,第1列開始,佔4列";

Grid.SetRow(button2, 2);
Grid.SetColumn(button2, 0);
Grid.SetColumnSpan(button2, 4);

grid.Children.Add(textBlock);
grid.Children.Add(button1);
grid.Children.Add(button2);

this.Content = grid;

ViewBox

Viewbox 是一個容器控制元件,允許其內容根據可用空間進行縮放,同時保持其縱橫比。它通常用於為其中的內容提供自動縮放和調整大小的行為。

ViewBox這個控制元件通常和其他控制元件結合起來使用,是WPF中非常有用的控制元件,用來定義一個內容容器。ViewBox元件的作用是拉伸或延展位於其中的元件,以填滿可用空間,使之有更好的佈局及視覺效果。

?一個 Viewbox中只能放一個控制元件。如果多新增了一個控制元件就會報錯。

以下是一些常用的 Viewbox 屬性:

  1. Stretch(拉伸):指定內容在檢視框中的拉伸方式。常見的取值包括:
    • Uniform(均勻):保持內容的縱橫比,同時填充檢視框,可能導致內容被裁剪。
    • UniformToFill(均勻填充):保持內容的縱橫比,同時填充檢視框,可能導致內容被裁剪。
    • Fill(填充):不保持內容的縱橫比,將內容拉伸以填充檢視框。
  2. StretchDirection(拉伸方向):指定內容在檢視框中拉伸的方向。常見的取值包括:
    • Both(雙向):內容可以在水平和垂直方向上拉伸。
    • DownOnly(僅向下):內容只能在垂直方向上拉伸。
    • UpOnly(僅向上):內容只能在水平方向上拉伸。
  3. HorizontalAlignment(水平對齊)和 VerticalAlignment(垂直對齊):指定內容在檢視框中的水平和垂直對齊方式。常見的取值包括:
    • Left(左對齊):內容在檢視框的左側對齊。
    • Center(居中對齊):內容在檢視框的中間對齊。
    • Right(右對齊):內容在檢視框的右側對齊。
    • Top(頂部對齊):內容在檢視框的頂部對齊。
    • Bottom(底部對齊):內容在檢視框的底部對齊。
  4. MaxWidth(最大寬度)和 MaxHeight(最大高度):指定內容在檢視框中的最大寬度和最大高度限制。當內容超過指定的最大尺寸時,將被自動縮放以適應。

Uniform效果下的Viewbox:

image-20230520120616348
<Viewbox Stretch="Uniform">
  <Button Content="Hello,Knights Warrior" />
</Viewbox>
Viewbox vb = new Viewbox();
vb.Stretch = Stretch.Uniform;
//Button1
Button b1 = new Button();
b1.Content = "Hello,Knights Warrior";
vb.Child = b1;
this.Content = vb;

Border

Border不是佈局皮膚,但是經常與佈局類的皮膚一起配合使用,用於為其內部的內容提供邊框和可選的背景樣式。它可以包含單個子元素,並且可以根據需要調整其大小以適應子元素。

以下是 Border 控制元件的一些常用屬性:

  1. Background(背景):指定 Border 的背景顏色或背景畫刷。可以使用顏色名稱、十六進位制值或畫刷物件來設定背景。
  2. BorderBrush(邊框畫刷):指定 Border 的邊框顏色或邊框畫刷。可以使用顏色名稱、十六進位制值或畫刷物件來設定邊框。
  3. BorderThickness(邊框厚度):指定 Border 的邊框厚度。可以設定為單個值,表示統一的邊框寬度,或設定為 LeftTopRightBottom 分別指定不同的邊框寬度。
  4. CornerRadius(圓角半徑):指定 Border 的圓角半徑,以使邊框的角變得圓潤。可以設定為單個值,表示統一的圓角半徑,或設定為 TopLeftTopRightBottomRightBottomLeft 分別指定不同的圓角半徑。
  5. Padding(內邊距):指定 Border 內容與邊框之間的空間,也就是內邊距。可以設定為單個值,表示統一的內邊距,或設定為 LeftTopRightBottom 分別指定不同的內邊距。
  6. Child(子元素):指定 Border 的單個子元素。這個子元素將被包含在邊框內部,並且可以根據需要調整邊框的大小。

透過使用 Border 控制元件,您可以為內部內容提供邊框和背景樣式,並控制邊框的大小、邊框顏色以及內部內容的對齊和間距。這使得 Border 成為一種常用的容器控制元件,用於組織和美化介面元素。

image-20230520144916164
<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp2"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="XAMLWithScript"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <Border Width="270"
            Height="250"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Background="LightGray"
            BorderBrush="LightGreen"
            BorderThickness="8"
            CornerRadius="5">
        <Canvas Background="LightYellow">
            <Rectangle Canvas.Left="30"
                       Canvas.Top="20"
                       Width="150"
                       Height="150"
                       Fill="red"
                       Stroke="Black"
                       StrokeThickness="10" />
        </Canvas>
    </Border>
</Window>
Border border = new Border();
border.Width = 270;
border.Height = 250;
border.HorizontalAlignment = HorizontalAlignment.Center;
border.VerticalAlignment = VerticalAlignment.Center;
border.Background = Brushes.LightGray;
border.BorderBrush = Brushes.LightGreen;
border.BorderThickness = new Thickness(8);
border.CornerRadius = new CornerRadius(5);

Canvas canvas = new Canvas();
canvas.Background = Brushes.LightYellow;

Rectangle rectangle = new Rectangle();
rectangle.Width = 150;
rectangle.Height = 150;
rectangle.Fill = Brushes.Red;
rectangle.Stroke = Brushes.Black;
rectangle.StrokeThickness = 10;
Canvas.SetLeft(rectangle, 30);
Canvas.SetTop(rectangle, 20);

canvas.Children.Add(rectangle);
border.Child = canvas;

// Add the Border to the main Window
this.Content = border;

ScrollViewer

ScrollViewer 是 WPF 中常用的滾動容器控制元件,用於在需要時提供滾動功能以顯示超出容器可見區域的內容。它可以包含單個子元素,並根據需要在垂直和/或水平方向上顯示捲軸。

以下是 ScrollViewer 控制元件的一些常用屬性:

  1. HorizontalScrollBarVisibility(水平捲軸可見性):指定水平捲軸的可見性。常見的取值包括:
    • Disabled(禁用):禁用水平捲軸。
    • Auto(自動):根據需要自動顯示水平捲軸。
    • Visible(可見):始終顯示水平捲軸。
  2. VerticalScrollBarVisibility(垂直捲軸可見性):指定垂直捲軸的可見性。常見的取值包括:
    • Disabled(禁用):禁用垂直捲軸。
    • Auto(自動):根據需要自動顯示垂直捲軸。
    • Visible(可見):始終顯示垂直捲軸。
  3. CanContentScroll(內容滾動):指定 ScrollViewer 是否以邏輯單元(例如行或項)為單位滾動內容。當設定為 True 時,捲軸將以邏輯單元為單位滾動,而不是以畫素為單位滾動。
  4. Content(內容):指定 ScrollViewer 的單個子元素。這個子元素將被包含在滾動容器中,並可以根據需要進行滾動。

透過使用 ScrollViewer 控制元件,可以將內容放置在可滾動的容器中,以便在容器的可見區域之外顯示內容,並透過捲軸來瀏覽內容。這對於處理大量內容或需要顯示大型元素的情況非常有用,以保持介面的可用性和可訪問性。

image-20230520145935943
<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp2"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="XAMLWithScript"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <Grid>
        <ScrollViewer>
            <Rectangle Width="500" Height="500" Fill="Gray"></Rectangle>
        </ScrollViewer>
    </Grid>
</Window>

?只能放單個子元素

佈局綜合應用

敘利亞戰損版 Microsoft ToDo 佈局 - 不建議

image-20230520155138778

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp2"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="WPFDemo"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <Grid Width="Auto" Height="Auto">
        <Grid.ColumnDefinitions >
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="8*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="6*"/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" Grid.Column="1" Grid.RowSpan="8" Background="#218868" 
                    Orientation="Vertical">
            <TextBlock Text="   我的一天" FontSize="18" FontWeight="Bold" Foreground="White"/>
            <TextBlock Text="      5月20日,星期六" FontSize="10" Foreground="White"/>
            <TextBlock />
            <Border BorderThickness="10" CornerRadius="2" BorderBrush="#1e7b5e" Background="#1e7b5e" >
                <TextBlock Text="+ 新增任務" Foreground="AntiqueWhite" FontSize="13"/>
            </Border>
        </StackPanel>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="Miscrosoft ToDo" FontSize="16" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Button Grid.Row="1" Grid.Column="0" Content="我的一天" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue"/>
        <Button Grid.Row="2" Grid.Column="0" Content="重要" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue"/>
        <Button Grid.Row="3" Grid.Column="0" Content="已計劃日程" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue"/>
        <Button Grid.Row="4" Grid.Column="0" Content="已分配任務" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue"/>
        <Button Grid.Row="5" Grid.Column="0" Content="任務" FontSize="16" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Background="LightBlue">
            <Button.Style>
                <Style TargetType="Button">
                    <Setter Property="BorderBrush" Value="LightGray"/>
                    <Setter Property="BorderThickness" Value="1"/>
                    <Setter Property="Padding" Value="5"/>
                    <Setter Property="Background" Value="Transparent"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="Button">
                                <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                CornerRadius="2">
                                    <ContentPresenter HorizontalAlignment="Center"
                                              VerticalAlignment="Center"/>
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Button.Style>
        </Button>
    </Grid>
</Window>

如果你真的看到了這兒,那我覺得,這件事真是 - 泰褲辣!!!?

自定義Panel

該章節省略未讀。。。

相關文章