Я сделал пример проекта MAUI в Visual Studio 2022, но когда я запускаю программу, строка заголовка окна становится серой, серой, которая вообще не меняет цвет, когда окно теряет фокус (хотя текст заголовка, который я добавил, изменился с черного на до серого). У меня есть «Показать акцентный цвет на следующих поверхностях» с обоими флажками.
Почему мое окно MAUI не использует мой акцентный цвет и как это исправить?
Примечание. У меня Windows 10, поэтому я не могу использовать эту штуку, которая работает только в Windows 11.
Комментарий Александра Мэя ссылается на документ, в котором описывается полная настройка строки заголовка:
Согласно официальной документации Настройка строки заголовка / Полная настройка, существует два уровня настройки, которые вы можете применить к строке заголовка: применить незначительные изменения к строке заголовка по умолчанию или расширить холст приложения в область строки заголовка и предоставить полностью индивидуальный контент.
Из этого документа / содержимого строки заголовка и областей перетаскивания:
<Grid x:Name = "AppTitleBar">
<Image Source = "Images/WindowIcon.png"
HorizontalAlignment = "Left"
Width = "16" Height = "16"
Margin = "8,0"/>
<TextBlock x:Name = "AppTitleTextBlock" Text = "App title"
TextWrapping = "NoWrap"
Style = "{StaticResource CaptionTextBlockStyle}"
VerticalAlignment = "Center"
Margin = "28,0,0,0"/>
</Grid>
public MainWindow()
{
this.InitializeComponent();
ExtendsContentIntoTitleBar = true;
SetTitleBar(AppTitleBar);
AppTitleTextBlock.Text = AppInfo.Current.DisplayInfo.DisplayName;
}
ОРИГИНАЛЬНЫЙ ОТВЕТ
Вкратце: вы не можете управлять цветом строки заголовка в Windows 10. По крайней мере, через API-интерфейсы WinUI-3.
ВАЖНО: этот ответ описывает ситуацию с API WinUI-3.
Я оставлю это кому-то другому, чтобы выяснить, как использовать P/Invoke: создайте приложение C# .NET с WinUI 3 и взаимодействием Win32, чтобы получить API-интерфейсы Win32, которые могут работать в Windows 10.
Это может быть невозможно даже таким образом.
Мне неясно, способно ли окно, созданное WinUI-3 в Windows 10, физически изменить цвет своего заголовка.
Существующие API-интерфейсы WinUI-3 не поддерживают это в Windows 10. WinUI-3 — это то, на что Мауи ориентируется в Windows.
Настройка строки заголовка говорит:
API настройки строки заголовка в настоящее время поддерживаются только в Windows 11. Мы рекомендуем вам проверить
AppWindowTitleBar.IsCustomizationSupported
в своем коде, прежде чем вызывать эти API, чтобы убедиться, что ваше приложение не аварийно завершает работу в других версиях Windows.
Дополнительные сведения приведены в Библиотеке пользовательского интерфейса Windows в Windows App SDK (WinUI 3).
Существует таблица Feature Window AppWindow
, показывающая функции, поддерживаемые в Windows 10.
В этой таблице мы видим, что Window
поддерживается в Windows 10, а AppWindow
— нет.
Это также показывает, что Window
позволяет установить (только) Title
. Нужно AppWindow
изменить цвета.
Согласно официальной документацииСколько стоит настроить строку заголовка, существует два уровня настройки, которые вы можете применить к строке заголовка: применить незначительные изменения к строке заголовка по умолчанию или расширить холст приложения в область строки заголовка и предоставить полностью индивидуальный контент.
Отлично, поэтому нам нужно расширить App Canvas, чтобы нарисовать фальшивую строку заголовка, чтобы обойти проблему... И из того, что мне удалось собрать, это именно то, что MAUI/WinUI3 уже делает, чтобы вызвать проблему. в первую очередь! Таким образом, мы должны нарисовать фальшивую строку заголовка, чтобы заменить собственную фальшивую строку заголовка WinUI3, чтобы исправить ущерб! Расскажите об инверсии абстракции...
К сожалению, приведенный выше код предназначен для работы непосредственно в окне WinUI3, он не совместим со структурой MAUI на основе Shell
.
кажется не совместимым" - может быть, может и нет. Это действительно код для работы непосредственно в окне WinUI3, которое Мауи создает и отображает при работе в Windows. Код должен быть только на WinUI3, а не в кроссплатформенном коде. Если вам нужна помощь в этом, добавьте новый раздел к вашему вопросу. Покажите испробованный код, где вы добавили этот код и что пошло не так.
В конце концов, я использовал код, основанный на этом, чтобы скрыть большую часть серой строки заголовка в Windows, а также зависимость только для Windows от библиотеки Windows Forms, которую я использую для дочернего создания и заключения окна WinUI3 внутри окна Windows. Форма (которая изменяет размер своего дочернего элемента при изменении размера).
Сторона WinForms
Я начинаю с создания базовой формы и добавляю код P/Invoke для дочернего элемента и изменения размера (а также упрощенную форму события FormClosed
).
public partial class Form1 : Form, IContainerForm
{
public Form1()
{
InitializeComponent();
}
static class NativeMethods
{
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetParent(IntPtr hWnd);
public const int GWL_STYLE = -16;
[DllImport("User32.dll", EntryPoint = "GetWindowLong", CharSet = CharSet.Auto)]
public extern static uint GetWindowLongU(IntPtr hwnd, int nIndex);
[DllImport("User32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)]
public extern static uint SetWindowLongU(IntPtr hwnd, int nIndex, uint dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
}
[Flags]
public enum WindowStyles : uint
{
WS_BORDER = 0x800000,
WS_CAPTION = 0xc00000,
WS_CHILD = 0x40000000,
WS_CLIPCHILDREN = 0x2000000,
WS_CLIPSIBLINGS = 0x4000000,
WS_DISABLED = 0x8000000,
WS_DLGFRAME = 0x400000,
WS_GROUP = 0x20000,
WS_HSCROLL = 0x100000,
WS_MAXIMIZE = 0x1000000,
WS_MAXIMIZEBOX = 0x10000,
WS_MINIMIZE = 0x20000000,
WS_MINIMIZEBOX = 0x20000,
WS_OVERLAPPED = 0x0,
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_SIZEFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
WS_POPUP = 0x80000000u,
WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
WS_SIZEFRAME = 0x40000,
WS_SYSMENU = 0x80000,
WS_TABSTOP = 0x10000,
WS_VISIBLE = 0x10000000,
WS_VSCROLL = 0x200000
}
IntPtr hwndContained;
#region IContainerForm members
public IntPtr EnclosedHandle { get => hwndContained; }
public void Enclose(IntPtr hWndToContain)
{
if (Handle==IntPtr.Zero)
throw new InvalidOperationException("Cannot enclose window because current object's window is not created.");
var windowStyles = (WindowStyles)NativeMethods.GetWindowLongU(hWndToContain, NativeMethods.GWL_STYLE);
windowStyles &= ~WindowStyles.WS_OVERLAPPEDWINDOW;
windowStyles &= ~WindowStyles.WS_POPUP;
windowStyles &= ~WindowStyles.WS_CAPTION;
windowStyles |= WindowStyles.WS_CHILD;
NativeMethods.SetWindowLongU(hWndToContain, NativeMethods.GWL_STYLE, (uint)windowStyles);
NativeMethods.SetParent(hWndToContain, Handle);
hwndContained = hWndToContain;
OnSizeChanged(EventArgs.Empty);
}
public string TitleText { get => this.Text; set => this.Text=value; }
private event EventHandler? formClosedSimple;
public event EventHandler FormClosedSimple { add => formClosedSimple+=value; remove => formClosedSimple-=value; }
private void FireFormClosedSimple()
{
if (formClosedSimple != null)
formClosedSimple(this, EventArgs.Empty);
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
FireFormClosedSimple();
}
#endregion
public static bool HasParent(IntPtr hWnd)
{
return NativeMethods.GetParent(hWnd)!=IntPtr.Zero;
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
int width = Width;
int height = Height;
if (width==0 || height==0 || WindowState == FormWindowState.Minimized)
return;
if (hwndContained!=IntPtr.Zero)
{
NativeMethods.MoveWindow(hwndContained, 0, 0, ClientSize.Width, ClientSize.Height, true);
}
}
}
Чтобы вызывающему коду не приходилось напрямую манипулировать типами WinForms, я изолирую его за интерфейсом:
public class Class1
{
public static IContainerForm CreateContainerForm()
{
var form = new Form1();
form.Show();
return form;
}
public static IContainerForm CreateContainerForm(int width, int height)
{
var form = new Form1();
form.Width = width;
form.Height = height;
form.Show();
return form;
}
public static bool HasParent(IntPtr hWnd) => Form1.HasParent(hWnd);
}
public interface IContainerForm
{
void Enclose(IntPtr hWnd);
IntPtr EnclosedHandle { get; }
string TitleText { get; set; }
event EventHandler FormClosedSimple;
}
Вот и все для проекта библиотеки управления Windows Forms.
Сторона МАУИ
Для начала добавлю условную ссылку на библиотеку управления Windows Forms:
<ItemGroup Condition = "$(TargetFramework.Contains('-windows')) != false">
<ProjectReference Include = "..\WinFormsLibrary1\WinFormsLibrary1.csproj" />
</ItemGroup>
Тогда это просто вызов его (но только в Windows) из кода MAUI:
using Microsoft.Extensions.Logging;
using Microsoft.Maui.LifecycleEvents;
#if WINDOWS
using Microsoft.UI;
using Microsoft.UI.Windowing;
#endif
namespace MySecondMauiApp
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
AddTitleBarCodeOnWindows(builder, new System.Drawing.Size(640, 640));
return builder.Build();
}
public static void AddTitleBarCodeOnWindows(MauiAppBuilder builder, System.Drawing.Size size)
{
#if WINDOWS
builder.ConfigureLifecycleEvents(events =>
{
events.AddWindows(wndLifeCycleBuilder =>
{
wndLifeCycleBuilder.OnWindowCreated(window =>
{
IntPtr nativeWindowHandle = WinRT.Interop.WindowNative.GetWindowHandle(window);
WindowId win32WindowsId = Win32Interop.GetWindowIdFromWindow(nativeWindowHandle);
AppWindow winuiAppWindow = AppWindow.GetFromWindowId(win32WindowsId);
if (winuiAppWindow.Presenter is OverlappedPresenter p)
{
window.ExtendsContentIntoTitleBar = false;
p.SetBorderAndTitleBar(false, false);
}
var containerForm = WinFormsLibrary1.Class1.CreateContainerForm(size.Width, size.Height);
containerForm.Enclose(nativeWindowHandle);
containerForm.TitleText = window.Title;
//Apparently necessary since app doesn't close on its own.
//I would have thought closing the form, and therefore destroying the child MAUI Window, would do the trick.
containerForm.FormClosedSimple += (sender, args) => Application.Current.Quit();
});
});
});
#endif
}
}//class
}
Вот и все, теперь у вас есть новое блестящее приложение MAUI, в строке заголовка которого правильно используется ваш цвет акцента, когда он сфокусирован (и становится белым, когда он не сфокусирован):
Конечно, ничего из этого не было бы необходимо, если бы Microsoft в одностороннем порядке не постановила, что все окна WinUI3 будут иметь серую строку заголовка, которая не меняет цвет в зависимости от фокуса, а не соответствует вашему акцентному цвету. Но теперь, по крайней мере, вы можете это исправить.
вы можете изменить платформы/Windows/App.xaml
<maui:MauiWinUIApplication
x:Class = "MauiApp1.WinUI.App"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maui = "using:Microsoft.Maui"
xmlns:local = "using:MauiApp1.WinUI">
<Application.Resources>
<ResourceDictionary>
<SolidColorBrush x:Key = "WindowCaptionBackground">#fff</SolidColorBrush>
<SolidColorBrush x:Key = "WindowCaptionBackgroundDisabled">#fff</SolidColorBrush>
<SolidColorBrush x:Key = "WindowCaptionForeground">#000</SolidColorBrush>
<SolidColorBrush x:Key = "WindowCaptionForegroundDisabled">#000</SolidColorBrush>
</ResourceDictionary>
</Application.Resources>
</maui:MauiWinUIApplication>
если вы хотите изменить цвет строки заголовка во время выполнения
1.Установите этот пакет
<PackageReference Include = "PInvoke.User32" Version = "0.7.124" Condition = "$(TargetFramework.Contains('-windows')) == true" />
2.Добавьте следующий код
using Microsoft.Maui.Platform;
using WinRT.Interop;
using System.Runtime.Versioning;
using PInvoke;
using static PInvoke.User32;
namespace MauiApp1;
[SupportedOSPlatform("Windows")]
public static class TitleBar
{
static Microsoft.UI.Xaml.Window? NativeWindow =>
(Microsoft.UI.Xaml.Window?)Application.Current?.Windows.FirstOrDefault()?.Handler?.PlatformView;
static Microsoft.UI.Xaml.ResourceDictionary Resources =>
Microsoft.UI.Xaml.Application.Current.Resources;
public static void SetColor(Color color)
{
Resources["WindowCaptionBackground"] = color.ToWindowsColor();
Resources["WindowCaptionBackgroundDisabled"] = color.ToWindowsColor();
TriggertTitleBarRepaint();
}
public static void SetStyle(TitleBarStyle style)
{
var color = style switch
{
TitleBarStyle.Default => Colors.Black,
TitleBarStyle.LightContent => Colors.White,
TitleBarStyle.DarkContent => Colors.Black,
_ => throw new NotSupportedException($"{nameof(TitleBarStyle)} {style} is not yet supported on iOS")
};
Resources["WindowCaptionForeground"] = color.ToWindowsColor();
Resources["WindowCaptionForegroundDisabled"] = color.ToWindowsColor();
TriggertTitleBarRepaint();
}
static void TriggertTitleBarRepaint()
{
if (NativeWindow is null)
{
return;
}
var hWnd = WindowNative.GetWindowHandle(NativeWindow);
var activeWindow = User32.GetActiveWindow();
if (hWnd == activeWindow)
{
User32.PostMessage(hWnd, WindowMessage.WM_ACTIVATE, new IntPtr((int)0x00), IntPtr.Zero);
User32.PostMessage(hWnd, WindowMessage.WM_ACTIVATE, new IntPtr((int)0x01), IntPtr.Zero);
}
else
{
User32.PostMessage(hWnd, WindowMessage.WM_ACTIVATE, new IntPtr((int)0x01), IntPtr.Zero);
User32.PostMessage(hWnd, WindowMessage.WM_ACTIVATE, new IntPtr((int)0x00), IntPtr.Zero);
}
}
}
public enum TitleBarStyle
{
Default = 0,
LightContent = 1,
DarkContent = 2
}
3.использовать
#if Windows
TitleBar.SetColor(titleBarColor);
TitleBar.SetStyle(TitleBarStyle.DarkContent);
#endif
Действует как на Win10, так и на Win11
:-( Большой вопрос в том, почему они считают необходимым переопределить обычную строку заголовка Windows, чтобы заменить ее этой серой полосой?