【问题标题】:WPF borderless window with shadow VS2012 styleWPF无边框窗口带阴影VS2012风格
【发布时间】:2013-02-06 13:29:41
【问题描述】:

我正在尝试创建一个看起来像 Visual Studio 2012 的应用程序。我使用 WindowChrome 删除了窗口边框,并更改了我的 xaml 中的边框颜色。

我不知道怎么做的是绘制窗口的阴影,这里你可以看到我所说的截图:

如你所见,有一个阴影,它的颜色也是边框颜色

你知道如何使用 WPF 来实现它吗?

【问题讨论】:

  • 虽然这个问题已经过时了,但我已经更新了我的答案,包括我的新库和说明如何最终实现所要求的发光边框。希望你会喜欢它。

标签: c# wpf user-interface visual-studio-2012


【解决方案1】:
<Window x:Class="MyProject.MiniWindow"
    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:local="clr-namespace:MyProject"
    mc:Ignorable="d" 
    WindowStyle="None" 
    Title="MiniWindow" Background="Transparent"
    Height="200" Width="200" 
    >

<WindowChrome.WindowChrome>
    <WindowChrome 
    CaptionHeight="0"
    ResizeBorderThickness="4" />
</WindowChrome.WindowChrome>

<Grid Margin="0">
    <Border BorderThickness="3">
        <Border BorderThickness="1" Margin="0" BorderBrush="#ff007acc">
            <Border.Effect>
                <DropShadowEffect Color="#ff007acc" Direction="132" ShadowDepth="0" BlurRadius="8" />
            </Border.Effect>
            <Grid   Background="#ff2d2d30">
                
            </Grid>
        </Border>
    </Border>
    
    
</Grid>

【讨论】:

    【解决方案2】:

    您可以使用这个简单的 xaml 代码

    <Window x:Class="VS2012.MainWindow" 
             xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation 
             xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml 
             Title="MainWindow" 
             Height="100" Width="200" 
             AllowsTransparency="True" WindowStyle="None" Background="Transparent"> 
    <Border BorderBrush="DarkOrange" BorderThickness="1" Background="White" Margin="5">
             <Border.Effect>
                    <DropShadowEffect ShadowDepth="0" BlurRadius="5" Color="DarkOrange"/>
             </Border.Effect>
    </Border>
    </Window> 
    

    【讨论】:

    • 我在我的帖子中提到将AllowsTransparency 设置为True 并将Background 设置为Transparent 会大大降低性能,由于更改窗口大小时出现滞后,您会注意到这一点或拖动它。如果我没记错的话,DirectX 没有为此设置使用硬件加速。因此我不建议这样做!
    【解决方案3】:

    更新(2017 年 10 月)

    现在已经四年了,我有兴趣再次解决这个问题,因此我一直在搞砸 MahApps.Metroderived my own library based on it。我的 ModernChrome 库提供了一个类似于 Visual Studio 2017 的自定义窗口:

    由于您很可能只对发光边框部分感兴趣,您应该使用 MahApps.Metro 本身或查看我如何创建一个类 GlowWindowBehavior 将发光边框附加到我的自定义ModernWindow 班级。它严重依赖于 MahApps.Metro 的一些内部结构和两个依赖属性 GlowBrushNonActiveGlowBrush

    如果您只想在自定义应用程序中包含发光边框,只需引用 MahApps.Metro 并复制我的GlowWindowBehavior.cs 并创建自定义窗口类并相应地调整引用。这最多需要 15 分钟。

    这个问题和我的回答被经常访问,所以我希望你会发现我最新的正确解决方案很有用:)


    原始帖子(2013 年 2 月)

    我一直在开发这样一个库来复制 Visual Studio 2012 用户界面。自定义镀铬并不难,但您应该注意的是难以实现的发光边框。您可以说将窗口的背景颜色设置为透明,并将主网格的填充设置为大约 30 像素。网格周围的边框可以着色并与彩色阴影效果相关联,但这种方法会强制您将 AllowsTransparency 设置为 true,这会大大降低应用程序的视觉性能,这是您绝对不想做的事情!

    我目前创建这样一个窗口的方法是在边框上具有彩色阴影效果并且是透明的但根本没有内容。每当我的主窗口的位置发生变化时,我只需更新包含边框的窗口的位置。所以最后我正在处理两个带有消息的窗口,以假装边框将成为主窗口的一部分。这是必要的,因为 DWM 库没有提供一种为 Windows 提供彩色阴影效果的方法,我认为 Visual Studio 2012 就像我尝试过的那样。

    并用更多信息扩展这篇文章:Office 2013 以不同的方式做到这一点。窗口周围的边框只有 1px 厚和彩色,但阴影是由 DWM 绘制的,代码如this one 这里。如果您可以在没有蓝色/紫色/绿色边框和普通边框的情况下生活,这就是我会选择的方法!只是不要将AllowsTransparency设置为true,否则你就输了。

    这里是我的窗口的屏幕截图,用奇怪的颜色突出它的样子:


    这里有一些关于如何开始的提示

    请记住,我的代码很长,因此我只能向您展示要做的基本事情,而您至少应该能够以某种方式开始。首先,我假设我们已经设计了我们的主窗口(手动或使用我昨天尝试的MahApps.Metro 包 - 对源代码进行了一些修改,这真的很好(1) sup>),我们目前正在努力实现发光的阴影边框,从现在开始我将称之为GlowWindow。最简单的方法是使用以下 XAML 代码创建一个窗口

    <Window x:Class="MetroUI.Views.GlowWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Name="GlowWindow"
        Title="" Width="300" Height="100" WindowStartupLocation="Manual"
        AllowsTransparency="True" Background="Transparent" WindowStyle="None"
        ShowInTaskbar="False" Foreground="#007acc" MaxWidth="5000" MaxHeight="5000">
        <Border x:Name="OuterGlow" Margin="10" Background="Transparent"
                BorderBrush="{Binding Foreground, ElementName=GlowWindow}"
                BorderThickness="5">
            <Border.Effect>
                <BlurEffect KernelType="Gaussian" Radius="15" RenderingBias="Quality" />
            </Border.Effect>
        </Border>
    </Window>
    

    生成的窗口应如下图所示。

    接下来的步骤非常困难 - 当我们的主窗口生成时,我们想让 GlowWindow 在主窗口后面可见,并且我们必须在主窗口被移动或调整大小时更新 GlowWindow 的位置。我建议防止可能和将发生的视觉故障是在窗口位置或大小的每次更改期间隐藏 GlowWindow。完成此类操作后,请再次显示。

    我有一些在不同情况下调用的方法(可能很多,但只是为了确定)

    private void UpdateGlowWindow(bool isActivated = false) {
        if(this.DisableComposite || this.IsMaximized) {
            this.glowWindow.Visibility = System.Windows.Visibility.Collapsed;
            return;
        }
        try {
            this.glowWindow.Left = this.Left - 10;
            this.glowWindow.Top = this.Top - 10;
            this.glowWindow.Width = this.Width + 20;
            this.glowWindow.Height = this.Height + 20;
            this.glowWindow.Visibility = System.Windows.Visibility.Visible;
            if(!isActivated)
                this.glowWindow.Activate();
        } catch(Exception) {
        }
    }
    

    这个方法主要是在我自定义的WndProc中调用的,我已经附加到主窗口了:

    /// <summary>
    /// An application-defined function that processes messages sent to a window. The WNDPROC type
    /// defines a pointer to this callback function.
    /// </summary>
    /// <param name="hwnd">A handle to the window.</param>
    /// <param name="uMsg">The message.</param>
    /// <param name="wParam">Additional message information. The contents of this parameter depend on
    /// the value of the uMsg parameter.</param>
    /// <param name="lParam">Additional message information. The contents of this parameter depend on
    /// the value of the uMsg parameter.</param>
    /// <param name="handled">Reference to boolean value which indicates whether a message was handled.
    /// </param>
    /// <returns>The return value is the result of the message processing and depends on the message sent.
    /// </returns>
    private IntPtr WindowProc(IntPtr hwnd, int uMsg, IntPtr wParam, IntPtr lParam, ref bool handled) {
        // BEGIN UNMANAGED WIN32
        switch((WinRT.Message)uMsg) {
            case WinRT.Message.WM_SIZE:
                switch((WinRT.Size)wParam) {
                    case WinRT.Size.SIZE_MAXIMIZED:
                        this.Left = this.Top = 0;
                        if(!this.IsMaximized)
                            this.IsMaximized = true;
                        this.UpdateChrome();
                        break;
                    case WinRT.Size.SIZE_RESTORED:
                        if(this.IsMaximized)
                            this.IsMaximized = false;
                        this.UpdateChrome();
                        break;
                }
                break;
    
            case WinRT.Message.WM_WINDOWPOSCHANGING:
                WinRT.WINDOWPOS windowPosition = (WinRT.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WinRT.WINDOWPOS));
                Window handledWindow = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
                if(handledWindow == null)
                    return IntPtr.Zero;
                bool hasChangedPosition = false;
                if(this.IsMaximized == true && (this.Left != 0 || this.Top != 0)) {
                    windowPosition.x = windowPosition.y = 0;
                    windowPosition.cx = (int)SystemParameters.WorkArea.Width;
                    windowPosition.cy = (int)SystemParameters.WorkArea.Height;
                    hasChangedPosition = true;
                    this.UpdateChrome();
                    this.UpdateGlowWindow();
                }
                if(!hasChangedPosition)
                    return IntPtr.Zero;
                Marshal.StructureToPtr(windowPosition, lParam, true);
                handled = true;
                break;
        }
        return IntPtr.Zero;
        // END UNMANAGED WIN32
    }
    

    但是仍然存在一个问题 - 一旦您调整主窗口的大小,GlowWindow 将无法以其大小覆盖整个窗口。也就是说,如果您将主窗口的大小调整到屏幕的 MaxWidth 左右,那么 GlowWindow 的宽度将是相同的值 + 20,因为我向其添加了 10 的边距。因此,右边缘会在看起来难看的主窗口右边缘之前被中断。为了防止这种情况,我使用了一个钩子来使 GlowWindow 成为一个工具窗口:

    this.Loaded += delegate {
        WindowInteropHelper wndHelper = new WindowInteropHelper(this);
        int exStyle = (int)WinRT.GetWindowLong(wndHelper.Handle, (int)WinRT.GetWindowLongFields.GWL_EXSTYLE);
        exStyle |= (int)WinRT.ExtendedWindowStyles.WS_EX_TOOLWINDOW;
        WinRT.SetWindowLong(wndHelper.Handle, (int)WinRT.GetWindowLongFields.GWL_EXSTYLE, (IntPtr)exStyle);
    };
    

    我们仍然会遇到一些问题 - 当您将鼠标悬停在 GlowWindow 上并左键单击时,它将被激活并获得焦点,这意味着它将与主窗口重叠,如下所示:

    为防止这种情况发生,只需捕获边框的Activated 事件并将主窗口置于前台即可。

    你应该怎么做?

    我建议不要尝试这个 - 我花了大约一个月的时间来实现我想要的,但它仍然存在一些问题,因此我会采用像 Office 2013 那样的方法 - 彩色边框和 DWM 的常见阴影API 调用 - 没有别的,而且看起来还不错。


    (1) 我刚刚编辑了一些文件以启用在 Window 8 上为我禁用的窗口周围的边框。此外,我还操纵了标题栏的Padding,使其看起来不那么被挤压,最后我更改了 All-Caps 属性以模仿 Visual Studio 呈现标题的方式。到目前为止,MahApps.Metro 是绘制主窗口的更好方法,因为它甚至支持 AeroSnap,我无法通过通常的 P/Invoke 调用实现。

    【讨论】:

    • 克里斯蒂安,感谢您的 cmets。请问,你能为这种方法提供一个简单的代码示例吗? “......边框上的彩色阴影效果,透明但根本没有内容”。非常感谢。
    • @ChristianIvicevic:在我的博客上查看这篇文章:mingslogar.comule.com/posts/2014/02/28_2.php。如果您无法查看,请告诉我,我将在此处发布代码。
    • 将 WS_EX_TRANSPARENT 添加到 GlowWindow 的 exStylesthis.Loaded 事件中,GlowWindow 将不再可点击。
    • @Stefano 对此我深表歉意;我的网站暂时关闭。您可以在此处查看原始代码:1drv.ms/1C9N8xI(只需在浏览器中打开 HTML 文件)。另一位开发人员使用我网站上的代码创建了windowglows.codeplex.com。我还没有测试过,但也可以随意检查一下。
    • @MingSlogar 我已经回到这个项目并更新了我的帖子 - 请随时查看。
    【解决方案4】:

    这称为“Metro 风格”(Windows 8 风格)。我认为this Code Project 文章对您来说很有趣,它会对您有所帮助。

    您可以尝试Elysium,它在 MIT 许可下获得许可并包含 ApplicationBar 和 ToastNotification 类,或者也来自 codeplext 的 MetroToolKit

    This 是一个关于极乐世界的很棒的教程,我认为它对你有帮助。

    对于阴影,只需将 BitmapEffect 添加到 XAML 中的 Grid 中的 Border

    <Grid>
        <Border BorderBrush="#FF006900" BorderThickness="3" Height="157" HorizontalAlignment="Left" Margin="12,12,0,0" Name="border1" VerticalAlignment="Top" Width="479" Background="#FFCEFFE1" CornerRadius="20, 20, 20, 20">
            <Border.BitmapEffect>
              <DropShadowBitmapEffect Color="Black" Direction="320" ShadowDepth="10" Opacity="0.5" Softness="5" />
            </Border.BitmapEffect>
            <TextBlock Height="179" Name="textBlock1" Text="Hello, this is a beautiful DropShadow WPF Window Example." FontSize="40" TextWrapping="Wrap" TextAlignment="Center" Foreground="#FF245829" />
        </Border>
    </Grid>
    

    【讨论】:

    • 但是,我的问题呢,我只需要添加边框阴影,我不需要工具包。
    【解决方案5】:

    我正在尝试获得相同的效果,我的应用正在使用 .NET 4,因此我无法直接使用 WindowChrome(因此,我正在使用 Microsoft Windows Shell 库来获得相同的效果)。

    thread 中正确指出,使用 spy++ 可以看到 Visual Studio 有四个称为 VisualStudioGlowWindow 的窗口来实现发光效果。 已经在很多地方描述了将AllowsTransparency 属性设置为true 会降低性能。

    所以,我尝试走VS的方式,结果还不错(至少对我来说);无需在主窗口上使用模糊或类似效果,我只需要与一些窗口状态(焦点/可见/隐藏)进行一些斗争。

    我已将所有需要的东西放在 github 上 - 希望这能有所帮助。

    【讨论】:

    • 我以为问题是“你知道如何使用 WPF 实现它吗?”我在 WPF 中对此进行了完整的实现:我添加了一个指向包含完整源代码的存储库的链接(我想在帖子中复制/粘贴 500 行代码没有什么意义......)。
    • Github 链接已失效 :(
    猜你喜欢
    • 2017-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-09
    • 1970-01-01
    • 1970-01-01
    • 2019-01-10
    • 2012-11-02
    相关资源
    最近更新 更多