1. 程式人生 > >WPF無邊框視窗滑鼠拖動縮放大小

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。