WPF 自定義表單(一)

2023-09-04 21:00:20

.Net預設的表單樣式只有四種:None、SingleBorderWindow、ThreeDBorderWindow、ToolWindow,都比較「醜」。而很多時候,我們希望自定義表單,比如,無邊框,有陰影,或者有模糊效果等。

在WPF中,要實現自定義表單比較簡單,主要有兩種方法:

1)使用WindowChrome;

2)使用WindowStyle = 「None」。

一、使用WindowChrome。

  WindowChrome,可以翻譯為:表單裝飾條,官方檔案中的定義是:表示一個物件,它描述視窗非工作區區域的自定義。(官方連結:WindowChrome 類 (System.Windows.Shell) | Microsoft Learn

  在官方的解釋中,視窗由兩部分構成:客戶區域,非客戶區域。

圖中,Client Area表示客戶區域;其他的部分,統稱為非客戶區域

 

那麼WindowChrome的作用是,將客戶區域擴充套件至整個表單(遮住了非客戶區),同時提供部分標準表單的功能。如下所示:

<Window x:Class="ControlTest.WindowNone"
        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:ControlTest"
        mc:Ignorable="d"
        Title="WindowNone" Height="450" Width="800">
    
    <!-- WindowChrome將客戶區域擴充套件至整個表單,並遮住標題列、按鈕等-->
    <WindowChrome.WindowChrome>
        <WindowChrome />
    </WindowChrome.WindowChrome>
    
    <Grid>
        <TabControl>
            <TabItem Header="專案"/>
            <TabItem Header="程式碼"/>
        </TabControl>
    </Grid>
</Window>

   備註:這裡的邊框,是TabControl的邊框,不是表單的邊框。

 

 用上WindowChrome後,會驚奇的發現:在原標題列的位置,可以用滑鼠拖動了;在表單的四周,可以調整表單的大小了!Amazing!

但同時,又出現了一個新的問題:表單中的所以內容,都不能互動(滑鼠點選,使用者輸入)了。

這是為什麼呢?可以這樣理解。WindowChrome就像一個圖層,它將表單整個覆蓋住了。因此表單上的內容,自然就操作不了。那要如何才能點選呢?

這需要給互動控制元件,新增WindowChrome的附件屬性:IsHitTestVisibleInChrome。如下所示。

    <Grid>
        <!-- 使用WindowChrome的附件屬性 -->
        <TabControl WindowChrome.IsHitTestVisibleInChrome="True">
            <TabItem Header="專案"/>
            <TabItem Header="程式碼"/>
        </TabControl>
    </Grid>

  如果你以為這樣就萬事大吉了,那隻能說太天真了,微軟的東西,哪有那麼簡單的呢??哈哈~ 

  如果真的按照這個程式碼,你會發現,又不能使用滑鼠拖動表單了。這是為什麼呢?明明之前都可以,為何為控制元件新增了一個附加屬性後,就不行了呢?

  問題肯定出在WindowChrome上。那麼我們再來看看WindowChrome:

圖中有顏色的區域,實際上均為透明的看不見的。此處附上顏色則是為了方便解釋。

 

這個圖就是WindowChrome的模型。其中Caption區域,表示標題列,就是它,允許表單被滑鼠拖動。GlassFrameThickness就是Aero表單的透明邊框(Aero主體只在部分作業系統中支援)。ResizeBorderThickness就是調整表單大小的邊框的粗細,它提供了使用滑鼠調整表單大小的功能。而CornerRadius,則將表單變成了圓角,它只有在GlassFrameThickness = 0 或者未啟用Aero主體的視窗中才有效。。

再回到上面的問題,為什麼新增了附加屬性,就不能用滑鼠拖動表單了呢?

原因在於,TabControl進入了Caption區域。因為設定了附加屬性(IsHitTestVisibleInChrome),表示滑鼠可以「擊穿」WindowChrome,那麼自然就無法「點選」到Caption區域,自然就無法拖動表單了。

那麼如果解決這個問題呢?以及如何新增按鈕呢?答案是手動新增標題列。哈哈~ 如下程式碼所示:

Xaml程式碼:

<Window x:Class="ControlTest.WindowNone"
        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:ControlTest"
        mc:Ignorable="d"
        Title="WindowNone" Height="450" Width="800">
    
    <!-- WindowChrome將客戶區域擴充套件至整個表單,並遮住標題列、按鈕等 -->
    <WindowChrome.WindowChrome>
     <!-- 設定了標題列的高度 = 30,圓角 = 20 --> <WindowChrome CaptionHeight="30" CornerRadius="20" GlassFrameThickness="0"/> </WindowChrome.WindowChrome> <Border BorderThickness="1"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Border Height="30" Background="YellowGreen"> <Grid> <Grid.Resources> <Style TargetType="Button"> <Setter Property="Width" Value="30"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderThickness" Value="0"/> </Style> </Grid.Resources> <StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True"> <Image /> <TextBlock VerticalAlignment="Center" Margin="3,0" Text="{Binding Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/> </StackPanel> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True"> <Button Content="_" Click="Btn_Min"/> <Button Content="Max" Click="Btn_Max"/> <Button Content="X" Click="Btn_Close"/> </StackPanel> </Grid> </Border> <!-- 使用WindowChrome的附件屬性 --> <TabControl Grid.Row="1" WindowChrome.IsHitTestVisibleInChrome="True"> <TabItem Header="專案"/> <TabItem Header="程式碼"/> </TabControl> </Grid> </Border> </Window> C# 程式碼: public partial class WindowNone : Window { public WindowNone() { InitializeComponent(); }
// 最小化 private void Btn_Min(object sender, RoutedEventArgs e) { this.WindowState = WindowState.Minimized; }
  // 最大化、還原 private void Btn_Max(object sender, RoutedEventArgs e) { if(this.WindowState == WindowState.Normal) { this.WindowState = WindowState.Maximized; } else { this.WindowState = WindowState.Normal; } }
  // 關閉表單 private void Btn_Close(object sender, RoutedEventArgs e) { this.Close(); } }

 

手動新增了標題列之後,在標題列上,你就可以放上任何你放的東西。。。。

 

二、使用WindowStyle = "None"

將表單的WindowStyle屬性設定為None後,表單呈現這樣:

<Window x:Class="ControlTest.NoneWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="NoneWindow" Height="450" Width="800"
        WindowStyle="None">
    <Grid>
        <TabControl>
            <TabItem Header="專案"/>
            <TabItem Header="程式碼"/>
        </TabControl>
    </Grid>
</Window>

  

這裡,你會發現,表單可以通過滑鼠調整大小,但是不能用滑鼠拖動。那解決的辦法是什麼呢?同樣是手動設定一個標題列: 

Xaml 程式碼:
<Window x:Class="ControlTest.NoneWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="NoneWindow" Height="450" Width="800"
        WindowStyle="None" BorderThickness="0" BorderBrush="Transparent">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Border Height="30" Background="YellowGreen"
                MouseDown="TitleMove">
            <Grid>
                <Grid.Resources>
                    <Style TargetType="Button">
                        <Setter Property="Width" Value="30"/>
                        <Setter Property="Background" Value="Transparent"/>
                        <Setter Property="BorderThickness" Value="0"/>
                    </Style>
                </Grid.Resources>
                <StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
                    <Image />
                    <TextBlock VerticalAlignment="Center" Margin="3,0" Text="{Binding Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
                </StackPanel>

                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True">
                    <Button Content="_" Click="Btn_Min"/>
                    <Button Content="Max" Click="Btn_Max"/>
                    <Button Content="X" Click="Btn_Close"/>
                </StackPanel>
            </Grid>
        </Border>
        <TabControl Grid.Row="1" Margin="10">
            <TabItem Header="專案"/>
            <TabItem Header="程式碼"/>
        </TabControl>
    </Grid>
</Window>

C# 程式碼:

    public partial class NoneWindow : Window
    {
        public NoneWindow()
        {
            InitializeComponent();
        }

        // 表單移動
        private void TitleMove(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton != MouseButton.Left) return;            // 非左鍵點選,退出
            if (e.ClickCount == 1)
            {
                this.DragMove();                                        // 拖動表單
            }
            else
            {
                WindowMax();                                            // 雙擊時,最大化或者還原表單
            }
        }


        // 最小化
        private void Btn_Min(object sender, RoutedEventArgs e)
        {
            this.WindowState = WindowState.Minimized;
        }

        

        // 關閉表單
        private void Btn_Close(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        // 最大化、還原
        private void Btn_Max(object sender, RoutedEventArgs e)
        {
            WindowMax();
        }

        private void WindowMax()
        {
            if (this.WindowState == WindowState.Normal)
            {
                this.WindowState = WindowState.Maximized;
            }
            else
            {
                this.WindowState = WindowState.Normal;
            }
        }

        
    }

  

這種方式下,會發現在表單的「標題列」上面,還有一點留白無法去除,同樣表單的邊框也是無法去除的。

如果解決?且聽下回分解。