专栏首页黄腾霄的博客2020-5-22-如何使WPF在窗口外部区域可拖动缩放

2020-5-22-如何使WPF在窗口外部区域可拖动缩放

今天来和大家聊如何使WPF在窗口外部区域可拖动缩放。


问题来源

对于WPF窗口来说,默认的可拖动缩放区域较小。

在某些应用场景下我们期望能够设置一个较大的可拖动的缩放区域。

自定义WindowChrome

有同学马上想到了,通过WindowChromeResizeBorderThickness属性进行设置

比如下面的方式

<Window x:Class="WpfApp2.MainWindow"
        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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <WindowChrome.WindowChrome>
        <WindowChrome ResizeBorderThickness="100"></WindowChrome>
    </WindowChrome.WindowChrome>
    <Grid Background="Transparent" MouseDown="UIElement_OnMouseDown">
        <Grid Margin="100" Background="White" />
        <Button Width="100" Height="100" Command="Undo" />
    </Grid>
</Window>

但是ResizeBorderThickness只能向窗口内部扩展,而在外部依然不可以进行拖动缩放

添加外部窗口

想要在WPF窗口外部能够拖动缩放,问题的关键就在于如何能在外部收到鼠标点击拖动等消息。

那么实际上我们只需要在主窗口周围添加四个alpha值为1窗口。

这些窗口用于接受消息,并传递给主窗口进行拖动变化即可实现。

(注:visual studio就是这样做的,可以通过工具抓到他周围包含了4个宽度为9像素的窗口)

监听主窗口状态变化

现在我们一共有了5个窗口。

要想这5个窗口能像一个窗口一样工作,必须要让周围的辅助窗口跟随主窗口的状态变化。

这里我主要关注主窗口的下面5个事件:

  • LocationChanged
  • SizeChanged
  • StateChanged
  • IsVisibleChanged
  • Closed

LocationChanged 和SizeChanged主要是通知辅助窗口调整位置和大小,确定包裹在主窗口周围

StateChanged和IsVisibleChanged用于通知窗口的显示隐藏,避免主窗口隐藏时,辅助窗口还能被拖动

Closed用于在主窗口关闭后,关闭辅助窗口以及释放资源。

有了这些事件,辅助窗口就能够跟随主窗口进行变换了。

通知主窗口

接下来一个重要的事情就是辅助窗口被点击拖动时,通知主窗口进行拖动缩放。

这个行为有很多实现方法,最简单的一种是,让辅助窗口假装自己是主窗口的非客户区。

听着很复杂,实际做起来很简单,就是在辅助窗口被点击时,给主窗口发一个非客户区被点击的win消息。

例如下面的代码加入到辅助左侧的辅助窗口中,就能让它在接收到windows的鼠标左键点击时,向主窗口发出一个左侧非客户区的border被点击的消息。

于是主窗口就傻傻的以为自己左侧非客户区的border被点中了,就进入了拖拽缩放的行为。

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == (int)WM.LBUTTONDOWN)
            {
                Win32.PostMessage(new WindowInteropHelper(Owner).Handle,
                    (int) WM.NCLBUTTONDOWN, (int) HitTestValues.HTLEFT,
                    0);
            }

            return IntPtr.Zero;
        }

辅助窗口隐藏

有了双向的通知机制后,需要接下来需要将辅助窗口设置为透明。

这里参考了毅仔同学的高性能透明窗口做法。

具体内容不做详细讲解,有兴趣的同学可以参考毅仔同学的博客。

WPF 制作高性能的透明背景异形窗口(使用 WindowChrome 而不要使用 AllowsTransparency=True) - walterlv

关键代码如下

//设置窗口非客户区大小为0
ResizeMode = ResizeMode.NoResize;
WindowStyle = WindowStyle.None;
WindowChrome.SetWindowChrome(this, new WindowChrome()
{
    GlassFrameThickness = new Thickness(-),
    CaptionHeight = 
});
//设置窗口alpha值为0x01
Opacity = 1.0 / ;

效果呈现

最后就可以得到如下的效果,我们可以在窗口的外部进行拖动,让主窗口进行缩放了。

另外,这里对整个代码做了封装,所以在使用时可以非常简单。

只需要在xaml中配置一个附加属性即可。

<Window x:Class="WpfApp2.MainWindow"
        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:extendedResizeBorder="clr-namespace:ExtendedResizeBorder;assembly=ExtendedResizeBorder"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <extendedResizeBorder:ExtendedResizeBorder.ExtendedResizeBorder>
        <extendedResizeBorder:ExtendedResizeBorder Radius="100" />
    </extendedResizeBorder:ExtendedResizeBorder.ExtendedResizeBorder>
    <Grid Background="Transparent" MouseDown="UIElement_OnMouseDown">
        <Grid Margin="100" Background="White" />
        <Button Width="100" Height="100" Command="Undo" />
    </Grid>
</Window>

代码

文中对应的代码已经推送到github的dotnet-campus组织下。

欢迎大家issue和star

dotnet-campus/ExtendedResizeBorder: Enable WPF window has an outside resizable border


参考文档:


本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/%E5%A6%82%E4%BD%95%E4%BD%BFWPF%E5%9C%A8%E7%AA%97%E5%8F%A3%E5%A4%96%E9%83%A8%E5%8C%BA%E5%9F%9F%E5%8F%AF%E6%8B%96%E5%8A%A8.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接: https://xinyuehtx.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 2019-11-13-C++CLI的托管字符串std字符串和c字符串的转换

    在C++/CLI中,我们可以接触到三种字符串std::string,System::string,cstring。这里我们分别称之为标准字符串,托管字符串和c语...

    黄腾霄
  • 2020-1-17-C++项目文件升级

    想必大家都有过维护旧版本项目的经验。遇到的第一个问题一定是如何将项目跑起来。相较于其他项目类型,c++(尤其是vc++)应该是一个项目升级较为麻烦程序类型。今天...

    黄腾霄
  • 2019-7-16-为什么MessageBox会跑到窗口下面

    我们在一个窗口调用MessageBox.Show方法会在,该窗口制造出一个模态的消息框。但是有小伙伴最近问我他发现这个消息框经常会到窗口下面显示。

    黄腾霄
  • 浅析 Android 的窗口

    一、窗口的概念 在开发过程中,我们经常会遇到,各种跟窗口相关的类,或者方法。但是,在 Android 的框架设计中,到底什么是窗口?窗口跟 Android Fr...

    腾讯Bugly
  • GUI组件添加、布局设置

    先构建一个窗口对象,使用setLayout();方法把布局设置为null,用setBounds();方法将窗口的位置坐标设置好,记得setVisibel();显...

    端碗吹水
  • [DeeplearningAI笔记]卷积神经网络2.9-2.10迁移学习与数据增强

    [1]吴恩达老师课程原地址: https://mooc.study.163.com/smartSpec/detail/1001319001.htm

    DrawSky
  • 1000道Python题库系列分享九(31道)

    上期题目:1000道Python题库系列分享八(29道) 上期答案: ? 本期题目: ?

    Python小屋屋主
  • Object类解析

    Object类是所有类的父类,任何类都默认继承Object,以下是Object的方法导图。

    用户6055494
  • React中的模式对话框 转

    在16.x版本之后React提供了Protals功能来解决模式对话框不在Dom根节点导致的一些BUG。除了Protal还有更多的方法去解决这些问题,本文来自Da...

    随风溜达的向日葵
  • 贪心算法之背包问题

    贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。 贪心算法...

    欠扁的小篮子

扫码关注云+社区

领取腾讯云代金券