1. 程式人生 > >WPF中自定義標題欄時窗體最大化處理之WindowChrome

WPF中自定義標題欄時窗體最大化處理之WindowChrome

注意:

  • 本文方法基礎是WindowChrome,而WindowChrome在.NET Framework 4.5之後才整合釋出的。見:WindowChrome Class
  • .NET Framework 4.0中使用WindowChrome,需要安裝Ribbon來支援WindowChrome
  • 目前官方文件的內容較為陳舊(但仍有參考價值),其中提到了SystemParameters2,這個應該是Ribbon裡的東西,4.5想用可以安裝Xceed.Wpf.AvalonDock庫,這裡面有現成的Microsoft.Windows.Shell.SystemParameters2
    實現——當然,自己不怕麻煩,可以自己實現來獲取要用的系統變數。(一般用不著,4.5SystemParameters添加了許多系統變數的實現)

實現步驟

第一步:基本實現【保留系統基礎操作按鈕】

  • 新增Window的Style定義,並設定WindowChrome.WindowChrome屬性;
  • 設定WindowChrome標題欄:
    • CaptionHeight——主要用於拖動有效區;
    • GlassFrameThickness——影響標題欄系統按鈕顯示,0表示不使用系統按鈕【後面介紹】,-1表示用的系統預設值,如下示例則表示標題欄高度30;
    • 自定義窗體Title

注意:控制元件模板中的定義:

  • 1、最外層Border背景無顏色,否則會覆蓋標題欄,看不到系統按鈕。
  • 2、內部佈局定義,使用Grid隔出30的標題欄高度,也可以直接對ContentPresenter設定Margin【主要是為了讓頂部顯示出標題欄】。
    <Style x:Key="WindowStyle1" TargetType="{x:Type Window}">
        <Setter Property="WindowChrome.WindowChrome">
            <Setter.Value>
                <WindowChrome CaptionHeight="30" GlassFrameThickness="0,30,0,0"/>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" >
                        <AdornerDecorator  >
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="30"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>
                                <ContentPresenter Grid.Row="1"/>
                                <TextBlock Text="{TemplateBinding Title}" HorizontalAlignment="Left" VerticalAlignment="Center" />
                            </Grid>
                        </AdornerDecorator>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

存在的問題: 最大化邊框問題 —— 會有部分溢位。

根據觀察,這個溢位寬度應該是8,此值的來源:(SystemParameters.MaximizedPrimaryScreenWidth - SystemParameters.WorkArea.Width)/2 ,高度也可以這樣計算。

第二步:優化邊界處理

  • 方法1:模板新增最大化觸發器,設定最大化時,內部佈局Margin設為8
  • 方法2:模板新增最大化觸發器,設定最大化時,限制佈局最大化的寬高最大值

不管使用哪種方法,最大化時,系統的標題欄高度都發生了變化,故,需要重新設定模板中定義的標題欄高度。

    <ControlTemplate TargetType="{x:Type Window}">
        <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" >
            <AdornerDecorator  >
                <Grid Name="win_content">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="30" x:Name="row_title"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <ContentPresenter Grid.Row="1"/>
                    <TextBlock Text="{TemplateBinding Title}" HorizontalAlignment="Left" VerticalAlignment="Center" />
                </Grid>
            </AdornerDecorator>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="WindowState" Value="Maximized">
                <Setter Property="Margin" TargetName="win_content" Value="8"/>
                <!--二選一-->
                <Setter Property="MaxWidth" TargetName="win_content" Value="{Binding Source={x:Static SystemParameters.WorkArea},Path=Width}" />
                <Setter Property="MaxHeight" TargetName="win_content" Value="{Binding Source={x:Static SystemParameters.WorkArea},Path=Height}"/>
        
                <Setter Property="Height" TargetName="row_title" Value="22" />
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

至此,都是系統標題欄和我們自定義內容組合使用,但這樣總有些邊邊角角要修正,下面就完全自定義標題欄

第三步:完全自定義標題欄【即,不使用系統的操作按鈕】

  • 初步操作類似第一步,其中將GlassFrameThickness設定為0
  • 在內容定義部分新增自定義的標題欄,新增操作按鈕,並設定按鈕屬性WindowChrome.IsHitTestVisibleInChrome="True"

如果不設定WindowChrome.IsHitTestVisibleInChrome,則由於我們之前設定CaptionHeight,則這個區域內,按鈕將失效。
但是,也不能將整個標題欄佈局設定這個屬性,那樣會完全覆蓋系統標題欄的操作,如拖動效果,即CaptionHeight設定的那個區域。

    <!--樣式定義-->
    <Style x:Key="WindowStyle2" TargetType="{x:Type Window}">
        <Setter Property="WindowChrome.WindowChrome">
            <Setter.Value>
                <WindowChrome UseAeroCaptionButtons="False" GlassFrameThickness="0" CaptionHeight="30"  />
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"  >
                        <AdornerDecorator >
                            <ContentPresenter x:Name="win_content" />
                        </AdornerDecorator>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="WindowState" Value="Maximized">
                            <Setter Property="Margin" TargetName="win_content" Value="8"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <!--定義標題欄-->
    <Grid Background="Red" >
        <Grid Height="30" HorizontalAlignment="Stretch" VerticalAlignment="Top" Background="Blue">
            <StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True" HorizontalAlignment="Right" >
                <Button Name="btn_Min" Content="—" ></Button>
                <Button Name="btn_Max" Content="☐" ></Button>
                <Button Name="btn_Close" Content="✕" ></Button>
            </StackPanel>
        </Grid>
    </Grid>
    //標題欄按鈕功能實現
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.btn_Min.Click += Btn_Min_Click;
            this.btn_Max.Click += Btn_Max_Click;
            this.btn_Close.Click += Btn_Close_Click;
        }

        private void Btn_Close_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        private void Btn_Max_Click(object sender, RoutedEventArgs e)
        {
            this.WindowState = WindowState.Maximized == this.WindowState ? WindowState.Normal : WindowState.Maximized;
        }

        private void Btn_Min_Click(object sender, RoutedEventArgs e)
        {
            this.WindowState = WindowState.Minimized;
        }
    }

關聯瞭解:
如果你平時也使用Visual Studio Code【VSCode】,文中的第一步和第三步,在vscode的設定中有對應效果:

  • 第一步 —— 將vscode使用者設定中Title Bar Style值設為native;
  • 第三步 —— 將vscode使用者設定中Title Bar Style值設為custom。

相關下載

點選檢視完整原始碼

解:奇葩史


落後要捱打,個人以前一直侷限於老舊方式實現窗體處理,沒有跟進WPF技術改進,在此,非常感謝@vbfool的提醒。【之前說週末搞出來,自己這幾周浪了,沒研究。今天趁著別人雙11買買買,咱就擼擼程式碼】