나의 개발일지

WinUi 3 Full Custom Window And TitleBar 본문

어플리케이션/WinUi(C#)

WinUi 3 Full Custom Window And TitleBar

인공지능싱글톤 2024. 1. 3. 19:16

NuGet Package

 

App.xaml.cs

using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;

namespace AppTest
{
	public partial class App : Application
	{
		public App()
		{
			this.InitializeComponent();
		}

		protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
		{
			m_window = new MainWindow();
			m_window.ExtendsContentIntoTitleBar = true;
			m_window.Activate();

			var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(m_window);

			Microsoft.UI.WindowId windowId =
				Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);

			var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);

			if (appWindow.Presenter is OverlappedPresenter p)
			{
				p.SetBorderAndTitleBar(false, false);
				p.IsResizable = false;
			}
		}

		private Window m_window;
	}
}

 

MainWindow.xaml

<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="AppTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AppTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:winuiex="using:WinUIEx"
    mc:Ignorable="d">
    <Window.SystemBackdrop>
        <winuiex:TransparentTintBackdrop/>
    </Window.SystemBackdrop>
    <local:GridMain x:Name="Grid_Main" Margin="0" Padding="7" Background="Transparent" PointerMoved="Grid_Main_PointerMoved" PointerPressed="Grid_Main_PointerPressed">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid x:Name="Grid_TitleBar" Grid.Column="0" Grid.Row="0" Margin="0" Padding="0" Background="#B6BBC4" DoubleTapped="Grid_TitleBar_DoubleTapped"
          PointerPressed="Grid_TitleBar_PointerPressed">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="40"/>
                <ColumnDefinition Width="40"/>
                <ColumnDefinition Width="40"/>
            </Grid.ColumnDefinitions>
            <Grid Grid.Column="1" Margin="0" Tapped="Grid_Minimize_Tapped"
              PointerEntered="Grid_PointerEntered" PointerExited="Grid_PointerExited">
                <FontIcon Foreground="#656565"  Glyph="&#xE921;" FontSize="12"/>
            </Grid>
            <Grid Grid.Column="2" Margin="0" Tapped="Grid_Maximize_Tapped"
              PointerEntered="Grid_PointerEntered" PointerExited="Grid_PointerExited">
                <FontIcon x:Name="FI_Maximize" Foreground="#656565"  Glyph="&#xE922;" FontSize="12"/>
            </Grid>
            <Grid Grid.Column="3" Margin="0" Tapped="Grid_Close_Tapped" 
              PointerEntered="Grid_PointerEntered" PointerExited="Grid_PointerExited">
                <FontIcon Foreground="#656565"  Glyph="&#xE8BB;" FontSize="12"/>
            </Grid>
        </Grid>
        <Frame x:Name="ContentFrame" Background="#FFFFFFFF" Grid.Column="0" Grid.Row="1"/>
    </local:GridMain>
</Window>

 

MainWindow.xaml.cs

using Microsoft.UI;
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Windows.Graphics;
using Windows.UI;
using WinRT.Interop;

namespace AppTest
{
	public sealed partial class MainWindow : Window
	{
		private Microsoft.UI.Windowing.AppWindow _appWindow;
		private OverlappedPresenter _appWindow_overlapped;
		private DisplayArea _displayArea;
		private int _Grid_Main_Padding;
		private int _realWindowMinWidth, _realWindowMinHeight;

		[StructLayout(LayoutKind.Sequential)]
		public struct POINT
		{
			public int X;
			public int Y;
		}

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool GetCursorPos(out POINT lpPoint);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool GetAsyncKeyState(int vKey);
		const int VK_LBUTTON = 0x01;
		private bool IsLeftMouseButtonPressed()
		{
			return GetAsyncKeyState(VK_LBUTTON) & 0x8000 != 0;
		}

		#region [| Get Cursor Icon Using Cursor Position (마우스 위치 기반 아이콘 가져오기)|]
		private InputSystemCursor GetCursorShape(POINT mousePosition)
		{
			if (mousePosition.X < _appWindow.Position.X + _Grid_Main_Padding)
			{
				if (mousePosition.Y < _appWindow.Position.Y + 2 * _Grid_Main_Padding)
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNorthwestSoutheast);
				}
				else if (mousePosition.Y > _appWindow.Position.Y + _appWindow.Size.Height - 2 * _Grid_Main_Padding)
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNortheastSouthwest);
				}
				else
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeWestEast);
				}
			}
			else if (mousePosition.X > _appWindow.Position.X + _appWindow.Size.Width - _Grid_Main_Padding)
			{
				if (mousePosition.Y < _appWindow.Position.Y + 2 * _Grid_Main_Padding)
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNortheastSouthwest);
				}
				else if (mousePosition.Y > _appWindow.Position.Y + _appWindow.Size.Height - 2 * _Grid_Main_Padding)
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNorthwestSoutheast);
				}
				else
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeWestEast);
				}
			}
			else if (mousePosition.Y < _appWindow.Position.Y + _Grid_Main_Padding)
			{
				if (mousePosition.X < _appWindow.Position.X + 2 * _Grid_Main_Padding)
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNorthwestSoutheast);
				}
				else if (mousePosition.X > _appWindow.Position.X + _appWindow.Size.Width - 2 * _Grid_Main_Padding)
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNortheastSouthwest);
				}
				else
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNorthSouth);
				}
			}
			else if (mousePosition.Y > _appWindow.Position.Y + _appWindow.Size.Height - _Grid_Main_Padding)
			{
				if (mousePosition.X < _appWindow.Position.X + 2 * _Grid_Main_Padding)
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNortheastSouthwest);
				}
				else if (mousePosition.X > _appWindow.Position.X + _appWindow.Size.Width - 2 * _Grid_Main_Padding)
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNorthwestSoutheast);
				}
				else
				{
					return InputSystemCursor.Create(InputSystemCursorShape.SizeNorthSouth);
				}
			}
			else
			{
				return InputSystemCursor.Create(InputSystemCursorShape.Arrow);
			}
		}
		#endregion

		#region [| Get Hex To Color (색상코드 Color로 변환) |]
		public static Color GetColor(string hex)
		{
			hex = hex.Replace("#", string.Empty);
			if (hex.Length == 6)
			{
				hex = "FF" + hex;
			}
			byte a = (byte)(Convert.ToUInt32(hex.Substring(0, 2), 16));
			byte r = (byte)(Convert.ToUInt32(hex.Substring(2, 2), 16));
			byte g = (byte)(Convert.ToUInt32(hex.Substring(4, 2), 16));
			byte b = (byte)(Convert.ToUInt32(hex.Substring(6, 2), 16));
			return Color.FromArgb(a, r, g, b);
		}
		#endregion

		public MainWindow()
		{
			this.InitializeComponent();

			using (var manager = WinUIEx.WindowManager.Get(this))
			{
				manager.Width = 500;
				manager.Height = 500;
			}

			IntPtr hWnd = WindowNative.GetWindowHandle(this);
			WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
			_appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(wndId);
			_realWindowMinWidth = _appWindow.Size.Width;
			_realWindowMinHeight = _appWindow.Size.Height;
			_appWindow_overlapped = (OverlappedPresenter)_appWindow.Presenter;
			_appWindow_overlapped.IsMaximizable = false;
			if (_appWindow is not null)
			{
				_displayArea = DisplayArea.GetFromWindowId(wndId, DisplayAreaFallback.Nearest);
				if (_displayArea is not null)
				{
					// Open Window Center
					// 윈도우 화면 중앙으로
					var position = _appWindow.Position;
					position.X = ((_displayArea.WorkArea.Width - _appWindow.Size.Width) / 2);
					position.Y = ((_displayArea.WorkArea.Height - _appWindow.Size.Height) / 2);
					_appWindow.Move(position);
				}
			}

			_Grid_Main_Padding = (int)Grid_Main.Padding.Top;
		}

		#region [| Side Cursor Change (테두리 마우스 아이콘 변환) |]
		private void Grid_Main_PointerMoved(object sender, PointerRoutedEventArgs e)
		{
			if (!_appWindow_overlapped.IsMaximizable)
			{
				GridMain gridMain = sender as GridMain;
				POINT mousePosition;
				GetCursorPos(out mousePosition);

				gridMain.ChangeCursor(GetCursorShape(mousePosition));
			}
		}
		#endregion

		#region [| Side Cursor Change Width Height (테두리 마우스 아이콘 변환) |]
		private async void Grid_Main_PointerPressed(object sender, PointerRoutedEventArgs e)
		{
			if (sender is GridMain gridMain && e.OriginalSource == sender)
			{
				if (e.GetCurrentPoint(gridMain).Properties.IsLeftButtonPressed)
				{
					int windowWidth, windowHeight;
					using (var manager = WinUIEx.WindowManager.Get(this))
					{
						windowWidth = (int)manager.Width;
						windowHeight = (int)manager.Height;
					}
					int realWindowWidth = _appWindow.Size.Width;
					int realWindowHeight = _appWindow.Size.Height;

					POINT windowPosition = new POINT { X = _appWindow.Position.X, Y = _appWindow.Position.Y };
					POINT pressedPosition = new POINT { X = (int)e.GetCurrentPoint(gridMain).Position.X, Y = (int)e.GetCurrentPoint(gridMain).Position.Y };
					bool isLeft = pressedPosition.X <= 2 * _Grid_Main_Padding;
					bool isTop = pressedPosition.Y <= 2 * _Grid_Main_Padding;
					bool isOnlyWidth = !isTop && pressedPosition.Y <= windowHeight - 2 * _Grid_Main_Padding;
					bool isOnlyHeight = !isLeft && pressedPosition.X <= windowWidth - 2 * _Grid_Main_Padding;
					int padding_x = windowWidth - pressedPosition.X;
					int padding_y = windowHeight - pressedPosition.Y;
					int width, height;
					POINT mousePosition;
					POINT windowMovePostion = new POINT();
					while (IsLeftMouseButtonPressed())
					{
						GetCursorPos(out mousePosition);
						if (isOnlyWidth)
						{
							width = isLeft ? realWindowWidth + windowPosition.X - mousePosition.X + pressedPosition.X : mousePosition.X - windowPosition.X + padding_x;
							height = realWindowHeight;
							windowMovePostion.X = isLeft ? mousePosition.X - pressedPosition.X : windowPosition.X;
							windowMovePostion.Y = windowPosition.Y;
						}
						else if (isOnlyHeight)
						{
							width = realWindowWidth;
							height = isTop ? realWindowHeight + windowPosition.Y - mousePosition.Y + pressedPosition.Y : mousePosition.Y - windowPosition.Y + padding_y;
							windowMovePostion.X = windowPosition.X;
							windowMovePostion.Y = isTop ? mousePosition.Y - pressedPosition.Y : windowPosition.Y;
						}
						else
						{
							width = isLeft ? realWindowWidth + windowPosition.X - mousePosition.X + pressedPosition.X : mousePosition.X - windowPosition.X + padding_x;
							height = isTop ? realWindowHeight + windowPosition.Y - mousePosition.Y + pressedPosition.Y : mousePosition.Y - windowPosition.Y + padding_y;
							windowMovePostion.X = isLeft ? mousePosition.X - pressedPosition.X : windowPosition.X;
							windowMovePostion.Y = isTop ? mousePosition.Y - pressedPosition.Y : windowPosition.Y;
						}
						await Task.Run(() => {
							_appWindow.MoveAndResize(new RectInt32(
								_realWindowMinWidth > width ? _appWindow.Position.X : windowMovePostion.X,
								_realWindowMinHeight > height ? _appWindow.Position.Y : windowMovePostion.Y,
								_realWindowMinWidth > width ? _realWindowMinWidth : width,
								_realWindowMinHeight > height ? _realWindowMinHeight : height
								));
						});
					}
					_appWindow_overlapped = (OverlappedPresenter)_appWindow.Presenter;
				}
			}
		}
		#endregion

		#region [| Move Window Using TitleBar (윈도우 이동) |]
		private void Grid_TitleBar_PointerPressed(object sender, PointerRoutedEventArgs e)
		{
			if (sender is Grid grid && e.OriginalSource == sender)
			{
				if (e.GetCurrentPoint(grid).Properties.IsLeftButtonPressed)
				{
					POINT mousePosition;
					POINT mouseDownPosition;
					var position = _appWindow.Position;

					GetCursorPos(out mousePosition);
					mouseDownPosition.X = mousePosition.X - position.X;
					mouseDownPosition.Y = mousePosition.Y - position.Y;

					while (IsLeftMouseButtonPressed())
					{
						GetCursorPos(out mousePosition);
						position.X = mousePosition.X - mouseDownPosition.X;
						position.Y = mousePosition.Y - mouseDownPosition.Y;
						_appWindow.Move(position);
					}
				}
			}
		}
		#endregion

		#region [| TitleBar DoubleTapped (TitleBar 더블클릭) |]
		private void Grid_TitleBar_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
		{
			if (e.OriginalSource == sender)
			{
				if (_appWindow_overlapped.IsMaximizable)
				{
					Grid_Main.Padding = new Thickness(_Grid_Main_Padding);
					_appWindow_overlapped.Restore();
					_appWindow_overlapped.IsMaximizable = false;
					FI_Maximize.Glyph = "\uE922";
				}
				else
				{
					Grid_Main.Padding = new Thickness(0);
					_appWindow_overlapped.Maximize();
					_appWindow_overlapped.IsMaximizable = true;
					FI_Maximize.Glyph = "\uE923";
				}
			}
		}
		#endregion

		#region [| Window Minimize (윈도우 최소화) |]
		private void Grid_Minimize_Tapped(object sender, RoutedEventArgs e)
		{
			_appWindow_overlapped.Minimize();
		}
		#endregion

		#region [| Window Maximize (윈도우 최대화) |]
		private void Grid_Maximize_Tapped(object sender, RoutedEventArgs e)
		{
			if (_appWindow_overlapped.IsMaximizable)
			{
				Grid_Main.Padding = new Thickness(_Grid_Main_Padding);
				_appWindow_overlapped.Restore();
				_appWindow_overlapped.IsMaximizable = false;
				FI_Maximize.Glyph = "\uE922";
			}
			else
			{
				Grid_Main.Padding = new Thickness(0);
				_appWindow_overlapped.Maximize();
				_appWindow_overlapped.IsMaximizable = true;
				FI_Maximize.Glyph = "\uE923";
			}
		}
		#endregion

		#region [| Close Window (윈도우 닫기) |]
		private void Grid_Close_Tapped(object sender, RoutedEventArgs e)
		{
			this.Close();
		}
		#endregion

		#region [| Gird Mouse Over Background Color Change (마우스 오버 효과) |]
		private Grid _grid;
		private void Grid_PointerEntered(object sender, PointerRoutedEventArgs e)
		{
			_grid = sender as Grid;
			_grid.Background = new SolidColorBrush(GetColor("#40000000"));
		}

		private void Grid_PointerExited(object sender, PointerRoutedEventArgs e)
		{
			_grid = sender as Grid;
			_grid.Background = new SolidColorBrush(GetColor("#00000000"));
		}
		#endregion
	}
	public class GridMain : Grid
	{
		public GridMain()
		{
		}
		public void ChangeCursor(InputCursor cursor)
		{
			this.ProtectedCursor = cursor;
		}

		public InputCursor GetCursor()
		{
			return this.ProtectedCursor;
		}
	}
}

 

결과