Ответ 1
Обновление (октябрь '17)
Прошло уже четыре года, и мне было интересно снова заняться этим, и поэтому я снова возился с MahApps.Metro и получил мои собственные библиотеки на его основе. Моя библиотека ModernChrome предоставляет настраиваемое окно, которое выглядит как Visual Studio 2017:
Поскольку вы, скорее всего, интересуетесь только частью светящейся границы, вы должны либо использовать сам MahApps.Metro, либо посмотреть, как я создал класс GlowWindowBehavior
, который привязывает границы свечения к моему пользовательскому классу ModernWindow
. Он зависит от некоторых внутренних элементов MahApps.Metro и двух свойств зависимостей GlowBrush
и NonActiveGlowBrush
.
Если вы хотите включить только светящиеся границы в свои пользовательские приложения, просто укажите MahApps.Metro и скопируйте мой GlowWindowBehavior.cs
и создайте собственный класс окон и соответствующим образом скопируйте ссылки. Это всего лишь 15 минут.
Этот вопрос и мой ответ были доступны очень часто, поэтому я надеюсь, что вы найдете мое новое подходящее решение полезным:)
Оригинальное сообщение (февраль '13)
Я работаю над такой библиотекой, чтобы скопировать пользовательский интерфейс Visual Studio 2012. Пользовательский хром не так уж и сложный, но то, что вам следует позаботиться, - это светящаяся граница, которую трудно реализовать. Вы могли бы просто сказать, чтобы цвет фона вашего окна был прозрачным и установил прокладку основной сетки примерно на 30 пикселей. Граница вокруг сетки может быть окрашена и связана с эффектом цветной тени, но этот подход заставляет вас установить AllowsTransparency
значение true, которое резко снижает визуальную производительность вашего приложения, и это то, что вы определенно не хотите делать!
Мой нынешний подход к созданию такого окна, который имеет только цветной эффект тени на границе и прозрачен, но не имеет содержимого вообще. Evertime меняет положение моего главного окна. Я просто обновляю позицию окна, которое удерживает границу. Поэтому в конце я обрабатываю два окна с сообщениями о том, что граница будет частью главного окна. Это было необходимо, потому что библиотека DWM не предоставляет способ иметь цветной эффект тени для окон, и я думаю, что 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 не сможет охватить все окно с его размером. То есть, если вы измените размер основного окна на максимальную ширину экрана, тогда ширина 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
в строке заголовка таким образом, что он не выглядит так, что sqiedzed inplace, и, наконец, я изменил свойство All-Caps, чтобы имитировать способ визуализации Visual Studio. Пока MahApps.Metro
- лучший способ рисовать главное окно, поскольку он даже поддерживает AeroSnap, я не мог реализовать обычные вызовы P/Invoke.