제목 표시줄 사용자 지정
Windows 모든 창에 대한 기본 제목 표시줄을 제공하며 앱의 성격에 맞게 사용자 지정할 수 있습니다. 기본 제목 표시줄에는 창 끌기 및 크기 조정과 같은 몇 가지 표준 구성 요소와 핵심 기능이 함께 제공됩니다.
앱의 제목 표시 줄 사용자 지정, 허용되는 제목 표시줄 영역 콘텐츠 및 권장 UI 패턴에 대한 지침은 제목 표시줄 디자인 문서를 참조하세요.
제목 표시줄 구성 요소
이 목록에서는 표준 제목 표시줄의 구성 요소를 설명합니다.
- 제목 표시줄 사각형
- 제목 텍스트
- 시스템 아이콘(UWP 제외)
- 시스템 메뉴 - 앱 아이콘을 클릭하거나 제목 표시줄을 마우스 오른쪽 단추로 클릭하여 액세스
- 캡션 컨트롤
- 최소화 단추
- 최대화/복원 단추
- [닫기] 단추
플랫폼 옵션
제목 표시줄의 정확한 기능과 사용자 지정에 사용할 수 있는 옵션은 UI 플랫폼 및 앱 요구 사항에 따라 달라집니다. 이 문서에서는 WinUI 2에서 Windows 앱 SDK, WinUI 3 또는 UWP를 사용하는 앱의 제목 표시줄을 사용자 지정하는 방법을 보여줍니다.
참고
Windows 앱 SDK 및 UWP에서 사용하는 창 모델에 대한 자세한 비교는 Windowing 기능 마이그레이션을 참조하세요.
- 적용 대상: Windows 앱 SDK
- 중요 API: AppWindow.TitleBar 속성, AppWindowTitleBar 클래스, AppWindow 클래스
Windows 앱 SDK 창 기능은 Win32 HWND 모델을 기반으로 하는 Microsoft.UI.Windowing.AppWindow 클래스를 통해 수행됩니다. 앱에서 AppWindow와 최상위 HWND 간에 1:1 매핑이 있습니다. AppWindow 및 관련 클래스는 제목 표시줄 사용자 지정을 포함하여 앱 최상위 창의 여러 측면을 관리할 수 있는 API를 제공합니다. UI의 나머지 부분과 혼합되도록 Windows 제공하는 기본 제목 표시줄을 수정하거나 앱 캔버스를 제목 표시줄 영역으로 확장하고 고유한 제목 표시줄 콘텐츠를 제공할 수 있습니다.
중요
제목 표시줄 사용자 지정 API는 현재 Windows 11만 지원됩니다. 앱이 다른 버전의 Windows 충돌하지 않도록 이러한 API를 호출하기 전에 코드에서 AppWindowTitleBar.IsCustomizationSupported를 확인하는 것이 좋습니다.
WinUI 3을 사용하는 XAML 앱의 경우 XAML 창 API는 Windows 10 작동하는 제목 표시줄을 사용자 지정하는 더 간단한 방법을 제공합니다. 이러한 API는 Windows 앱 SDK API와 함께 사용할 수 있습니다(WinUI 3 탭 참조).
AppWindow로 작업하는 방법
Windows 앱 SDK 지원하는 모든 UI 프레임워크(Win32, WPF, WinForms 또는 WinUI 3)에서 AppWindow API를 사용할 수 있으며 필요한 API만 사용하여 증분 방식으로 채택할 수 있습니다. interop API를 사용하여 기존 창에서 AppWindow 개체를 가져옵니다. 이 AppWindow 개체를 사용하면 제목 표시줄 사용자 지정 API에 액세스할 수 있습니다. interop API에 대한 자세한 내용은 앱 창 관리 - UI 프레임워크 및 HWND interop 및 Windowing 갤러리 샘플을 참조하세요.
제목 표시줄의 사용자 지정 수준
제목 표시줄에 적용할 수 있는 두 가지 수준의 사용자 지정이 있습니다. 기본 제목 표시줄에 약간의 수정 내용을 적용하거나 앱 캔버스를 제목 표시줄 영역으로 확장하고 완전히 사용자 지정 콘텐츠를 제공합니다.
단순
간단한 사용자 지정은 Windows 앱 SDK 및 UWP/WinUI 2에만 사용할 수 있습니다.
제목 표시줄 색 변경과 같은 간단한 사용자 지정을 위해 앱 창의 제목 표시줄 개체에 속성을 설정하여 제목 표시줄 요소에 사용할 색을 지정할 수 있습니다. 이 경우 시스템은 앱 제목 그리기 및 끌기 영역 정의와 같은 제목 표시줄의 다른 모든 측면에 대한 책임을 유지합니다.
전체
다른 옵션은 기본 제목 표시줄을 숨기고 사용자 지정 콘텐츠로 바꾸는 것입니다. 예를 들어 제목 표시줄 영역에 텍스트, 검색 상자 또는 사용자 지정 메뉴를 배치할 수 있습니다. Mica와 같은 재질 배경을 제목 표시줄 영역으로 확장하려면 이 옵션을 사용해야 합니다.
전체 사용자 지정을 선택하면 제목 표시줄 영역에 콘텐츠를 배치해야 하며 사용자 고유의 끌기 영역을 정의할 수 있습니다. 캡션 컨트롤(시스템 닫기, 최소화 및 최대화 단추)은 시스템에서 계속 사용할 수 있고 처리되지만 앱 제목과 같은 요소는 그렇지 않습니다. 앱에서 필요에 따라 직접 요소를 만들어야 합니다.
간단한 사용자 지정
제목 표시줄 색 또는 아이콘만 사용자 지정하려는 경우 앱 창의 제목 표시줄 개체에서 속성을 설정할 수 있습니다.
(Windows 11. 자세한 내용은 플랫폼 옵션을 참조하세요.)
다음 예제에서는 AppWindow 인스턴스를 가져와 속성을 설정하는 방법을 보여 줍니다.
제목
기본적으로 제목 표시줄은 앱 형식을 창 제목(예: "WinUI Desktop")으로 표시합니다. 창 제목을 변경하려면 AppWindow.Title 속성을 한 줄 텍스트 값으로 설정합니다.
using Microsoft.UI; // Needed for WindowId.
using Microsoft.UI.Windowing; // Needed for AppWindow.
using WinRT.Interop; // Needed for XAML/HWND interop.
private AppWindow m_AppWindow;
public MainWindow()
{
this.InitializeComponent();
m_AppWindow = GetAppWindowForCurrentWindow();
m_AppWindow.Title = "App title";
}
private AppWindow GetAppWindowForCurrentWindow()
{
IntPtr hWnd = WindowNative.GetWindowHandle(this);
WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
return AppWindow.GetFromWindowId(wndId);
}
색
이 예제에서는 AppWindowTitleBar 의 인스턴스를 가져와서 해당 색 속성을 설정하는 방법을 보여줍니다.
private bool SetTitleBarColors()
{
// Check to see if customization is supported.
// Currently only supported on Windows 11.
if (AppWindowTitleBar.IsCustomizationSupported())
{
if (m_AppWindow is null)
{
m_AppWindow = GetAppWindowForCurrentWindow();
}
var titleBar = m_AppWindow.TitleBar;
// Set active window colors
titleBar.ForegroundColor = Colors.White;
titleBar.BackgroundColor = Colors.Green;
titleBar.ButtonForegroundColor = Colors.White;
titleBar.ButtonBackgroundColor = Colors.SeaGreen;
titleBar.ButtonHoverForegroundColor = Colors.Gainsboro;
titleBar.ButtonHoverBackgroundColor = Colors.DarkSeaGreen;
titleBar.ButtonPressedForegroundColor = Colors.Gray;
titleBar.ButtonPressedBackgroundColor = Colors.LightGreen;
// Set inactive window colors
titleBar.InactiveForegroundColor = Colors.Gainsboro;
titleBar.InactiveBackgroundColor = Colors.SeaGreen;
titleBar.ButtonInactiveForegroundColor = Colors.Gainsboro;
titleBar.ButtonInactiveBackgroundColor = Colors.SeaGreen;
return true;
}
return false;
}
아이콘 및 시스템 메뉴
시스템 아이콘을 숨기거나 사용자 지정 아이콘으로 바꿀 수도 있습니다. 시스템 아이콘은 마우스 오른쪽 단추를 클릭하거나 한 번 탭할 때 시스템 메뉴를 표시합니다. 두 번 클릭/탭하면 창이 닫힙니다.
시스템 아이콘 및 관련 동작을 표시하거나 숨기려면 제목 표시줄 IconShowOptions 속성을 설정합니다.
titleBar.IconShowOptions = IconShowOptions.HideIconAndSystemMenu;
참고
IconShowOptions 열거형을 사용하면 이후 릴리스에서 다른 옵션이 추가될 수 있습니다. 관심 있는 경우 GitHub Windows 앱 SDK 리포지토리에 대한 피드백을 제공할 수 있습니다.
사용자 지정 창 아이콘을 사용하려면 AppWindow.SetIcon 메서드 중 하나를 호출하여 새 아이콘을 설정합니다.
SetIcon(String)SetIcon(String) 메서드는 현재 .ico 파일에서만 작동합니다. 이 메서드에 전달하는 문자열은 .ico 파일의 정규화된 경로입니다.
m_AppWindow.SetIcon("iconPath/iconName.ico");SetIcon(IconId)CreateIcon과 같은 아이콘 함수 중 하나에서 아이콘(
HICON)에 대한 핸들이 이미 있는 경우 GetIconIdFromIcon interop API를 사용하여 IconId를 가져올 수 있습니다. 그런 다음 SetIcon(IconId) 메서드에 전달IconId하여 창 아이콘을 설정할 수 있습니다.m_AppWindow.SetIcon(iconId));
제목 표시줄 색상을 설정할 때 주의해야 할 몇 가지 사항이 있습니다.
- 단추 배경색은 닫기 단추 가리키 기 및 누름 상태에 적용되지 않습니다. 닫기 단추는 항상 해당 상태에 대해 시스템 지정 색상을 사용합니다.
- 색 속성을 설정하여
null기본 시스템 색으로 다시 설정합니다. - 투명한 색을 설정할 수 없습니다. 색상의 알파 채널은 무시됩니다.
Windows는 사용자에게 선택한 주목 효과 색을 제목 표시줄에 적용할 수 있는 옵션을 제공합니다. 제목 표시줄 색을 설정하는 경우 모든 색상을 명시적으로 설정하는 것이 좋습니다. 그러면 사용자 지정된 색상 설정으로 인한 의도하지 않은 색상 조합이 발생하지 않습니다.
전체 사용자 지정
전체 제목 표시줄 사용자 지정을 선택하면 앱의 클라이언트 영역이 제목 표시줄 영역을 포함하여 전체 창을 포함하도록 확장됩니다. 창에서 제공하는 캡션 단추를 제외하고 전체 창에 대한 그리기 및 입력 처리를 담당합니다.
기본 제목 표시줄을 숨기고 콘텐츠를 제목 표시줄 영역으로 확장하려면 앱 콘텐츠를 제목 표시줄 영역 true으로 확장하는 속성을 설정합니다. XAML 앱에서 이 속성은 앱의 OnLaunched 메서드(App.xaml.cs) 또는 앱의 첫 번째 페이지에서 설정할 수 있습니다.
팁
모든 코드를 한 번에 보려면 전체 사용자 지정 예제 섹션을 참조하세요.
(Windows 11. 자세한 내용은 플랫폼 옵션을 참조하세요.)
이 예제에서는 AppWindowTitleBar 를 가져와 ExtendsContentIntoTitleBar 속성을 true로 설정하는 방법을 보여 줍니다.
중요
타이틀 바 사용자 지정 API는 앱이 실행될 수 있는 Windows 모든 버전에서 지원되지 않으므로 이러한 API를 호출하기 전에 코드에서 AppWindowTitleBar.IsCustomizationSupported를 확인해야 합니다. 제목 표시줄 사용자 지정이 지원되지 않는 경우 일반적으로 .로 설정 Visibility 하여 사용자 지정 제목 표시줄 UI를 Collapsed숨깁니다.
using Microsoft.UI; // Needed for WindowId
using Microsoft.UI.Windowing; // Needed for AppWindow
using WinRT.Interop; // Needed for XAML/HWND interop
private AppWindow m_AppWindow;
public MainWindow()
{
this.InitializeComponent();
m_AppWindow = GetAppWindowForCurrentWindow();
// Check to see if customization is supported.
// Currently only supported on Windows 11.
if (AppWindowTitleBar.IsCustomizationSupported())
{
var titleBar = m_AppWindow.TitleBar;
// Hide default title bar.
titleBar.ExtendsContentIntoTitleBar = true;
}
else
{
// Title bar customization using these APIs is currently
// supported only on Windows 11. In other cases, hide
// the custom title bar element.
AppTitleBar.Visibility = Visibility.Collapsed;
}
}
private AppWindow GetAppWindowForCurrentWindow()
{
IntPtr hWnd = WindowNative.GetWindowHandle(this);
WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
return AppWindow.GetFromWindowId(wndId);
}
제목 표시줄 콘텐츠 및 끌기 영역
앱이 제목 표시줄 영역으로 확장되면 제목 표시줄에 대한 UI를 정의하고 관리해야 합니다. 여기에는 일반적으로 제목 텍스트와 끌기 영역을 최소한으로 지정하는 것이 포함됩니다. 제목 표시줄의 끌기 영역은 사용자가 클릭하고 끌어 창을 이동할 수 있는 위치를 정의합니다. 또한 사용자가 마우스 오른쪽 단추를 클릭하여 시스템 메뉴를 표시할 수도 있습니다.
허용되는 제목 표시줄 콘텐츠 및 권장 UI 패턴에 대한 자세한 내용은 제목 표시줄 디자인을 참조하세요.
(Windows 11. 자세한 내용은 플랫폼 옵션을 참조하세요.)
콘텐츠를 제목 표시줄 영역으로 확장하면 기본적으로 시스템은 캡션 단추를 제외한 전체 제목 표시줄 영역을 끌기 영역으로 유지합니다. 제목 표시줄에 대화형 콘텐츠를 배치하지 않으면 이 기본 끌기 지역을 있는 그대로 둘 수 있습니다. 제목 표시줄에 대화형 콘텐츠를 배치하는 경우 다음 섹션에서 다루는 끌기 영역을 지정해야 합니다.
이 예제에서는 대화형 콘텐츠가 없는 사용자 지정 제목 표시줄 UI에 대한 XAML을 보여줍니다.
<Grid x:Name="AppTitleBar"
Height="32">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
<ColumnDefinition/>
<ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
</Grid.ColumnDefinitions>
<Image x:Name="TitleBarIcon" Source="/Images/WindowIcon.png"
Grid.Column="1"
HorizontalAlignment="Left"
Width="16" Height="16"
Margin="8,0,0,0"/>
<TextBlock x:Name="TitleTextBlock"
Text="App title"
Style="{StaticResource CaptionTextBlockStyle}"
Grid.Column="1"
VerticalAlignment="Center"
Margin="28,0,0,0"/>
</Grid>
중요
RightPaddingColumn 캡션 LeftPaddingColumn 단추의 공간을 예약하는 데 사용됩니다. 이러한 열의 값은 Width 코드에서 설정되며 나중에 표시됩니다. 코드 및 설명 은 시스템 캡션 단추 섹션을 참조하세요.
대화형 콘텐츠
단추, 메뉴 또는 검색 상자와 같은 대화형 컨트롤을 앱 상단에 배치하여 제목 표시줄에 표시되도록 할 수 있습니다. 그러나 사용자가 창을 이동할 수 있도록 허용하면서 대화형 요소가 사용자 입력을 수신하도록 하기 위해 따라야 하는 몇 가지 규칙이 있습니다.
(Windows 11. 자세한 내용은 플랫폼 옵션을 참조하세요.)
제목 표시줄 영역에 대화형 콘텐츠를 추가하는 경우 사용자가 상호 작용할 수 있도록 해당 콘텐츠 주위에 명시적 끌기 영역을 정의해야 합니다. 사용자 지정 끌기 영역을 설정하면 기본 끌기 영역이 제거되고 시스템에서 필수 끌기 영역을 예약하지 않습니다. 사용자가 창을 이동할 수 있도록 제목 표시줄에 충분한 공간이 있는지 확인할 책임이 있습니다.
끌기 영역을 설정하려면 AppWindowTitleBar.SetDragRectangles 메서드를 호출합니다. 이 메서드는 각각 끌기 영역을 정의하는 사각형 배열을 사용합니다. 제목 표시줄의 크기가 변경되면 새 크기와 일치하도록 끌기 영역을 다시 계산하고 새 값으로 호출 SetDragRectangles 해야 합니다.
실행 중인 시스템에서 지원되지 않는 경우 사용자 지정 제목 표시줄이 표시되지 않습니다. 사용자 지정 제목 표시줄에 배치한 모든 기능에 대한 대체 UI를 제공해야 합니다.
다음은 검색 상자가 있는 사용자 지정 제목 표시줄 UI를 보여 주고 검색 상자 양쪽에서 끌기 사각형을 계산하고 설정하는 방법을 보여 주는 예제입니다. 코드에서 확인할 몇 가지 중요한 사항은 다음과 같습니다.
- 대화형 콘텐츠에
AppTitleBar대한 제목 표시 줄 디자인 지침을 따르려면 그리드 높이를 48로 설정합니다. - 끌기 사각형을 더 쉽게 계산하려면 레이아웃에
Grid여러 개의 명명된 열을 사용합니다. trueMainWindow 생성자에서 설정합니다ExtendsContentIntoTitleBar. 나중에 호출되는 코드에서 설정하는 경우 기본 시스템 제목 표시줄이 먼저 표시되고 숨겨질 수 있습니다.- 요소가 로드된 후 끌기 영역을 계산하기 위해 초기 호출을
AppTitleBar만듭니다(AppTitleBar_Loaded). 그렇지 않으면 계산에 사용되는 요소에 올바른 값이 있다는 보장은 없습니다. - 요소의 크기(
AppTitleBar_SizeChanged)가 변경된 후에만 끌기 사각형 계산을AppTitleBar업데이트합니다. 창Changed이벤트에 종속된 경우 이벤트가 크기 조정되기 전에AppTitleBar발생하는 상황(예: 창 최대화/최소화)이 있고 계산에서 잘못된 값을 사용합니다. - 사용자 지정 제목 표시줄이 지원되고 사용되고 있는지 확인한
IsCustomizationSupportedExtendsContentIntoTitleBar후에만 호출SetDragRectangles합니다.
<Grid x:Name="AppTitleBar"
Height="48">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
<ColumnDefinition x:Name="IconColumn" Width="Auto"/>
<ColumnDefinition x:Name="TitleColumn" Width="Auto"/>
<ColumnDefinition x:Name="LeftDragColumn" Width="*"/>
<ColumnDefinition x:Name="SearchColumn" Width="Auto"/>
<ColumnDefinition x:Name="RightDragColumn" Width="*"/>
<ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
</Grid.ColumnDefinitions>
<Image x:Name="TitleBarIcon" Source="/Images/WindowIcon.png"
Grid.Column="1"
Width="16" Height="16"
Margin="8,0,0,0"/>
<TextBlock x:Name="TitleTextBlock"
Text="App title"
Style="{StaticResource CaptionTextBlockStyle}"
Grid.Column="2"
VerticalAlignment="Center"
Margin="4,0,0,0"/>
<AutoSuggestBox Grid.Column="4" QueryIcon="Find"
PlaceholderText="Search"
VerticalAlignment="Center"
Width="260" Margin="4,0"/>
</Grid>
using System.Runtime.InteropServices;
private AppWindow m_AppWindow;
public MainWindow()
{
this.InitializeComponent();
m_AppWindow = GetAppWindowForCurrentWindow();
// Check to see if customization is supported.
// Currently only supported on Windows 11.
if (AppWindowTitleBar.IsCustomizationSupported())
{
var titleBar = m_AppWindow.TitleBar;
titleBar.ExtendsContentIntoTitleBar = true;
AppTitleBar.Loaded += AppTitleBar_Loaded;
AppTitleBar.SizeChanged += AppTitleBar_SizeChanged;
}
else
{
// Title bar customization using these APIs is currently
// supported only on Windows 11. In other cases, hide
// the custom title bar element.
AppTitleBar.Visibility = Visibility.Collapsed;
// Show alternative UI for any functionality in
// the title bar, such as search.
}
}
private void AppTitleBar_Loaded(object sender, RoutedEventArgs e)
{
// Check to see if customization is supported.
// Currently only supported on Windows 11.
if (AppWindowTitleBar.IsCustomizationSupported())
{
SetDragRegionForCustomTitleBar(m_AppWindow);
}
}
private void AppTitleBar_SizeChanged(object sender, SizeChangedEventArgs e)
{
// Check to see if customization is supported.
// Currently only supported on Windows 11.
if (AppWindowTitleBar.IsCustomizationSupported()
&& m_AppWindow.TitleBar.ExtendsContentIntoTitleBar)
{
// Update drag region if the size of the title bar changes.
SetDragRegionForCustomTitleBar(m_AppWindow);
}
}
private AppWindow GetAppWindowForCurrentWindow()
{
IntPtr hWnd = WindowNative.GetWindowHandle(this);
WindowId wndId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
return AppWindow.GetFromWindowId(wndId);
}
[DllImport("Shcore.dll", SetLastError = true)]
internal static extern int GetDpiForMonitor(IntPtr hmonitor, Monitor_DPI_Type dpiType, out uint dpiX, out uint dpiY);
internal enum Monitor_DPI_Type : int
{
MDT_Effective_DPI = 0,
MDT_Angular_DPI = 1,
MDT_Raw_DPI = 2,
MDT_Default = MDT_Effective_DPI
}
private double GetScaleAdjustment()
{
IntPtr hWnd = WindowNative.GetWindowHandle(this);
WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
DisplayArea displayArea = DisplayArea.GetFromWindowId(wndId, DisplayAreaFallback.Primary);
IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);
// Get DPI.
int result = GetDpiForMonitor(hMonitor, Monitor_DPI_Type.MDT_Default, out uint dpiX, out uint _);
if (result != 0)
{
throw new Exception("Could not get DPI for monitor.");
}
uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);
return scaleFactorPercent / 100.0;
}
private void SetDragRegionForCustomTitleBar(AppWindow appWindow)
{
// Check to see if customization is supported.
// Currently only supported on Windows 11.
if (AppWindowTitleBar.IsCustomizationSupported()
&& appWindow.TitleBar.ExtendsContentIntoTitleBar)
{
double scaleAdjustment = GetScaleAdjustment();
RightPaddingColumn.Width = new GridLength(appWindow.TitleBar.RightInset / scaleAdjustment);
LeftPaddingColumn.Width = new GridLength(appWindow.TitleBar.LeftInset / scaleAdjustment);
List<Windows.Graphics.RectInt32> dragRectsList = new();
Windows.Graphics.RectInt32 dragRectL;
dragRectL.X = (int)((LeftPaddingColumn.ActualWidth) * scaleAdjustment);
dragRectL.Y = 0;
dragRectL.Height = (int)(AppTitleBar.ActualHeight * scaleAdjustment);
dragRectL.Width = (int)((IconColumn.ActualWidth
+ TitleColumn.ActualWidth
+ LeftDragColumn.ActualWidth) * scaleAdjustment);
dragRectsList.Add(dragRectL);
Windows.Graphics.RectInt32 dragRectR;
dragRectR.X = (int)((LeftPaddingColumn.ActualWidth
+ IconColumn.ActualWidth
+ TitleTextBlock.ActualWidth
+ LeftDragColumn.ActualWidth
+ SearchColumn.ActualWidth) * scaleAdjustment);
dragRectR.Y = 0;
dragRectR.Height = (int)(AppTitleBar.ActualHeight * scaleAdjustment);
dragRectR.Width = (int)(RightDragColumn.ActualWidth * scaleAdjustment);
dragRectsList.Add(dragRectR);
Windows.Graphics.RectInt32[] dragRects = dragRectsList.ToArray();
appWindow.TitleBar.SetDragRectangles(dragRects);
}
}
경고
AppWindow 는 논리적 좌표를 사용하지 않는 UI 프레임워크와의 호환성을 위해 물리적 픽셀을 사용합니다. WPF 또는 WinUI 3RightInsetLeftInset을 사용하는 경우 표시 눈금이 100%가 아닌 경우 전달된 SetDragRectangles 값을 조정해야 합니다. 이 예제에서는 표시 크기 조정 설정을 고려할 값을 계산 scaleAdjustment 합니다.
WPF의 경우 Window.DpiChanged 이벤트를 처리하여 NewDpi 값을 가져올 수 있습니다.
WinUI 3의 경우 앞의 예제와 같이 플랫폼 호출(P/Invoke) 을 사용하여 네이티브 GetDpiForMonitor 함수를 호출합니다.
팁
시스템 TitleBar(int titleBarHeight = appWindow.TitleBar.Height;)의 높이를 가져와서 사용자 지정 제목 표시줄 및 끌기 영역의 높이를 설정하는 데 사용할 수 있습니다. 그러나 디자인 지침 에서는 다른 컨트롤을 추가하는 경우 제목 표시줄 높이를 48px로 설정하는 것이 좋습니다. 이 경우 시스템 제목 표시줄의 높이가 콘텐츠와 일치하지 않으므로 대신 제목 표시줄 요소의 ActualHeight 를 사용하여 끌기 영역 높이를 설정합니다.
시스템 캡션 단추
(Windows 11. 자세한 내용은 플랫폼 옵션을 참조하세요.)
시스템은 시스템 캡션 단추(최소화, 최대화/복원, 닫기)에 대해 앱 창의 왼쪽 위 또는 오른쪽 위 모서리를 예약합니다. 시스템은 캡션 단추 영역의 제어를 유지하여 창을 끌어서, 최소화, 최대화 및 닫는 데 필요한 최소 기능을 보장합니다. 시스템은 왼쪽에서 오른쪽으로 쓰는 언어의 경우 오른쪽 상단에, 오른쪽에서 왼쪽으로 쓰는 언어의 경우 왼쪽 상단에 닫기 단추를 그립니다.
앱 배경과 같은 캡션 컨트롤 영역 아래에 콘텐츠를 그릴 수 있지만 사용자가 상호 작용할 수 있을 것으로 예상하는 UI를 배치해서는 안 됩니다. 자막 제어에 대한 입력이 시스템에 의해 처리되므로 입력을 수신하지 못합니다.
이전 예제의 이러한 줄은 제목 표시줄을 정의하는 XAML의 안쪽 여백 열을 보여 줍니다. 여백 대신 안쪽 여백 열을 사용하면 배경이 캡션 컨트롤 단추 아래에 영역을 그립니다(투명한 단추의 경우). 오른쪽 및 왼쪽 안쪽 여백 열을 모두 사용하면 제목 표시줄이 오른쪽에서 왼쪽 및 왼쪽에서 오른쪽 레이아웃 모두에서 올바르게 작동합니다.
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
<ColumnDefinition/>
<ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
</Grid.ColumnDefinitions>
캡션 컨트롤 영역의 크기와 위치는 AppWindowTitleBar 클래스에서 전달되므로 제목 표시줄 UI의 레이아웃에서 이를 설명할 수 있습니다. 양쪽에 있는 예약 영역의 너비는 LeftInset 또는 RightInset 속성에 의해 지정되며 높이가 Height 속성에 의해 지정됩니다.
끌기 영역이 계산되고 설정될 때 안쪽 여백 열의 너비를 지정하는 방법은 다음과 같습니다.
// Get caption button occlusion information.
int CaptionButtonOcclusionWidthRight = appWindow.TitleBar.RightInset;
int CaptionButtonOcclusionWidthLeft = appWindow.TitleBar.LeftInset;
// Set the width of padding columns in the UI.
RightPaddingColumn.Width = new GridLength(CaptionButtonOcclusionWidthRight);
LeftPaddingColumn.Width = new GridLength(CaptionButtonOcclusionWidthLeft);
중요
디스플레이 크기 조정이 이러한 값에 미치는 영향에 대한 대화형 콘텐츠 섹션의 중요한 정보를 참조하세요.
사용자 지정 제목 표시줄에 대한 긴 제목 표시줄 지원
제목 표시줄에 검색 상자 또는 사람 그림과 같은 대화형 콘텐츠를 추가하는 경우 제목 표시줄의 높이를 늘려 이러한 요소에 더 많은 공간을 제공하는 것이 좋습니다. 더 높은 제목 표시줄을 사용하면 터치 조작이 더 쉬워집니다. AppWindowTitleBar.PreferredHeightOption 속성을 사용하면 기본값인 표준 높이에서 더 높은 높이로 제목 표시줄 높이를 늘릴 수 있습니다. 제목 표시줄 모드를 선택하면 Tall 시스템에서 클라이언트 영역에서 오버레이로 그리는 캡션 단추가 최소/최대/닫기 문자 모양을 중심으로 더 크게 렌더링됩니다. 끌기 영역을 지정하지 않은 경우 시스템은 창의 너비와 설정한 값에 따라 PreferredHeightOption 결정되는 높이를 확장하는 영역을 그립니다.
속성이 적용되려면 AppWindowTitleBar.ExtendsContentIntoTitleBar 속성이 있어야 truePreferredHeightOption 합니다. 이전 설정을 ExtendsContentIntoTitlebartrue설정 PreferredHeightOption 하면 설정될 때까지 ExtendsContentIntoTitlebartrue속성이 자동으로 무시됩니다. 이때 적용됩니다.
이 예제에서는 속성을 설정하는 PreferredHeightOption 방법을 보여줍니다.
bool isTallTitleBar = true;
// A taller title bar is only supported when drawing a fully custom title bar
if (AppWindowTitleBar.IsCustomizationSupported() && m_AppWindow.TitleBar.ExtendsContentIntoTitleBar)
{
if (isTallTitleBar)
{
// Choose a tall title bar to provide more room for interactive elements
// like search box or person picture controls.
m_AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
}
else
{
_mainAppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Standard;
}
// Recalculate the drag region for the custom title bar
// if you explicitly defined new draggable areas.
SetDragRegionForCustomTitleBar(_m_AppWindow);
}
캡션 단추의 색 및 투명도
앱 콘텐츠를 제목 표시줄 영역으로 확장하면 캡션 단추의 배경을 투명하게 만들어 앱 배경이 표시되도록 할 수 있습니다. 일반적으로 전체 투명도를 위해 Colors.Transparent 배경을 설정합니다. 부분 투명도의 경우 속성을 설정한 알파 채널을 Color 설정합니다.
(Windows 11. 자세한 내용은 플랫폼 옵션을 참조하세요.)
이러한 제목 표시줄 속성은 투명할 수 있습니다.
- ButtonBackgroundColor
- ButtonHoverBackgroundColor
- ButtonPressedBackgroundColor
- ButtonInactiveBackgroundColor
다른 모든 색상 속성은 알파 채널을 계속 무시합니다. 설정된 false경우 ExtendsContentIntoTitleBar 알파 채널은 모든 AppWindowTitleBar 색 속성에 대해 항상 무시됩니다.
단추 배경색은 닫기 단추 가리키 기 및 누름 상태에 적용되지 않습니다. 닫기 단추는 항상 해당 상태에 대해 시스템 지정 색상을 사용합니다.
팁
Mica 는 포커스가 있는 창을 구별하는 데 도움이 되는 유쾌한 소재 입니다. Windows 11 수명이 긴 창의 배경으로 사용하는 것이 좋습니다. 창의 클라이언트 영역에 Mica를 적용한 경우 제목 표시줄 영역으로 확장하고 Mica가 표시할 캡션 단추를 투명하게 만들 수 있습니다. 자세한 내용은 Mica 자료를 참조하세요.
창이 비활성 상태이면 제목 표시줄을 흐리게 표시합니다.
창이 활성 상태이거나 비활성 상태일 때 이를 분명히 해야 합니다. 최소한 제목 표시줄에서 텍스트, 아이콘 및 단추의 색을 변경해야 합니다.
이벤트를 처리하여 창의 활성화 상태를 확인하고 필요에 따라 제목 표시줄 UI를 업데이트합니다. 창의 상태를 확인하는 방법은 앱에 사용하는 UI 프레임워크에 따라 달라집니다.
- Win32: WM_ACTIVATE 메시지를 수신 대기하고 응답합니다.
- WPF: Window.Activated 처리, Window.Deactivated.
- WinForms: Form.Activated 처리, Form.Deactivate.
- Windows 앱 SDK 제목 표시줄 API가 있는 WinUI 3: Window.Activated 처리(WinUI 3 탭 참조).
제목 표시줄 다시 설정
(Windows 11. 자세한 내용은 플랫폼 옵션을 참조하세요.)
앱이 실행되는 동안 시스템 제목 표시줄을 다시 설정하거나 전환하려면 AppWindowTitleBar.ResetToDefault를 호출할 수 있습니다.
m_AppWindow.TitleBar.ResetToDefault();
제목 표시줄 표시 및 숨기기
전체 화면 또는 컴팩트 오버레이 모드에 대한 지원을 앱에 추가하는 경우 앱이 이러한 모드 간에 전환될 때 제목 표시줄을 변경해야 할 수 있습니다.
(Windows 11. 자세한 내용은 플랫폼 옵션을 참조하세요.)
앱이 전체 화면 모드로 실행되면 시스템에서 제목 표시줄 및 캡션 컨트롤 단추를 숨깁니다. AppWindow.Changed 이벤트를 처리하고 이벤트 인수 DidPresenterChange 속성을 확인하여 새 창 프레젠테이션에 대한 응답으로 제목 표시줄을 표시, 숨기기 또는 변경해야 하는지 확인할 수 있습니다.
이 예제에서는 이전 예제에서 요소를 표시하고 숨기 AppTitleBar 도록 이벤트를 처리하는 Changed 방법을 보여 줍니다. 창이 컴팩트 오버레이 모드로 전환되면 제목 표시줄이 기본 시스템 제목 표시줄로 다시 설정됩니다(또는 컴팩트 오버레이에 최적화된 사용자 지정 제목 표시줄을 제공할 수 있음).
public MainWindow()
{
this.InitializeComponent();
m_AppWindow = GetAppWindowForCurrentWindow();
m_AppWindow.Changed += AppWindow_Changed;
}
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
// Check to see if customization is supported.
// Currently only supported on Windows 11.
if (args.DidPresenterChange
&& AppWindowTitleBar.IsCustomizationSupported())
{
switch (sender.Presenter.Kind)
{
case AppWindowPresenterKind.CompactOverlay:
// Compact overlay - hide custom title bar
// and use the default system title bar instead.
AppTitleBar.Visibility = Visibility.Collapsed;
sender.TitleBar.ResetToDefault();
break;
case AppWindowPresenterKind.FullScreen:
// Full screen - hide the custom title bar
// and the default system title bar.
AppTitleBar.Visibility = Visibility.Collapsed;
sender.TitleBar.ExtendsContentIntoTitleBar = true;
break;
case AppWindowPresenterKind.Overlapped:
// Normal - hide the system title bar
// and use the custom title bar instead.
AppTitleBar.Visibility = Visibility.Visible;
sender.TitleBar.ExtendsContentIntoTitleBar = true;
SetDragRegionForCustomTitleBar(sender);
break;
default:
// Use the default system title bar.
sender.TitleBar.ResetToDefault();
break;
}
}
}
참고
앱에서 지원하는 경우에만 전체 화면 및 컴팩트 오버레이 모드를 입력할 수 있습니다. 자세한 내용은 앱 창, FullScreenPresenter 및 CompactOverlayPresenter 관리를 참조하세요.
권장 사항 및 금지 사항
- 창이 활성 또는 비활성 상태일 때 만든 내용이 명확하게 나타납니다. 최소한 제목 표시줄의 텍스트, 아이콘 및 단추 색상을 변경합니다.
- 앱 캔버스의 위쪽 가장자리를 따라 끌기 영역을 정의합니다. 시스템 제목 표시줄의 배치를 맞추면 사용자가 쉽게 찾을 수 있습니다.
- 앱 캔버스의 시각적 제목 표시줄(있는 경우)과 일치하는 끌기 영역을 정의합니다.
전체 사용자 지정의 예
이 예제에서는 전체 사용자 지정 섹션에 설명된 모든 코드를 보여 줍니다.
(Windows 11. 자세한 내용은 플랫폼 옵션을 참조하세요.)
<Window
x:Class="WASDK_ExtendedTitleBar.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WASDK_ExtendedTitleBar"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid x:Name="AppTitleBar"
Height="48">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
<ColumnDefinition x:Name="IconColumn" Width="Auto"/>
<ColumnDefinition x:Name="TitleColumn" Width="Auto"/>
<ColumnDefinition x:Name="LeftDragColumn" Width="*"/>
<ColumnDefinition x:Name="SearchColumn" Width="Auto"/>
<ColumnDefinition x:Name="RightDragColumn" Width="*"/>
<ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
</Grid.ColumnDefinitions>
<Image x:Name="TitleBarIcon" Source="/Images/WindowIcon.png"
Grid.Column="1"
Width="16" Height="16"
Margin="8,0,0,0"/>
<TextBlock x:Name="TitleTextBlock"
Text="App title"
Style="{StaticResource CaptionTextBlockStyle}"
Grid.Column="2"
VerticalAlignment="Center"
Margin="4,0,0,0"/>
<AutoSuggestBox Grid.Column="4" QueryIcon="Find"
PlaceholderText="Search"
VerticalAlignment="Center"
Width="260" Margin="4,0"/>
</Grid>
<NavigationView Grid.Row="1"
IsBackButtonVisible="Collapsed"
IsSettingsVisible="False">
<StackPanel>
<TextBlock Text="Content"
Style="{ThemeResource TitleTextBlockStyle}"
Margin="32,0,0,0"/>
<StackPanel Grid.Row="1" VerticalAlignment="Center">
<Button Margin="4" x:Name="CompactoverlaytBtn"
Content="Enter CompactOverlay"
Click="SwitchPresenter"/>
<Button Margin="4" x:Name="FullscreenBtn"
Content="Enter FullScreen"
Click="SwitchPresenter"/>
<Button Margin="4" x:Name="OverlappedBtn"
Content="Revert to default (Overlapped)"
Click="SwitchPresenter"/>
</StackPanel>
</StackPanel>
</NavigationView>
</Grid>
</Window>
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using WinRT.Interop;
namespace WASDK_ExtendedTitleBar
{
public sealed partial class MainWindow : Window
{
private AppWindow m_AppWindow;
public MainWindow()
{
this.InitializeComponent();
m_AppWindow = GetAppWindowForCurrentWindow();
m_AppWindow.Changed += AppWindow_Changed;
// Check to see if customization is supported.
// Currently only supported on Windows 11.
if (AppWindowTitleBar.IsCustomizationSupported())
{
var titleBar = m_AppWindow.TitleBar;
titleBar.ExtendsContentIntoTitleBar = true;
AppTitleBar.Loaded += AppTitleBar_Loaded;
AppTitleBar.SizeChanged += AppTitleBar_SizeChanged;
}
else
{
// Title bar customization using these APIs is currently
// supported only on Windows 11. In other cases, hide
// the custom title bar element.
AppTitleBar.Visibility = Visibility.Collapsed;
// Show alternative UI for any functionality in
// the title bar, such as search.
}
}
private void AppTitleBar_Loaded(object sender, RoutedEventArgs e)
{
if (AppWindowTitleBar.IsCustomizationSupported())
{
SetDragRegionForCustomTitleBar(m_AppWindow);
}
}
private void AppTitleBar_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (AppWindowTitleBar.IsCustomizationSupported()
&& m_AppWindow.TitleBar.ExtendsContentIntoTitleBar)
{
// Update drag region if the size of the title bar changes.
SetDragRegionForCustomTitleBar(m_AppWindow);
}
}
private AppWindow GetAppWindowForCurrentWindow()
{
IntPtr hWnd = WindowNative.GetWindowHandle(this);
WindowId wndId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
return AppWindow.GetFromWindowId(wndId);
}
[DllImport("Shcore.dll", SetLastError = true)]
internal static extern int GetDpiForMonitor(IntPtr hmonitor, Monitor_DPI_Type dpiType, out uint dpiX, out uint dpiY);
internal enum Monitor_DPI_Type : int
{
MDT_Effective_DPI = 0,
MDT_Angular_DPI = 1,
MDT_Raw_DPI = 2,
MDT_Default = MDT_Effective_DPI
}
private double GetScaleAdjustment()
{
IntPtr hWnd = WindowNative.GetWindowHandle(this);
WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
DisplayArea displayArea = DisplayArea.GetFromWindowId(wndId, DisplayAreaFallback.Primary);
IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);
// Get DPI.
int result = GetDpiForMonitor(hMonitor, Monitor_DPI_Type.MDT_Default, out uint dpiX, out uint _);
if (result != 0)
{
throw new Exception("Could not get DPI for monitor.");
}
uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);
return scaleFactorPercent / 100.0;
}
private void SetDragRegionForCustomTitleBar(AppWindow appWindow)
{
if (AppWindowTitleBar.IsCustomizationSupported()
&& appWindow.TitleBar.ExtendsContentIntoTitleBar)
{
double scaleAdjustment = GetScaleAdjustment();
RightPaddingColumn.Width = new GridLength(appWindow.TitleBar.RightInset / scaleAdjustment);
LeftPaddingColumn.Width = new GridLength(appWindow.TitleBar.LeftInset / scaleAdjustment);
List<Windows.Graphics.RectInt32> dragRectsList = new();
Windows.Graphics.RectInt32 dragRectL;
dragRectL.X = (int)((LeftPaddingColumn.ActualWidth) * scaleAdjustment);
dragRectL.Y = 0;
dragRectL.Height = (int)(AppTitleBar.ActualHeight * scaleAdjustment);
dragRectL.Width = (int)((IconColumn.ActualWidth
+ TitleColumn.ActualWidth
+ LeftDragColumn.ActualWidth) * scaleAdjustment);
dragRectsList.Add(dragRectL);
Windows.Graphics.RectInt32 dragRectR;
dragRectR.X = (int)((LeftPaddingColumn.ActualWidth
+ IconColumn.ActualWidth
+ TitleTextBlock.ActualWidth
+ LeftDragColumn.ActualWidth
+ SearchColumn.ActualWidth) * scaleAdjustment);
dragRectR.Y = 0;
dragRectR.Height = (int)(AppTitleBar.ActualHeight * scaleAdjustment);
dragRectR.Width = (int)(RightDragColumn.ActualWidth * scaleAdjustment);
dragRectsList.Add(dragRectR);
Windows.Graphics.RectInt32[] dragRects = dragRectsList.ToArray();
appWindow.TitleBar.SetDragRectangles(dragRects);
}
}
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
if (args.DidPresenterChange
&& AppWindowTitleBar.IsCustomizationSupported())
{
switch (sender.Presenter.Kind)
{
case AppWindowPresenterKind.CompactOverlay:
// Compact overlay - hide custom title bar
// and use the default system title bar instead.
AppTitleBar.Visibility = Visibility.Collapsed;
sender.TitleBar.ResetToDefault();
break;
case AppWindowPresenterKind.FullScreen:
// Full screen - hide the custom title bar
// and the default system title bar.
AppTitleBar.Visibility = Visibility.Collapsed;
sender.TitleBar.ExtendsContentIntoTitleBar = true;
break;
case AppWindowPresenterKind.Overlapped:
// Normal - hide the system title bar
// and use the custom title bar instead.
AppTitleBar.Visibility = Visibility.Visible;
sender.TitleBar.ExtendsContentIntoTitleBar = true;
SetDragRegionForCustomTitleBar(sender);
break;
default:
// Use the default system title bar.
sender.TitleBar.ResetToDefault();
break;
}
}
}
private void SwitchPresenter(object sender, RoutedEventArgs e)
{
if (m_AppWindow != null)
{
AppWindowPresenterKind newPresenterKind;
switch ((sender as Button).Name)
{
case "CompactoverlaytBtn":
newPresenterKind = AppWindowPresenterKind.CompactOverlay;
break;
case "FullscreenBtn":
newPresenterKind = AppWindowPresenterKind.FullScreen;
break;
case "OverlappedBtn":
newPresenterKind = AppWindowPresenterKind.Overlapped;
break;
default:
newPresenterKind = AppWindowPresenterKind.Default;
break;
}
// If the same presenter button was pressed as the
// mode we're in, toggle the window back to Default.
if (newPresenterKind == m_AppWindow.Presenter.Kind)
{
m_AppWindow.SetPresenter(AppWindowPresenterKind.Default);
}
else
{
// Else request a presenter of the selected kind
// to be created and applied to the window.
m_AppWindow.SetPresenter(newPresenterKind);
}
}
}
}
}