WPF一步步实现完全无边框自定义Window(附源码)-阿里云开发者社区

开发者社区> 杰克.陈> 正文

WPF一步步实现完全无边框自定义Window(附源码)

简介: 原文:WPF一步步实现完全无边框自定义Window(附源码)    在我们设计一个软件的时候,有很多时候我们需要按照美工的设计来重新设计整个版面,这当然包括主窗体,因为WPF为我们提供了强大的模板的特性,这就为我们自定义各种空间提供了可能性,这篇博客主要用来介绍如何自定义自己的Window,在介绍整个写作思路之前,我们来看看最终的效果。
+关注继续查看
原文:WPF一步步实现完全无边框自定义Window(附源码)

   在我们设计一个软件的时候,有很多时候我们需要按照美工的设计来重新设计整个版面,这当然包括主窗体,因为WPF为我们提供了强大的模板的特性,这就为我们自定义各种空间提供了可能性,这篇博客主要用来介绍如何自定义自己的Window,在介绍整个写作思路之前,我们来看看最终的效果。  

  图一 自定义窗体主界面

  这里面的核心就是重写Window的Template,针对整个开发过程中出现的问题我们再来一步步去剖析,首先要看看我们定义好的样式 

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ui="clr-namespace:CustomWPFWindow.Controls"
                    xmlns:local="clr-namespace:CustomWPFWindow.Themes">
    <Style TargetType="{x:Type Window}" x:Key="ShellWindow">
        <Setter Property="Background" Value="#2B5A97"></Setter>
        <Setter Property="WindowStyle" Value="None"></Setter>
        <Setter Property="AllowsTransparency" Value="False"></Setter>
        <Setter Property="Template" >
            <Setter.Value>
                <ControlTemplate TargetType="Window">
                    <Border BorderBrush="#333" BorderThickness="1" Background="#eee">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"></RowDefinition>
                                <RowDefinition Height="*"></RowDefinition>
                            </Grid.RowDefinitions>
                            <ui:WindowTopArea Background="#2B579A">
                                <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                                    <StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Left" VerticalAlignment="Center">
                                        <Image Source="/CustomWPFWindow;component/Resources/Images/application.png"></Image>
                                        <TextBlock Text="标题" Foreground="White" Margin="5 2" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
                                    </StackPanel>
                                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                                        <ui:WindowButtonMin ToolTip="最小化">
                                            <Image Source="/CustomWPFWindow;component/Resources/Images/min.png" Width="16" Height="16" 
                                              HorizontalAlignment="Center" VerticalAlignment="Center" RenderOptions.BitmapScalingMode="NearestNeighbor"></Image>
                                        </ui:WindowButtonMin>
                                        <ui:WindowButtonMax x:Name="max" ToolTip="最大化">
                                            <Image Source="/CustomWPFWindow;component/Resources/Images/max.png" Width="16" Height="16" 
                                           HorizontalAlignment="Center" VerticalAlignment="Center" RenderOptions.BitmapScalingMode="NearestNeighbor"></Image>
                                        </ui:WindowButtonMax>
                                        <ui:WindowButtonNormal x:Name="normal" ToolTip="向下还原">
                                            <Image Source="/CustomWPFWindow;component/Resources/Images/normal.png" Width="16" Height="16" 
                                           HorizontalAlignment="Center" VerticalAlignment="Center" RenderOptions.BitmapScalingMode="NearestNeighbor"></Image>
                                        </ui:WindowButtonNormal>
                                        <ui:WindowButtonClose x:Name="windowclose" ToolTip="关闭">
                                            <Image  Source="/CustomWPFWindow;component/Resources/Images/close.png" Width="16" Height="16" 
                                           HorizontalAlignment="Center" VerticalAlignment="Center" RenderOptions.BitmapScalingMode="NearestNeighbor">
                                            </Image>
                                        </ui:WindowButtonClose>
                                    </StackPanel>
                                </Grid>
                            </ui:WindowTopArea>
                            <AdornerDecorator Grid.Row="1">
                                <ContentPresenter></ContentPresenter>
                            </AdornerDecorator>
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="WindowState" Value="Maximized">
                            <Setter Property="Visibility" Value="visible" TargetName="normal"></Setter>
                            <Setter Property="Visibility" Value="collapsed" TargetName="max"></Setter>
                        </Trigger>
                        <Trigger Property="WindowState" Value="Normal">
                            <Setter Property="Visibility" Value="collapsed" TargetName="normal"></Setter>
                            <Setter Property="Visibility" Value="visible" TargetName="max"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

  这里面要设置几个最核心的属性,第一个就是WindowStyle要设置成None,否则就无法进行自定义的设置按键功能区,另外一个就是是否设置AllowsTransparency属性的问题,我们分别看一下设置和不设置的效果。

  1 设置属性为false时:

图二 设置AllowsTransparency=“False”时主窗体样式

我们会发现此时在窗体的正上方出现一块白色的矩形区域,这块区域无论你怎么重写Window的样式,它都是一直存在的,但是此时整个窗体都是允许你进行拉伸的操作,但是此时我们会发现这样不是真正地无边框的Window的样式,整个窗体上面的白色区域都是存在的,查阅相关的资料的时候,我们会发现这一块是整个窗体上面部分的拉伸的区域,是无法通过重写模板去去掉它的,那么该怎样真正地去实现无边框的样式呢?

  2 设置属性为true时:

  当我们更改窗体的样式,设置窗体可以允许为透明窗体的时候,我们是否可以实现上面的效果呢?当我们设置这个属性为true的时候,我们发现能实现图一所示的效果,但是整个窗体都不能够进行拉伸了,那么这个怎么办呢?其实这个也可以理解,当我们设置Window窗体允许透明的时候,所有和窗体相关的事件都会消失了,这个是必然的,那么我们就不得不去重写所有的这些事件,下面我们将贴出整个窗体重写后的核心代码,并就里面的核心部分来进行深入的分析。

#region 窗体大小变化
        public void SetWindowResizer()
        {
            Window win = Window.GetWindow(this);
            ResizePosition ResPosition = ResizePosition.None;
            int Resizer = 5;
            win.MouseMove += new MouseEventHandler(
                    delegate (object target, MouseEventArgs args)
                    {
                        try
                        {
                            //do resize
                            if (win.WindowState == WindowState.Normal)
                            {
                                Point MS = args.GetPosition(win);
                                if (args.LeftButton == MouseButtonState.Pressed)
                                {
                                    Win32.POINT pos = new Win32.POINT();
                                    Win32.GetCursorPos(out pos);
                                    #region 改变窗体大小
                                    switch (ResPosition)
                                    {
                                        case ResizePosition.Left:
                                            //左边                          
                                            Mouse.SetCursor(Cursors.SizeWE);
                                            Point transPointLeft = win.PointToScreen(new Point(0, 0));
                                            win.Left += pos.X - transPointLeft.X;
                                            win.Width += transPointLeft.X - pos.X;
                                            break;
                                        case ResizePosition.Right:
                                            //右边
                                            Mouse.SetCursor(Cursors.SizeWE);
                                            Point transPointRight = win.PointToScreen(new Point(win.Width, 0));
                                            win.Width += pos.X - transPointRight.X;
                                            break;
                                        case ResizePosition.Top:
                                            //顶部
                                            Mouse.SetCursor(Cursors.SizeNS);
                                            Point transPointTop = win.PointToScreen(new Point(0, 0));
                                            win.Top += pos.Y - transPointTop.Y;
                                            win.Height += transPointTop.Y - pos.Y;
                                            break;
                                        case ResizePosition.Bottom:
                                            //底部
                                            Mouse.SetCursor(Cursors.SizeNS);
                                            Point transPointBottom = win.PointToScreen(new Point(0, win.Height));
                                            win.Height += (pos.Y - transPointBottom.Y);
                                            break;
                                        case ResizePosition.TopLeft:
                                            //左上
                                            Mouse.SetCursor(Cursors.SizeNWSE);
                                            Point transPointTopLeft = win.PointToScreen(new Point(0, 0));
                                            win.Left += pos.X - transPointTopLeft.X;
                                            win.Top += pos.Y - transPointTopLeft.Y;
                                            win.Width += transPointTopLeft.X - pos.X;
                                            win.Height += transPointTopLeft.Y - pos.Y;
                                            break;
                                        case ResizePosition.BottomLeft:
                                            //左下
                                            Mouse.SetCursor(Cursors.SizeNESW);
                                            Point transPointBottomLeft = win.PointToScreen(new Point(0, win.Height));
                                            win.Left += pos.X - transPointBottomLeft.X;
                                            win.Width += transPointBottomLeft.X - pos.X;
                                            win.Height += pos.Y - transPointBottomLeft.Y;
                                            break;
                                        case ResizePosition.TopRight:
                                            //右上
                                            Mouse.SetCursor(Cursors.SizeNESW);
                                            Point transPointTopRight = win.PointToScreen(new Point(win.Width, 0));
                                            win.Top += pos.Y - transPointTopRight.Y;
                                            win.Width = transPointTopRight.Y - pos.X;
                                            win.Height = transPointTopRight.Y - pos.Y;
                                            break;
                                        case ResizePosition.BottomRight:
                                            //右下
                                            Mouse.SetCursor(Cursors.SizeNWSE);
                                            Point transPointBottomRight = win.PointToScreen(new Point(win.Width, win.Height));
                                            win.Width += pos.X - transPointBottomRight.X;
                                            win.Height += pos.Y - transPointBottomRight.Y;
                                            break;
                                        case ResizePosition.None:
                                        default:
                                            Mouse.SetCursor(Cursors.Arrow);
                                            break;
                                    }
                                    #endregion
                                }
                                else if (MS.X <= Resizer + 5 && MS.Y <= Resizer + 5)
                                {
                                    //左上 (不执行)
                                    Mouse.SetCursor(Cursors.SizeNWSE);
                                    ResPosition = ResizePosition.TopLeft;
                                }
                                else if (MS.X <= Resizer && MS.Y >= win.ActualHeight - Resizer)
                                {
                                    //左下
                                    Mouse.SetCursor(Cursors.SizeNESW);
                                    ResPosition = ResizePosition.BottomLeft;
                                }
                                else if (MS.X >= win.ActualWidth - Resizer - 5 && MS.Y <= Resizer + 5)
                                {
                                    //右上(不执行)
                                    Mouse.SetCursor(Cursors.SizeNESW);
                                    ResPosition = ResizePosition.TopRight;
                                }
                                else if (MS.X >= win.ActualWidth - Resizer && MS.Y >= win.ActualHeight - Resizer)
                                {
                                    //右下
                                    Mouse.SetCursor(Cursors.SizeNWSE);
                                    ResPosition = ResizePosition.BottomRight;
                                }
                                else if (MS.X <= Resizer)
                                {
                                    //左边
                                    Mouse.SetCursor(Cursors.SizeWE);
                                    ResPosition = ResizePosition.Left;
                                }
                                else if (MS.Y <= Resizer + 5)
                                {
                                    //顶部(不执行)
                                    Mouse.SetCursor(Cursors.SizeNS);
                                    ResPosition = ResizePosition.Top;
                                }
                                else if (MS.X >= win.ActualWidth - Resizer)
                                {
                                    //右边
                                    Mouse.SetCursor(Cursors.SizeWE);
                                    ResPosition = ResizePosition.Right;
                                }
                                else if (MS.Y >= win.ActualHeight - Resizer)
                                {
                                    //底部
                                    Mouse.SetCursor(Cursors.SizeNS);
                                    ResPosition = ResizePosition.Bottom;
                                }
                                else
                                {
                                    //无
                                    Mouse.SetCursor(Cursors.Arrow);
                                    ResPosition = ResizePosition.None;
                                }
                            }
                        }
                        catch
                        {
                            ResPosition = ResizePosition.None;
                            win.ReleaseMouseCapture();
                        }
                        args.Handled = CaptureMouse;
                    }
                );
            win.MouseLeftButtonDown += new MouseButtonEventHandler(
                    delegate (object target, MouseButtonEventArgs args)
                    {
                        if (win.WindowState == WindowState.Normal)
                        {
                            //获取当前鼠标点击点相对于Dvap.Shell窗体的位置
                            Point pos = args.GetPosition(win);
                            if (ResPosition != ResizePosition.None)
                            {
                                CaptureMouse = win.CaptureMouse();
                            }
                            args.Handled = CaptureMouse;
                        }
                    }
                );
            win.MouseLeftButtonUp += new MouseButtonEventHandler(
                    delegate (object target, MouseButtonEventArgs args)
                    {
                        if (win.WindowState == WindowState.Normal)
                        {
                            ResPosition = ResizePosition.None;
                            if (CaptureMouse)
                            {
                                win.ReleaseMouseCapture();
                                CaptureMouse = false;
                            }
                            args.Handled = CaptureMouse;
                        }
                    }
                );
        }
        #endregion

  这段代码还是很容易理解的,就是为整个窗体添加MouseLeftButtonDown、MouseMove、MouseLeftButtonUp这个事件,这里需要特别注意的就是,我们获取屏幕的坐标的方式是调用Win32的API  GetCursorPos来获取当前的屏幕坐标的位置,但是我们在操作的时候获取到的是主窗体的坐标位置,这两个位置该如何进行转化呢?这个是核心,上面的代码是经过反复进行验证后的代码,就具体的拉伸过程我们再来做进一步的分析。

     2.1 首先我们要设置一个进行拉伸的区域,这里我们设置Resizer为5个像素,这个距离内作为窗体拉伸的识别区域。

     2.2  首先在MouseLeftButtonDown事件中我们需要窗体能够捕获到鼠标的位置,这里我们使用win.CaptureMouse()方法来捕获鼠标的输入。

     2.3   最重要的部分都是在MouseMove事件中完成的,首先我们需要通过Point MS = args.GetPosition(win)来获取到当前鼠标相对于主窗体win的位置,记住这个获取到的位置是相对于主窗体的而不是相对于屏幕的坐标位置的。然后我们判断当前鼠标左键是否按下来将整个过程分为两个部分,按下的话进行窗体的拉伸操作,没有按下的话进行窗体的初始化状态操作,通过获取到的MS的坐标位置来初始化操作对象,并且设置当前鼠标的光标的样式,这个是非常重要的一个过程的,只有完成了这个过程才能进行下面的操作。

  2.4  当鼠标左键按下时,我们将会看到通过3步骤进行初始化的状态我们来改变窗体的大小以及位置信息,在这一步骤中需要特别注意的是,注意坐标系的转换,比如向右拉伸的时候,我们首先获取到的是通过GetCursorPos来获取到的相对于屏幕的位置信息,那么当我们将窗体向右拉伸时,窗体移动的距离应该是窗体的Width对应的点转化为屏幕坐标点后再与通过GetCursorPos来获取到的相对于屏幕的位置信息做差运算的结果,切不可直接将屏幕坐标位置减去窗体当前的位置,因为窗体获取到的Width以及其它位置信息和屏幕的坐标系是不统一的,无法直接做差运算,这里读者也可以改动代码进行尝试,所以这里就有了PointToScreen和PointFromScreen这两个坐标转换函数的用武之地了,比如我们拉伸窗体的右侧距离时,我们通过 Point transPointRight = win.PointToScreen(new Point(win.Width, 0))这句代码将当前主窗体最右侧的位置首先转成相对于屏幕坐标系的位置,然后用两个相同坐标系的两个坐标位置进行做差运算,从而改变窗体的Left、Top、Width、Height属性,这个是需要我们去一点点分析的,后面的每一个过程都与此类似,这个过程是整个窗体拉伸变换的关键。

  2.5 最后一个需要注意的地方就是窗体拉伸的时候,需要考虑窗体是否设置了MinWidt、MinHeight这些属性,当超过这些属性的时候窗体是无法进行拉伸操作的。

  3  通过Win32的API函数设置窗体的无边框透明属性。

  在进一步分析代码时我们发现可以通过设置SetWindowLong来设置窗体的属性,这个函数可以在设置AllowsTransparency=“False”的状态下仍然改变窗体的无边框样式,下面贴出具体代码,具体每个参数的含义需要参考具体的文档。

private void SetWindowNoBorder()
        {
            Window win = Window.GetWindow(this);
            // 获取窗体句柄  
            IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(win).Handle;

            // 获得窗体的 样式  
            long oldstyle = Win32.GetWindowLong(hwnd, Win32.GWL_STYLE);

            // 更改窗体的样式为无边框窗体  
            Win32.SetWindowLong(hwnd, Win32.GWL_STYLE, (int)(oldstyle & ~Win32.WS_CAPTION));
        }

  4 最后需要提及的是一个鼠标移入关闭按钮时的一个动画状态,通过这段代码我们可以学习一下该如何在Trigger中设置动画属性。

<Style TargetType="{x:Type controls:WindowButtonClose}">
        <Setter Property="Margin" Value="0 0 1 0"></Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:WindowButtonClose}">
                    <Border  x:Name="bg" Background="Transparent" Width="32" Height="32">
                        <ContentPresenter x:Name="content" HorizontalAlignment="Center" VerticalAlignment="Center"  Opacity="0.5" RenderTransformOrigin="0.5 0.5">
                            <ContentPresenter.RenderTransform>
                                <RotateTransform x:Name="angleRotateTransform" ></RotateTransform>
                            </ContentPresenter.RenderTransform>
                        </ContentPresenter>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="Background" Value="Red" TargetName="bg"></Setter>
                            <Setter Property="Opacity" Value="1" TargetName="content"></Setter>
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="angleRotateTransform" Storyboard.TargetProperty="Angle"
                                                         From="0" To="90" Duration="0:0:0.5">
                                            <DoubleAnimation.EasingFunction>
                                                <BackEase EasingMode="EaseInOut"></BackEase>
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="angleRotateTransform" Storyboard.TargetProperty="Angle"
                                                         From="90" To="0" Duration="0:0:0.5">
                                            <DoubleAnimation.EasingFunction>
                                                <BackEase EasingMode="EaseInOut"></BackEase>
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

  最后,贴出整个工程的源码,请点击此处进行下载,文中有表述不当的地方请批评指正,谢谢!

 

  

  

 

 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
JSP自定义标签之简单标签入门
在sun官方文档上有下面这样一段话。 官方文档声明 public interface SimpleTag extends JspTag Interface for defining Simple Tag Handlers.
705 0
Android 自定义Toast,并且勘误Android工具类里面的ToastUtils
前言 相信大部分仁兄在使用系统Toast的时候,都感觉不太尽如人意,因为系统Toast显示的位置比较固定,并且字体颜色等会跟随系统版本变化,那么能不能自己写一个呢,答案是当然的。
933 0
JSP自定义简单标签入门之带有属性
上面写的那个简单标签来控制页面中标签内容的重复次数是固定的,这就是权限“写死了”,这是非常不好的行为,因为其灵活性太差。所以下面就介绍一种使用属性来控制标签内容输出次数的方法。 准备工作 创建实现了SimpleTag接口的实现类(或者是继承了SimpleTagSupport类的子类) 在类中为相应的属性字段添加setter方法,注意字段名称一定要一致。
825 0
WPF一步步实现完全无边框自定义Window(附源码)
原文:WPF一步步实现完全无边框自定义Window(附源码)    在我们设计一个软件的时候,有很多时候我们需要按照美工的设计来重新设计整个版面,这当然包括主窗体,因为WPF为我们提供了强大的模板的特性,这就为我们自定义各种空间提供了可能性,这篇博客主要用来介绍如何自定义自己的Window,在介绍整个写作思路之前,我们来看看最终的效果。
1017 0
+关注
杰克.陈
一个安静的程序猿~
10427
文章
2
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载