更新(2017 年 10 月)
现在已经四年了,我有兴趣再次解决这个问题,因此我一直在搞乱MahApps.Metro再次和基于它派生出我自己的库 https://github.com/ChristianIvicevic/ModernChrome. My 现代镀铬库提供了一个类似于 Visual Studio 2017 的自定义窗口:
由于您很可能只对发光边框的部分感兴趣,因此您应该使用MahApps.Metro本身或看看我如何创建一个类GlowWindowBehavior
它将发光边框附加到我的自定义中ModernWindow
班级。它严重依赖于某些内部结构MahApps.Metro和两个依赖属性GlowBrush
and NonActiveGlowBrush
.
如果您只想在自定义应用程序中包含发光边框,请参考MahApps.Metro并复制我的GlowWindowBehavior.cs
并创建一个自定义窗口类并相应地调整引用。这最多也就15分钟的事情。
这个问题和我的答案已经被频繁访问,所以我希望您会发现我最新的正确解决方案很有用:)
原帖(2013 年 2 月)
我一直在开发这样一个库来复制 Visual Studio 2012 用户界面。自定义镀铬并不那么困难,但您应该注意的是很难实现的发光边框。您可以将窗口的背景颜色设置为透明,并将主网格的填充设置为大约 30 像素。网格周围的边框可以是彩色的,并与彩色阴影效果相关联,但这种方法迫使您设置AllowsTransparency
设置为 true 会大大降低应用程序的视觉性能,这是您绝对不想做的事情!
我当前创建这样一个窗口的方法,该窗口仅在边框上具有彩色阴影效果并且是透明的,但根本没有内容。每当主窗口的位置发生变化时,我只需更新包含边框的窗口的位置。所以最后我正在处理两个带有消息的窗口,以假装边框将成为主窗口的一部分。这是必要的,因为 DWM 库没有提供一种为窗口提供彩色阴影效果的方法,而且我认为 Visual Studio 2012 的做法与我尝试的类似。
并用更多信息来扩展这篇文章:Office 2013 的做法有所不同。窗口周围的边框只有 1px 厚且有颜色,但阴影是由 DWM 使用如下代码绘制的this one https://stackoverflow.com/a/6313576/796036这里。如果你可以在没有蓝色/紫色/绿色边框而只有普通边框的情况下生活,这就是我会选择的方法!只是不设置AllowsTransparency
为真,否则你就输了。
这是我的窗口的屏幕截图,用奇怪的颜色突出显示它的样子:
以下是有关如何开始的一些提示
Please keep in mind that my code is quite long, such that I will only be able to show you the basic things to do and you should be able to at least start somehow. First of all I'm going to assume that we have designed our main window somehow (either manually or with the MahApps.Metro
package I tried out yesterday - with some modifications to the sourcecode this is really good(1)) and we are currently working to implement the glowing shadow border, which I will call GlowWindow
from now on. The easiest approach is to create a window with the following XAML code
<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) I have just edited some files to enable the border around the window which is disabled on Window 8 for me. Furthermore I have manipulated the Padding
of the title bar such that it doesn't look that sqeezed inplace and lastly I have change the All-Caps property to mimic Visual Studio's way of rendering the title. So far the MahApps.Metro
is a better way of drawing the main window as it even supports AeroSnap I couldn't implement with usual P/Invoke calls.