上一篇文章寫了如何建立自定義表單:使用 WindowChrome 或者 WindowStyle=「None」這兩種方式。本文將講述如何設定表單的效果(以陰影效果為例),以及在效果模式下,表單各功能的配合。
表單的範圍,就是白色區域部分:包括表單的邊框,標題列,以及內部的空白部分。出了白色範圍,不再屬於表單,且表單也不能影響到白色區域意外的地方。理解這一點很重要!
要設定透明表單,比較簡單,要同時設定三個屬性:
<Window x:Class="ControlTest.EffectNoneWindow"
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="EffectNoneWindow" Height="450" Width="800"
AllowsTransparency="True" Background="Transparent" WindowStyle ="None">
<Grid>
</Grid>
</Window>
注意:當AllowTransparency = 「True」時,WindowStyle的值必須為None。
這個截圖就是表單。因為它是透明的,所以直接看到了桌面(注意看頂部的偵錯工具條)。
<Window x:Class="ControlTest.EffectNoneWindow" 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="EffectNoneWindow" Height="450" Width="800" AllowsTransparency="True" Background="Transparent" WindowStyle ="None"> <Border Margin="20" Background="White"> <Border.Effect> <DropShadowEffect Direction="0" BlurRadius="20" ShadowDepth="0" Color="#FF585252" /> </Border.Effect> </Border> </Window>
注意,最外圈的灰色部分(圖中標1的地方)是桌面(它是微信截圖的時候產生的);圖中標2的青色框,是表單的實際邊框,它與白色部分(圖中標3的地方)之間的空間,就是Border的Margin。為什麼要這個Margin?
因為Border有一個Effect。如果沒有這個Margin,Effect沒有地方放置(因為任何表單的效果、元素,都不能超出表單的區域)。
你以為到這裡文章就結束了?No No No,讓我們新增功能之後再看看。把上一篇文章中,關於標題列,按鈕的功能加上。
Xaml程式碼: <Window x:Class="ControlTest.EffectNoneWindow" 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="EffectNoneWindow" Height="450" Width="800" AllowsTransparency="True" Background="Transparent" WindowStyle ="None"> <Border Margin="20" Background="White"> <Border.Effect> <DropShadowEffect Direction="0" BlurRadius="20" ShadowDepth="0" Color="#FF585252" /> </Border.Effect> <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> </Border> </Window> C#程式碼:(此處不包含調整表單大小的程式碼。具體請看上一篇文章的末尾) // 表單移動 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; } }
大部分功能完好。可以用滑鼠在標題上拖動表單,雙擊標題列放大、縮小,最小化、關閉按鈕工作正常。唯獨最大化按鈕的效果不是我們想要的!
問題:1. 最大化之後,它的四周與螢幕邊緣還有空白;2. 它遮住了桌面的工作列。
這才是要解決的問題的關鍵!這個問題的產生,還是「表單的空間範圍」引起的:
1. 因為最大化的是表單,而表單包括了最外層Border的Margin,所以最大化之後,Margin依然是存在的,四周與螢幕之間自然就有了空白。
2. 因為沒有作業系統賦予的「視窗邊框」,表單也就不知道了螢幕可用範圍大小,只能是儘可能佔據全部螢幕,因此就遮住了桌面的工作列。
要解決這兩個問題,只需要做兩個設定。且看如下程式碼(最重要的是OnStateChanged()方法):
Xaml程式碼: <!-- 設定最外層Border的Name屬性 --> <Border Name="bd" Margin="20" Background="White"> …… </Border> C#程式碼: public partial class EffectNoneWindow : Window { Thickness originMargin; // 記錄bd的初始Margin值 Thickness zeroMargin = new Thickness(5); // 設定當表單最大化之後,bd的Margin值。該值本應該為0,但是實際上最大化之後會有點「吃」表單,因此設定為5或者其他數值,以求更好的視覺效果。 public EffectNoneWindow() { InitializeComponent();
// 註冊事件。如此當拖動滑鼠到螢幕的邊緣時,Windows系統會將表單最大化,程式也能正常工作。 this.StateChanged += OnStateChanged; // 設定表單最大化時的最大高度 this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight; this.originMargin = this.bd.Margin; } private void OnStateChanged(object? sender, EventArgs e) { if(this.WindowState == WindowState.Normal) { // Normal狀態時,將bd的Margin設定為初始值 this.bd.Margin = this.originMargin; } else { // 最大化時,將bd的Margin設定為0; this.bd.Margin = zeroMargin; } } // 表單移動 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; } } }
你以為這就完事了?太天真了,微軟的東西哪有這麼簡單?它總是會給你埋坑的!
以上程式碼,在點選「最大化」按鈕、用滑鼠把表單拖到螢幕頂端(系統賦予的最大化操作)時,都OK,沒問題。但如果把表單用滑鼠拖到螢幕的左側、右側、螢幕的四個角時,系統會讓表單佔據部分螢幕(類似於分屏)時,表單呈現的效果如下:
表單高度OK了,但是Margin錯了!
因此,還要修改。程式碼如下:
public partial class EffectNoneWindow : Window { Thickness originMargin; Thickness margin5 = new Thickness(8); Thickness marginTopLeft; Thickness marginTopRight; Thickness marginBottomLeft; Thickness marginBottomRight; Thickness marginLeft; Thickness marginRight; double maxWidth = SystemParameters.MaximizedPrimaryScreenWidth; double maxHeight = SystemParameters.MaximizedPrimaryScreenHeight; public EffectNoneWindow() { InitializeComponent(); //不再使用StateChanged事件,而使用SizeChanged事件 this.SizeChanged += EffectNoneWindow_SizeChanged; // 設定表單最大化時的最大高度 MaxHeight = maxHeight; originMargin = bd.Margin; marginTopLeft = new Thickness(0, 0, originMargin.Right, originMargin.Bottom); marginTopRight = new Thickness(originMargin.Left, 0, 0, originMargin.Bottom); marginBottomLeft = new Thickness(0, originMargin.Top, originMargin.Right, 0); marginBottomRight = new Thickness(originMargin.Left, originMargin.Top, 0, 0); marginLeft = new Thickness(0, 0, originMargin.Right, 0); marginRight = new Thickness(originMargin.Left, 0, 0, 0); } // 尺寸發生改變時: private void EffectNoneWindow_SizeChanged(object sender, SizeChangedEventArgs e) { // 1. 判斷是否最大化: if(Math.Abs(this.ActualWidth- maxWidth)<4) // 此處用絕對值判斷,是因為表單的大小,不會剛剛好是最大值。下同。 { bd.Margin = margin5; return; } // 2. 判斷是否分屏: if(Math.Abs(this.Height-maxHeight)<20) { // 在表單的左側: bd.Margin = Left == 0 ? marginLeft : marginRight; //Height = maxHeight; return; } // 3. 判斷是否四分屏: if(Math.Abs(this.Width - maxWidth/2)<20 && Math.Abs(this.Height - maxHeight/2)<20) { if(Left == 0) { if(Top ==0) { // 左上角 bd.Margin = marginTopLeft; } else { bd.Margin = marginBottomLeft; } } else { if(Top == 0) { bd.Margin = marginTopRight; } else { bd.Margin = marginBottomRight; } } return; } this.bd.Margin = originMargin; } private void OnStateChanged(object? sender, EventArgs e) { if(this.WindowState == WindowState.Normal) { // Normal狀態時,將bd的Margin設定為初始值 this.bd.Margin = this.originMargin; } else { // 最大化時,將bd的Margin設定為0; this.bd.Margin = margin5; } } // 表單移動 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; } } }
此處,最主要的是EffectNoneWindow_SizeChanged()方法。當表單的尺寸發生改變時,它會判斷表單在哪個位置,是最大化、兩分屏,還是四分屏,並對Margin進行相應的賦值。
至此,一個功能齊全的自定義表單就搞定了。要改換效果,則只需要將Xaml程式碼中的Effect替換即可。
有些時候,我們不希望表單能調整大小,比如初始視窗,僅僅只是在載入的時候顯示,程式載入完之後就關閉了。
如果不希望表單能調整大小,只需設定Windows的ResizeMode = 「NoResize」即可。