雖然說自Windows Vista採用了AERO之後,Windows作業系統中應用程式預設的視窗就美觀了不少,但是對於喜歡應用程式有整體美的設計師來說,使用預設的視窗外觀對某些WPF應用程式來說,或許還是有可能造成破壞整體設計感的情況發生。所以,把預設的視窗外觀拔掉,讓設計師們更能展現設計的才能,針對不同的應用程式設計出不同的視窗外觀,想像起來是不是蠻美好的一件事呢?
雖然說自Windows Vista採用了AERO之後,Windows作業系統中應用程式預設的視窗就美觀了不少,但是對於喜歡應用程式有整體美的設計師來說,使用預設的視窗外觀對某些WPF應用程式來說,或許還是有可能造成破壞整體設計感的情況發生。所以,把預設的視窗外觀拔掉,讓設計師們更能展現設計的才能,針對不同的應用程式設計出不同的視窗外觀,想像起來是不是蠻美好的一件事呢?
以下就來跟各位分享使用WPF實作出自定外觀視窗的方法。這次的目標是做出如下的不規則外框視窗:
為了方便練習,請先使用Expression Blend建立一個標準的WPF應用程式,接著針對MainWindow.xaml做以下的調整:
- 勾選AllowsTransparence。
- 將WindowStyle改為None。
- 將ResizeMode改為NoResize。
做完上述三個步驟的話,我們會得到一個透明背景而且沒有外框的視窗。
接著就可以來打造我們自己要的視窗長相啦! 一般的視窗的Title區是可以供使用者拖拉進行視窗搬移的區域,還有用來縮小、放大、關閉視窗的按鈕也會放在這邊(當然,也可以發揮創意把按鈕放在別的地方)。
在這邊就順便分享一個簡單就可以做出透明自訂視窗的Title的偷吃步,有興趣又很懶的朋友們可以跟我一樣將MainWindow.xaml的LayoutRoot改為如下:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
mc:Ignorable="d" x:Class="Wpf_CustomWindow.MainWindow" x:Name="Window" Title="MainWindow" Width="640"
Height="480" WindowStyle="None" Background="{x:Null}" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
AllowsTransparency="True" MinWidth="400" MinHeight="300">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<!-- 第一個Row使用固定的高度,因為Title列通常不需要隨著視窗大小縮放 -->
<RowDefinition Height="40" />
<!-- 第二個Row用來放置視窗的內容,就把剩餘的空間都給它吧 -->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 透過設定Border的邊框,就可以做出自訂的TitleBar的感覺啦!! 另外,透過MouseLeftButtonDown的EventHandler去處理視窗的拖拉移動功能 -->
<Border x:Name="bdrWindowTitle" BorderBrush="Black" BorderThickness="2,2,2,0" CornerRadius="10,10,0,0"
Margin="0,10,0,0" Background="White" MouseLeftButtonDown="bdrWindowTitle_MouseLeftButtonDown">
<Grid x:Name="grdTitleContent" Margin="10,10,10,0">
<StackPanel x:Name="stkTitle" Orientation="Horizontal" d:LayoutOverrides="Height" Margin="0,-20,0,0"
HorizontalAlignment="Left">
<TextBlock x:Name="textBlock" TextWrapping="Wrap" Text="W" VerticalAlignment="Bottom" FontSize="48"
FontWeight="Bold" FontFamily="Arial Black" d:LayoutOverrides="Width" Margin="0,-20,0,-5"
Foreground="White">
<TextBlock.Effect>
<DropShadowEffect ShadowDepth="3" BlurRadius="2" Opacity="0.5" />
</TextBlock.Effect></TextBlock>
<TextBlock TextWrapping="Wrap" Text="indowTitle" VerticalAlignment="Bottom" FontSize="26.667"
FontWeight="Bold" FontFamily="Arial Black">
<TextBlock.Effect>
<DropShadowEffect ShadowDepth="3" BlurRadius="2" Opacity="0.5" />
</TextBlock.Effect></TextBlock>
</StackPanel>
<StackPanel x:Name="stkButtonBar" HorizontalAlignment="Right" Orientation="Horizontal" Margin="0,-5,0,0">
<!-- 最小化按鈕 -->
<Button x:Name="btnMinimize" Content="-" Margin="10,0,0,0" Width="28" Click="btnMinimize_Click" />
<!-- 最大化按鈕 -->
<Button x:Name="btnMaximize" Content="+" Margin="10,0,0,0" Width="28" Click="btnMaximize_Click" />
<!-- 關閉視窗鈕 -->
<Button x:Name="btnClose" Content="X" Margin="10,0,0,0" Width="28" Click="btnClose_Click" />
</StackPanel>
</Grid>
</Border>
<Border x:Name="bdrWindowContent" BorderBrush="Black" BorderThickness="2,0,2,2" Grid.Row="1"
CornerRadius="0,0,10,10" Background="White">
</Border>
</Grid>
</Window>
接著來看看CodeBehind的部份:
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace Wpf_CustomWindow
{
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
private void bdrWindowTitle_MouseLeftButtonDown( object sender , MouseButtonEventArgs e )
{
this.DragMove();
}
private void btnClose_Click( object sender , RoutedEventArgs e )
{
this.Close();
}
private void btnMaximize_Click( object sender , RoutedEventArgs e )
{
this.WindowState = ( this.WindowState != WindowState.Maximized ) ? WindowState.Maximized : WindowState.Normal;
}
private void btnMinimize_Click( object sender , RoutedEventArgs e )
{
this.WindowState = WindowState.Minimized;
}
}
}
OK!!到這邊為止,我們就有一個可以拖拉移動的視窗啦!!
接下來,就是另一個重頭戲了!! 視窗只能移動當然是不夠的啊!! 還得要能任意的放大縮小才行!! 這時候,就要藉助Thumb這個控制項來幫我們完成後面艱巨的任務啦!!
為了實作出和一般的視窗一樣,能讓使用者拖拉視窗的外緣來調整視窗大小的功能,我們要在將八個Thumb分別放置在自訂視窗邊緣的八個位置,並且妥善命名(在CodeBehind會用到),另外,也順便設定每個Thumb所要使用的滑鼠游標:
八個Thumb放置妥當之後,就可以將它們的Opacity屬性設為0%,因為我們不希望在執行的時候它們會被顯示在畫面上;並且為每個Thumb的完成後的Xaml如下:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
mc:Ignorable="d" x:Class="Wpf_CustomWindow.MainWindow" x:Name="Window" Title="MainWindow" Width="640"
Height="480" WindowStyle="None" Background="{x:Null}" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
AllowsTransparency="True" MinWidth="400" MinHeight="300">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<!-- 第一個Row使用固定的高度,因為Title列通常不需要隨著視窗大小縮放 -->
<RowDefinition Height="40" />
<!-- 第二個Row用來放置視窗的內容,就把剩餘的空間都給它吧 -->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 透過設定Border的邊框,就可以做出自訂的TitleBar的感覺啦!! -->
<Border x:Name="bdrWindowTitle" BorderBrush="Black" BorderThickness="2,2,2,0" CornerRadius="10,10,0,0"
Margin="0,10,0,0" Background="White" MouseLeftButtonDown="bdrWindowTitle_MouseLeftButtonDown">
<Grid x:Name="grdTitleContent" Margin="10,10,10,0">
<StackPanel x:Name="stkTitle" Orientation="Horizontal" d:LayoutOverrides="Height" Margin="0,-20,0,0"
HorizontalAlignment="Left">
<TextBlock x:Name="textBlock" TextWrapping="Wrap" Text="W" VerticalAlignment="Bottom" FontSize="48"
FontWeight="Bold" FontFamily="Arial Black" d:LayoutOverrides="Width" Margin="0,-20,0,-5"
Foreground="White">
<TextBlock.Effect>
<DropShadowEffect ShadowDepth="3" BlurRadius="2" Opacity="0.5" />
</TextBlock.Effect></TextBlock>
<TextBlock TextWrapping="Wrap" Text="indowTitle" VerticalAlignment="Bottom" FontSize="26.667"
FontWeight="Bold" FontFamily="Arial Black">
<TextBlock.Effect>
<DropShadowEffect ShadowDepth="3" BlurRadius="2" Opacity="0.5" />
</TextBlock.Effect></TextBlock>
</StackPanel>
<StackPanel x:Name="stkButtonBar" HorizontalAlignment="Right" Orientation="Horizontal" Margin="0,-5,0,0">
<!-- 最小化按鈕 -->
<Button x:Name="btnMinimize" Content="-" Margin="10,0,0,0" Width="28" Click="btnMinimize_Click" />
<!-- 最大化按鈕 -->
<Button x:Name="btnMaximize" Content="+" Margin="10,0,0,0" Width="28" Click="btnMaximize_Click" />
<!-- 關閉視窗鈕 -->
<Button x:Name="btnClose" Content="X" Margin="10,0,0,0" Width="28" Click="btnClose_Click" />
</StackPanel>
</Grid>
</Border>
<Border x:Name="bdrWindowContent" BorderBrush="Black" BorderThickness="2,0,2,2" Grid.Row="1"
CornerRadius="0,0,10,10" Background="White">
</Border>
<Thumb x:Name="thumbTop" Height="5" Margin="15,10,15,0" VerticalAlignment="Top" Background="Red"
Grid.RowSpan="2" Cursor="SizeNS" Opacity="0" />
<Thumb x:Name="thumbTopRight" HorizontalAlignment="Right" Height="15" Margin="0,10,0,0" VerticalAlignment="Top"
Width="15" Background="Green" Grid.RowSpan="2" Cursor="SizeNESW" Opacity="0" />
<Thumb x:Name="thumbRight" HorizontalAlignment="Right" Margin="0,25,0,15" Width="5" Background="Red"
Grid.RowSpan="2" Cursor="SizeWE" Opacity="0" />
<Thumb x:Name="thumbBottomRight" HorizontalAlignment="Right" Height="15" Margin="0" VerticalAlignment="Bottom"
Width="15" Background="Green" Grid.RowSpan="2" Cursor="SizeNWSE" Opacity="0" />
<Thumb x:Name="thumbBottom" Height="5" Margin="15,0" VerticalAlignment="Bottom" Background="Red"
Grid.RowSpan="2" Cursor="SizeNS" Opacity="0" />
<Thumb x:Name="thumbBottomLeft" HorizontalAlignment="Left" Height="15" Margin="0" VerticalAlignment="Bottom"
Width="15" Background="Green" Grid.RowSpan="2" Cursor="SizeNESW" Opacity="0" />
<Thumb x:Name="thumbLeft" HorizontalAlignment="Left" Margin="0,25,0,15" Width="5" Background="Red"
Grid.RowSpan="2" Cursor="SizeWE" Opacity="0" />
<Thumb x:Name="thumbTopLeft" HorizontalAlignment="Left" Height="15" Margin="0,10,0,0" VerticalAlignment="Top"
Width="15" Background="Green" Grid.RowSpan="2" Cursor="SizeNWSE" Opacity="0" />
</Grid>
</Window>
最後,我們就要在CodeBehind來撰寫處理視這八個Thumb被拖放時的EventHandler啦!! 而在這邊,我們得藉助Windows API和Com元件來完成,所以請記得加入System.Runtime.InteropServices及System.Windows.Interop這兩個Namespace的引用。
接著請服用以下的程式碼:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace Wpf_CustomWindow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private const int WM_SYSCOMMAND = 0x112;
private HwndSource hwndSource;
IntPtr retInt = IntPtr.Zero;
public MainWindow()
{
this.InitializeComponent();
this.SourceInitialized += new System.EventHandler( MainWindow_SourceInitialized );
}
void MainWindow_SourceInitialized( object sender , System.EventArgs e )
{
hwndSource = PresentationSource.FromVisual( ( Visual ) sender ) as HwndSource;
hwndSource.AddHook( new HwndSourceHook( WndProc ) );
}
private IntPtr WndProc( IntPtr hwnd , int msg , IntPtr wParam , IntPtr lParam , ref bool handled )
{
Debug.WriteLine( "WndProc messages: " + msg.ToString() );
if( msg == WM_SYSCOMMAND )
{
Debug.WriteLine( "WndProc messages: " + msg.ToString() );
}
return IntPtr.Zero;
}
public enum ResizeDirection
{
Left = 1 ,
Right = 2 ,
Top = 3 ,
TopLeft = 4 ,
TopRight = 5 ,
Bottom = 6 ,
BottomLeft = 7 ,
BottomRight = 8 ,
}
[DllImport( "user32.dll" , CharSet = CharSet.Auto )]
private static extern IntPtr SendMessage( IntPtr hWnd , uint Msg , IntPtr wParam , IntPtr lParam );
private void ResizeWindow( ResizeDirection direction )
{
SendMessage( hwndSource.Handle , WM_SYSCOMMAND , ( IntPtr ) ( 61440 + direction ) , IntPtr.Zero );
}
private void ResetCursor( object sender , MouseEventArgs e )
{
if( Mouse.LeftButton != MouseButtonState.Pressed )
{
this.Cursor = Cursors.Arrow;
}
}
private void thumb_PreviewMouseLeftButtonDown( object sender , MouseButtonEventArgs e )
{
Thumb thumb = sender as Thumb;
//這邊要配合Thumb的命名來取處理,如果Thumb的命名和我的不同,請自行修改下面的程式內容。
switch( thumb.Name.Substring( 5 ) )
{
case "Top":
ResizeWindow( ResizeDirection.Top );
break;
case "Bottom":
ResizeWindow( ResizeDirection.Bottom );
break;
case "Left":
ResizeWindow( ResizeDirection.Left );
break;
case "Right":
ResizeWindow( ResizeDirection.Right );
break;
case "TopLeft":
ResizeWindow( ResizeDirection.TopLeft );
break;
case "TopRight":
ResizeWindow( ResizeDirection.TopRight );
break;
case "BottomLeft":
ResizeWindow( ResizeDirection.BottomLeft );
break;
case "BottomRight":
ResizeWindow( ResizeDirection.BottomRight );
break;
default:
break;
}
}
private void bdrWindowTitle_MouseLeftButtonDown( object sender , MouseButtonEventArgs e )
{
this.DragMove();
}
private void btnClose_Click( object sender , RoutedEventArgs e )
{
this.Close();
}
private void btnMaximize_Click( object sender , RoutedEventArgs e )
{
this.WindowState = ( this.WindowState != WindowState.Maximized ) ? WindowState.Maximized : WindowState.Normal;
}
private void btnMinimize_Click( object sender , RoutedEventArgs e )
{
this.WindowState = WindowState.Minimized;
}
}
}
最後一步,將我們的八個Thumb控制項的PreviewMouseLeftButtonDown事件指定給thumb_PreviewMouseLeftButtonDown這個EventHandler來處理,就大功告成啦!!
完成後的Xaml如下:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
mc:Ignorable="d" x:Class="Wpf_CustomWindow.MainWindow" x:Name="Window" Title="MainWindow" Width="640"
Height="480" WindowStyle="None" Background="{x:Null}" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
AllowsTransparency="True" MinWidth="400" MinHeight="300">
<Window.Resources></Window.Resources>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<!-- 第一個Row使用固定的高度,因為Title列通常不需要隨著視窗大小縮放 -->
<RowDefinition Height="40" />
<!-- 第二個Row用來放置視窗的內容,就把剩餘的空間都給它吧 -->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 透過設定Border的邊框,就可以做出自訂的TitleBar的感覺啦!! -->
<Border x:Name="bdrWindowTitle" BorderBrush="Black" BorderThickness="2,2,2,0" CornerRadius="10,10,0,0"
Margin="0,10,0,0" Background="White" MouseLeftButtonDown="bdrWindowTitle_MouseLeftButtonDown">
<Grid x:Name="grdTitleContent" Margin="10,10,10,0">
<StackPanel x:Name="stkTitle" Orientation="Horizontal" d:LayoutOverrides="Height" Margin="0,-20,0,0"
HorizontalAlignment="Left">
<TextBlock x:Name="textBlock" TextWrapping="Wrap" Text="W" VerticalAlignment="Bottom" FontSize="48"
FontWeight="Bold" FontFamily="Arial Black" d:LayoutOverrides="Width" Margin="0,-20,0,-5"
Foreground="White">
<TextBlock.Effect>
<DropShadowEffect ShadowDepth="3" BlurRadius="2" Opacity="0.5" />
</TextBlock.Effect></TextBlock>
<TextBlock TextWrapping="Wrap" Text="indowTitle" VerticalAlignment="Bottom" FontSize="26.667"
FontWeight="Bold" FontFamily="Arial Black">
<TextBlock.Effect>
<DropShadowEffect ShadowDepth="3" BlurRadius="2" Opacity="0.5" />
</TextBlock.Effect></TextBlock>
</StackPanel>
<StackPanel x:Name="stkButtonBar" HorizontalAlignment="Right" Orientation="Horizontal" Margin="0,-5,0,0">
<!-- 最小化按鈕 -->
<Button x:Name="btnMinimize" Content="-" Margin="10,0,0,0" Width="28" Click="btnMinimize_Click" />
<!-- 最大化按鈕 -->
<Button x:Name="btnMaximize" Content="+" Margin="10,0,0,0" Width="28" Click="btnMaximize_Click" />
<!-- 關閉視窗鈕 -->
<Button x:Name="btnClose" Content="X" Margin="10,0,0,0" Width="28" Click="btnClose_Click" />
</StackPanel>
</Grid>
</Border>
<Border x:Name="bdrWindowContent" BorderBrush="Black" BorderThickness="2,0,2,2" Grid.Row="1"
CornerRadius="0,0,10,10" Background="White">
<Grid x:Name="grdWindowContent" Margin="10,5,10,10" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="0.5*" />
<RowDefinition Height="0.5*" />
</Grid.RowDefinitions>
<TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="TextBlock" HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Button x:Name="button" Content="Button" Grid.Row="1" />
</Grid>
</Border>
<Thumb x:Name="thumbTop" Height="5" Margin="15,10,15,0" VerticalAlignment="Top" Background="Red"
Grid.RowSpan="2" Cursor="SizeNS" PreviewMouseLeftButtonDown="thumb_PreviewMouseLeftButtonDown" Opacity="0" />
<Thumb x:Name="thumbTopRight" HorizontalAlignment="Right" Height="15" Margin="0,10,0,0" VerticalAlignment="Top"
Width="15" Background="Green" Grid.RowSpan="2" Cursor="SizeNESW"
PreviewMouseLeftButtonDown="thumb_PreviewMouseLeftButtonDown" Opacity="0" />
<Thumb x:Name="thumbRight" HorizontalAlignment="Right" Margin="0,25,0,15" Width="5" Background="Red"
Grid.RowSpan="2" Cursor="SizeWE"
PreviewMouseLeftButtonDown="thumb_PreviewMouseLeftButtonDown" Opacity="0" />
<Thumb x:Name="thumbBottomRight" HorizontalAlignment="Right" Height="15" Margin="0" VerticalAlignment="Bottom"
Width="15" Background="Green" Grid.RowSpan="2" Cursor="SizeNWSE"
PreviewMouseLeftButtonDown="thumb_PreviewMouseLeftButtonDown" Opacity="0" />
<Thumb x:Name="thumbBottom" Height="5" Margin="15,0" VerticalAlignment="Bottom" Background="Red"
Grid.RowSpan="2" Cursor="SizeNS"
PreviewMouseLeftButtonDown="thumb_PreviewMouseLeftButtonDown" Opacity="0" />
<Thumb x:Name="thumbBottomLeft" HorizontalAlignment="Left" Height="15" Margin="0" VerticalAlignment="Bottom"
Width="15" Background="Green" Grid.RowSpan="2" Cursor="SizeNESW"
PreviewMouseLeftButtonDown="thumb_PreviewMouseLeftButtonDown" Opacity="0" />
<Thumb x:Name="thumbLeft" HorizontalAlignment="Left" Margin="0,25,0,15" Width="5" Background="Red"
Grid.RowSpan="2" Cursor="SizeWE"
PreviewMouseLeftButtonDown="thumb_PreviewMouseLeftButtonDown" Opacity="0" />
<Thumb x:Name="thumbTopLeft" HorizontalAlignment="Left" Height="15" Margin="0,10,0,0" VerticalAlignment="Top"
Width="15" Background="Green" Grid.RowSpan="2" Cursor="SizeNWSE"
PreviewMouseLeftButtonDown="thumb_PreviewMouseLeftButtonDown" Opacity="0" />
</Grid>
</Window>
OK!!大功告成!! 奉上專案源始碼來和大家一起慶祝: