• WPF换肤之二:可拉动的窗体


    让我们接着上一章: WPF换肤之一:创建圆角窗体 来继续。

    在这一章,我主要是实现对圆角窗体的拖动,改变大小功能。

    拖动自绘窗体的步骤

    首先,通过上节的设计,我们知道了如何设计一个圆角窗体,通过XAML代码量,我们发现设置这个窗体是多么的简单。但是如何让窗体能够进行Resize呢?

    在Winform时代,我们通过WndProc(ref Message m)处理窗体界面消息来实现,那么在WPF中是否也是如此呢?

    其实在WPF中,虽说封装比较紧密,但是对于处理界面消息这块,和WINFORM一样,未有所改变。下面请看具体设计:

    首先,由于要涉及到和Win32交互,我们需要订阅SourceInitialized事件。

      public MsgWindow()
            {
                InitializeComponent();
                this.SourceInitialized += new EventHandler(WSInitialized);
            }

    然后,由于涉及到SourceInitialized Event,我们就需要使用到HwndSource,它主要功能就是WPF放入到Win32窗体中。让我们看看WindowSourceInitialized事件:

    void WSInitialized(object sender, EventArgs e)
            {
                hs = PresentationSource.FromVisual(this) as HwndSource;
                hs.AddHook(new HwndSourceHook(WndProc));
            }

    接下来我们看到,窗体Hook了一个 HwndSourceHook的委托,这个委托能够接受所有经过Windows的消息。我们来看看WndProc函数:

     Dictionary<int, int> messages = new Dictionary<int, int>();
    
            private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                Debug.Print(string.Format("窗体消息: {0}, wParam: {1} , lParam: {2}", msg.ToString(), wParam.ToString(), lParam.ToString()));
                if (messages.ContainsKey(msg) == false)
                {
                    messages.Add(msg, msg);
                    // 添加接收到的WIndows信息到ListBox中
                    listMsg.Items.Add(msg);
                }
                return new IntPtr(0);
            } 

    这个函数会接收到所有windows消息,打印到Debug台上。

    接下来,知道了事件处理流程,我们开始讨论拖拉窗体的问题。

    首先,我们先给窗体添加一个ResetCursor事件,以便于拖拉结束后,恢复鼠标指针:

    <Window x:Class="WpfApplication1.MsgWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TestWindow" Height="391" Width="418" WindowStyle="None" AllowsTransparency="True" Background="Transparent" OpacityMask="White" ResizeMode="NoResize" PreviewMouseMove="ResetCursor" WindowStartupLocation="CenterScreen">
        

    其次,我们给Border元素添加一个MouseMove事件,用来显示鼠标特定情况下的鼠标指针形状(如达到了窗体边缘,则变换为拖拉的鼠标形状),同时添加一个PreviewMouseDown事件,用来进行Resize操作(也就是鼠标左键按下,开始进行拖放):

    <Border BorderThickness="5" BorderBrush="DarkGreen"  CornerRadius="10,10,10,10" MouseMove="DisplayResizeCursor" PreviewMouseDown="Resize" Name="top">

    这样,当事件添加好以后,我们转换到后台代码:

    由于窗体总共有八个地方可以进行拖拉,分别是Top,TopRight,Right,BottomRight,Bottom,BottomLeft,Left,TopLeft,那么我们先声明一个Enum:

     public enum ResizeDirection
            {
                Left = 1,
                Right = 2,
                Top = 3,
                TopLeft = 4,
                TopRight = 5,
                Bottom = 6,
                BottomLeft = 7,
                BottomRight = 8,
            }

    在Win32中,由于61440+1 代表左边,61440+2代表右边,一次类推,所以我们需要进行如下设计:

     [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(hs.Handle, WM_SYSCOMMAND, (IntPtr)(61440 + direction), IntPtr.Zero);
            }

    其中,WM_SYSCOMMAND为Int类型,初始值为0x112,它的解释如下:

    WM_SYSCOMMAND

    0x112

    A window   receives this message when the user chooses a command from the Window menu   (formerly known as the system or control menu) or when the user chooses the   maximize button, minimize button, restore button, or close button.

    这样,通过上面的函数,我们就可以实现窗体的Resize,下面我们来响应鼠标事件:

    首先是窗体的ResetCursor事件,这个主要是用来恢复鼠标形状:

      private void ResetCursor(object sender, MouseEventArgs e)
            {
                if (Mouse.LeftButton != MouseButtonState.Pressed)
                {
                    this.Cursor = Cursors.Arrow;
                }
            }

    然后我们来看看DisplayResizeCursor事件,它主要是用来改变鼠标形状,当鼠标达到一定区域,则显示拖拉的鼠标形状(<->):

    其计算方式,请参看下图:

    private void DisplayResizeCursor(object sender, MouseEventArgs e)
            {
                Border clickBorder = sender as Border;
    
                Point pos = Mouse.GetPosition(this);
                double x = pos.X;
                double y = pos.Y;
                double w= this.Width;
                double h= this.Height;
    
                this.label1.Content = x + "--" + y;
    
                if (x <= relativeClip & y <= relativeClip) // left top
                {
                    this.Cursor = Cursors.SizeNWSE;
                }
                if (x >= w - relativeClip & y <= relativeClip) //right top
                {
                    this.Cursor = Cursors.SizeNESW;
                }
    
                if (x >= w - relativeClip & y >= h - relativeClip) //bottom right
                {
                    this.Cursor = Cursors.SizeNWSE;
                }
    
                if (x <= relativeClip & y >= h - relativeClip)  // bottom left
                {
                    this.Cursor = Cursors.SizeNESW;
                }
    
                if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top
                {
                    this.Cursor = Cursors.SizeNS;
                }
    
                if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right
                {
                    this.Cursor = Cursors.SizeWE;
                }
    
                if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom
                {
                    this.Cursor = Cursors.SizeNS;
                }
    
                if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left
                {
                    this.Cursor = Cursors.SizeWE;
                }
            }

    最后就是Resize的函数,和上面的计算方式类似,只是拖拉的时候需要调用ResizeWindow函数来改变大小:

      private void Resize(object sender, MouseButtonEventArgs e)
            {
                Border clickedBorder = sender as Border;
    
                Point pos = Mouse.GetPosition(this);
                double x = pos.X;
                double y = pos.Y;
                double w = this.Width;
                double h = this.Height;
    
                if (x <= relativeClip & y <= relativeClip) // left top
                {
                    this.Cursor = Cursors.SizeNWSE;
                    ResizeWindow(ResizeDirection.TopLeft);
                }
                if (x >= w - relativeClip & y <= relativeClip) //right top
                {
                    this.Cursor = Cursors.SizeNESW;
                    ResizeWindow(ResizeDirection.TopRight);
                }
    
                if (x >= w - relativeClip & y >= h - relativeClip) //bottom right
                {
                    this.Cursor = Cursors.SizeNWSE;
                    ResizeWindow(ResizeDirection.BottomRight);
                }
    
                if (x <= relativeClip & y >= h - relativeClip)  // bottom left
                {
                    this.Cursor = Cursors.SizeNESW;
                    ResizeWindow(ResizeDirection.BottomLeft);
                }
    
                if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top
                {
                    this.Cursor = Cursors.SizeNS;
                    ResizeWindow(ResizeDirection.Top);
                }
    
                if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right
                {
                    this.Cursor = Cursors.SizeWE;
                    ResizeWindow(ResizeDirection.Right);
                }
    
                if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom
                {
                    this.Cursor = Cursors.SizeNS;
                    ResizeWindow(ResizeDirection.Bottom);
                }
    
                if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left
                {
                    this.Cursor = Cursors.SizeWE;
                    ResizeWindow(ResizeDirection.Left);
                }
            }

    最后效果图如下所示:

     源码下载

    PS:20121130新增了一个修改就是限制了最小宽度和最小高度,但是效果不是很满意,有闪烁,以后再完善吧。

     点击下载源码

  • 相关阅读:
    ld: symbol(s) not found for architecture arm64
    一个好玩的命令
    统计代码行数命令
    【转】 C语言自增自减运算符深入剖析
    gcc 生成动态库时-fpic选项是什么意思。
    每天一条linux命令——halt
    每天一条linux命令——shutdown
    推荐一些编程学习的网站
    每天一条linux命令——login
    linux如何开机以命令行形式启动?
  • 原文地址:https://www.cnblogs.com/scy251147/p/2609197.html
Copyright © 2020-2023  润新知