21

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

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

带阴影的 Visual Studio 无边框窗口

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

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

4

5 回答 5

33

更新(17 年 10 月)

现在已经四年了,我有兴趣再次解决这个问题,因此我一直在搞乱MahApps.Metro基于它派生了我自己的库。我的ModernChrome库提供了一个类似于 Visual Studio 2017 的自定义窗口:

ModernChrome 示例

由于您很可能只对有关发光边框的部分感兴趣,因此您应该使用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 绘制的,代码如下所示。如果您可以在没有蓝色/紫色/绿色边框和普通边框的情况下生活,这就是我会选择的方法!只是不要设置AllowsTransparency为true,否则你就输了。

这是我的窗口的屏幕截图,带有奇怪的颜色以突出显示它的外观:

地铁用户界面


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

请记住,我的代码很长,因此我只能向您展示基本的操作,您至少应该能够以某种方式开始。首先,我假设我们已经以某种方式设计了我们的主窗口(手动或使用MahApps.Metro我昨天尝试的包 - 对源代码进行了一些修改,这非常好(1))并且我们目前正在努力实现发光的阴影边框,我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 调用的彩色边框和通常的阴影 -没有别的,但它看起来还是不错的。

办公室 2013


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

于 2013-02-09T17:08:38.393 回答
5

您可以使用这个简单的 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> 
于 2013-11-16T03:05:52.560 回答
3

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

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

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

对于阴影,只需在XAML 中添加BitmapEffect一个:BorderGrid

<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>

在此处输入图像描述

于 2013-02-06T13:37:44.973 回答
2
<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>
于 2020-10-21T17:42:05.380 回答
1

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

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

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

我已经把所有需要的东西都放在了github 上——希望这能有所帮助。

于 2014-04-15T11:55:09.413 回答