WPF無邊框視窗滑鼠拖動縮放大小
通常,我們會 通過AllowsTransparency=”True”、 WindowStyle=”None” 這兩個屬性將wpf視窗的邊框去掉,由於邊框沒了,我們就不能通過滑鼠指標懸停在某一邊上拖動改變視窗的大小,此時若要能調整視窗大小,官方倒也提供了個屬性:ResizeMode=”CanResizeWithGrip”,這麼一設定,我們會發現視窗右下角多出一個三角標記,此時可以將滑鼠指正懸停在這個標記上,進而拖動改變視窗的大小,這確實能解決問題,但是產品經理那過不了啊= =,“別的軟體都可以在邊框上調整呀”,Orz…
百度一下,發現好多方案都是說監聽WM_NCHITTEST型別的訊息,然後提供了個滑鼠位置的列舉,然後就沒有然後了,這什麼鬼,現在滑鼠放到“邊框”上都不會變樣式唉,你監聽那個有啥用= =然後又看到有兄弟在視窗周圍新增4個邊4個角來處理滑鼠的拖動,感覺這個雖然聽起來不高大上,但應該可行,開幹……
首先,我們不可能在每一個視窗xaml裡都加上邊角,而是要寫一個視窗模板,然後讓需要能縮放的視窗繼承這個模板就行了。
ok,建立一個視窗模板的資原始檔:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate x:Key="CustomWindowTemplete" TargetType="Window" >
<Border BorderBrush="Transparent" BorderThickness="12" x:Name="outBorder">
<Border.Effect>
<DropShadowEffect BlurRadius="15" Color="#000000" Opacity=".25" Direction="90" ShadowDepth="1"/>
</Border.Effect>
<Grid>
<Grid .RowDefinitions>
<RowDefinition Height="1"/>
<RowDefinition/>
<RowDefinition Height="1"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1"/>
<ColumnDefinition/>
<ColumnDefinition Width="1"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1" Background="White" >
<AdornerDecorator>
<ContentPresenter></ContentPresenter>
</AdornerDecorator>
</Grid>
<Rectangle Name="ResizeTopLeft" Fill="{StaticResource WindowBorderColor}" Grid.Row="0" Grid.Column="0" Opacity=".25"/>
<Rectangle Name="ResizeTop" Fill="{StaticResource WindowBorderColor}" Grid.Row="0" Grid.Column="1" Opacity=".25"/>
<Rectangle Name="ResizeTopRight" Fill="{StaticResource WindowBorderColor}" Grid.Row="0" Grid.Column="2" Opacity=".25"/>
<Rectangle Name="ResizeLeft" Fill="{StaticResource WindowBorderColor}" Grid.Row="1" Grid.Column="0" Opacity=".25"/>
<Rectangle Name="ResizeRight" Fill="{StaticResource WindowBorderColor}" Grid.Row="1" Grid.Column="2" Opacity=".25"/>
<Rectangle Name="ResizeBottomLeft" Fill="{StaticResource WindowBorderColor}" Grid.Row="2" Grid.Column="0" Opacity=".25"/>
<Rectangle Name="ResizeBottom" Fill="{StaticResource WindowBorderColor}" Grid.Row="2" Grid.Column="1" Opacity=".25"/>
<Rectangle Name="ResizeBottomRight" Fill="{StaticResource WindowBorderColor}" Grid.Row="2" Grid.Column="2" Opacity=".25"/>
</Grid>
</Border>
</ControlTemplate>
<Style x:Key="CustomWindow" TargetType="Window">
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="Template" Value="{StaticResource CustomWindowTemplete}"></Setter>
</Style>
</ResourceDictionary>
以上程式碼,在Grid外還套了一個Border,是為了實現邊框陰影效果;Rectangle的Opacity屬性都是0.25,設定這個透明度是為了讓我們自定義的這個邊框看起來不明顯,如果不設這個透明度的話,視窗四周就會明顯有一個畫素為1的框,當然這個看你需求了,如果你視窗四周本來就是有線框而不是陰影,那就不用Border和這個Opacity了。
別忘了在App.xaml裡要引入這個資原始檔:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/CustomColor.xaml"/>
<ResourceDictionary Source="Resources/VcreditWindow.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
再新建一個基類視窗:
public class VcreditWindowBehindCode : Window
{
public const int WM_SYSCOMMAND = 0x112;
public HwndSource HwndSource;
public Dictionary<ResizeDirection, Cursor> cursors = new Dictionary<ResizeDirection, Cursor>
{
{ResizeDirection.Top, Cursors.SizeNS},
{ResizeDirection.Bottom, Cursors.SizeNS},
{ResizeDirection.Left, Cursors.SizeWE},
{ResizeDirection.Right, Cursors.SizeWE},
{ResizeDirection.TopLeft, Cursors.SizeNWSE},
{ResizeDirection.BottomRight, Cursors.SizeNWSE},
{ResizeDirection.TopRight, Cursors.SizeNESW},
{ResizeDirection.BottomLeft, Cursors.SizeNESW}
};
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)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
public VcreditWindowBehindCode()
{
this.SourceInitialized += VcreditWindowBehindCode_SourceInitialized;
this.Loaded += VcreditWindowBehindCode_Loaded;
this.MouseMove += VcreditWindowBehindCode_MouseMove;
}
void VcreditWindowBehindCode_MouseMove(object sender, MouseEventArgs e)
{
if (Mouse.LeftButton != MouseButtonState.Pressed)
{
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element != null && !element.Name.Contains("Resize"))
{
this.Cursor = Cursors.Arrow;
}
}
}
void VcreditWindowBehindCode_SourceInitialized(object sender, EventArgs e)
{
this.HwndSource = PresentationSource.FromVisual((Visual)sender) as HwndSource;
}
void VcreditWindowBehindCode_Loaded(object sender, RoutedEventArgs e)
{
ControlTemplate customWindowTemplate = App.Current.Resources["CustomWindowTemplete"] as ControlTemplate;
if (customWindowTemplate!=null)
{
var TopLeft = customWindowTemplate.FindName("ResizeTopLeft", this) as Rectangle;
TopLeft.MouseMove += ResizePressed;
TopLeft.MouseDown += ResizePressed;
var Top = customWindowTemplate.FindName("ResizeTop", this) as Rectangle;
Top.MouseMove += ResizePressed;
Top.MouseDown += ResizePressed;
var TopRight = customWindowTemplate.FindName("ResizeTopRight", this) as Rectangle;
TopRight.MouseMove += ResizePressed;
TopRight.MouseDown += ResizePressed;
var Left = customWindowTemplate.FindName("ResizeLeft", this) as Rectangle;
Left.MouseMove += ResizePressed;
Left.MouseDown += ResizePressed;
var Right = customWindowTemplate.FindName("ResizeRight", this) as Rectangle;
Right.MouseMove += ResizePressed;
Right.MouseDown += ResizePressed;
var BottomLeft = customWindowTemplate.FindName("ResizeBottomLeft", this) as Rectangle;
BottomLeft.MouseMove += ResizePressed;
BottomLeft.MouseDown += ResizePressed;
var Bottom = customWindowTemplate.FindName("ResizeBottom", this) as Rectangle;
Bottom.MouseMove += ResizePressed;
Bottom.MouseDown += ResizePressed;
var BottomRight = customWindowTemplate.FindName("ResizeBottomRight", this) as Rectangle;
BottomRight.MouseMove += ResizePressed;
BottomRight.MouseDown += ResizePressed;
}
}
public void ResizePressed(object sender, MouseEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
ResizeDirection direction = (ResizeDirection)Enum.Parse(typeof(ResizeDirection), element.Name.Replace("Resize", ""));
this.Cursor = cursors[direction];
if (e.LeftButton == MouseButtonState.Pressed)
{
ResizeWindow(direction);
}
}
public void ResizeWindow(ResizeDirection direction)
{
SendMessage(HwndSource.Handle, WM_SYSCOMMAND, (IntPtr)(61440 + direction), IntPtr.Zero);
}
}
注意建構函式裡的3個事件,在這3個事件裡分別做了一系列的初始化工作。這裡真正改變視窗大小的程式碼其實是user32裡的SendMessage方法。
下來就到了具體的視窗了:
在視窗屬性裡引入名稱空間: xmlns:vcredit=”clr-namespace:VcreditChat.Resources”
然後把視窗最外層的標籤Window替換成vcredit:VcreditWindowBehindCode,也就是剛剛那個基類視窗
再引用一下剛剛資源裡定義的樣式:Style=”{StaticResource CustomWindow}”
最後在視窗的後臺程式碼裡繼承一下基類視窗類: public partial class ChatSingle : VcreditWindowBehindCode
以上就完成了滑鼠在邊框拖動調整無邊框視窗大小的功能了,但是還有個問題,就是我們將Border的BorderThickness設定成了12,如果最大化視窗會發現,視窗離電腦螢幕邊框還有12畫素的空白,此時我們就要在最大化時通過後臺程式碼動態設定BorderThickness=0,在視窗正常化時再恢復12。