更新(2017 年 10 月)
现在已经四年了,我有兴趣再次解决这个问题,因此我一直在搞砸 MahApps.Metro 和 derived my own library based on it。我的 ModernChrome 库提供了一个类似于 Visual Studio 2017 的自定义窗口:
由于您很可能只对发光边框部分感兴趣,您应该使用 MahApps.Metro 本身或查看我如何创建一个类 GlowWindowBehavior 将发光边框附加到我的自定义ModernWindow 班级。它严重依赖于 MahApps.Metro 的一些内部结构和两个依赖属性 GlowBrush 和 NonActiveGlowBrush。
如果您只想在自定义应用程序中包含发光边框,只需引用 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 调用实现。