Responsive GUI
개요
1892 x 1030 크기의 프로그램을 개발하고 있다고 가정해 보자. 만약 해당 프로그램을 실행하는 환경이 변화, 이를테면 디스플레이 배율이 변동하면 설계한 UI의 밸런스가 깨질 수 있다. 이러한 변화를 반영하여 프로그램을 개발하려면 반응형 GUI가 필요하다. 반응형 GUI는 앞서 언급한 환경에 따라 사용자 인터페이스가 자동으로 조절되는 디자인 룰을 의미한다. 이를 C# Winform에서 어떻게 구현할지 알아보는 시간을 가지도록 해보자.
본문
원인 분석
다음과 같이 1900 x 1050 크기의 프로그램을 Full HD(1920 x 1080, 텍스트 배율 100%) 환경에서 실행시키면 별 이상 없이 동작한다. 하지만 텍스트 배율을 125%로 변경하면 어떻게 될까?
텍스트 박스의 위치와 크기는 변하지 않았지만, 폼의 크기가 1.25배 되어서 화면을 벗어나는 것을 확인할 수 있다. 이를 해결하기 위해서 현재 화면에 표시되는 픽셀 수의 밀도와 텍스트 배율을 이용할 수 있다.
DPI
DPI(Dots per Inch)는 1인치당 몇 개의 점(픽셀)을 포함시킬 수 있는지를 나타내는 수치로, DPI가 높을수록 텍스트와 이미지가 더 선명하고 읽기 쉽게 보인다. Windows에서는 기본적으로 96 DPI를 표준으로 사용한다. 현재 사용자가 설정한 디스플레이의 DPI 값을 기본 DPI 값으로 나누면, 현재 화면의 텍스트 배율(스케일링 팩터)을 계산할 수 있다.
따라서, 텍스트 배율 100%에서 개발된 프로그램이 다른 배율에서도 비율이 깨지지 않게 하려면, 폼과 컨트롤의 크기를 해당 배율로 조정해주어야 한다. 이를 통해 다양한 DPI 설정에서도 UI 요소들이 일관된 크기로 표시되도록 할 수 있다.
private const int LOGPIXELSX = 88;
private const float DEFAULT_DPI = 96f; // 기본 DPI를 96으로 고정
private float currentDpi;
private float ratio;
public enum PROCESS_DPI_AWARENESS
{
Process_DPI_Unaware = 0,
Process_System_DPI_Aware = 1,
Process_Per_Monitor_DPI_Aware = 2
}
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("shcore.dll")]
private static extern int SetProcessDpiAwareness(PROCESS_DPI_AWARENESS value);
먼저 다음과 같이 변수를 설정하고, DLL 파일을 불러오자. 윈도우 API는 DPI를 인식하는 함수와, DPI 값을 가져오는 함수를 제공한다. 여기서 LOGPIXELSX는 윈도 API에서 사용되는 상수로, 디스플레이 장치의 논리적 DPI 값을 나타낸다. 후술 할 GetDeviceCaps 함수와 함께 사용되어 디바이스 컨텍스트(Device Context, GUI를 통해 그래픽 출력을 관리하는 Windows 운영 체제의 데이터 구조)의 수평 DPI를 가져오는 데 사용된다.
private void SetDpiAwareness()
{
SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware);
}
애플리케이션의 DPI 인식을 Per Monitor DPI Aware로 설정한다. 이를 통해 애플리케이션은 각모니터의 DPI를 인식하고, 이에 맞게 스케일링을 조절할 수 있게 된다.
private void GetDisplayScalingFactor()
{
IntPtr hdc = GetDC(IntPtr.Zero);
currentDpi = GetDeviceCaps(hdc, LOGPIXELSX);
ReleaseDC(IntPtr.Zero, hdc);
ratio = currentDpi / DEFAULT_DPI;
textBox1.Text = "기본 DPI: " + DEFAULT_DPI.ToString();
textBox2.Text = "현재 DPI: " + currentDpi.ToString();
textBox3.Text = "배율: " + ratio.ToString();
}
현재 디스플레이 배율을 계산하는 GetDisplayScalingFactor 함수다. GetDC 함수를 호출하여 DC 핸들러의 제어권을 얻고, GetDeviceCaps 함수를 통해 현재 DPI 계산한다. 그리고 제어권을 반납한 후, 윈도우에서 설정한 기본 DPI값과 나눠주면 배율을 얻을 수 있다.
public Form1()
{
InitializeComponent();
SetDpiAwareness();
GetDisplayScalingFactor();
}
현재 실행되고 있는 환경에서 다음과 같이 올바르게 설정되고 있다는 것을 확인할 수 있다. 이번에는 텍스트 배율을 바꾸었을 때 Width랑 Height가 변경되게 코드를 변경해 보자.
public Form1()
{
InitializeComponent();
SetDpiAwareness();
GetDisplayScalingFactor();
AdjustFormSize();
}
private void AdjustFormSize()
{
int originalWidth = 1892; // 설정한 초기 폼 너비
int originalHeight = 1030; // 설정한 초기 폼 높이
this.Size = new Size((int)(originalWidth / ratio), (int)(originalHeight / ratio));
textBox4.Text = "폼 크기: " + this.Size.Width.ToString() + ", " + this.Size.Height.ToString();
}
현재 실행되고 있는 환경에서 다음과 같이 올바르게 설정되고 있다는 것을 확인할 수 있다. 즉, 텍스트 배율을 바꾸었을 때 Width랑 Height가 이에 맞게 줄어들게 설정되었다.
총합본
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private const int LOGPIXELSX = 88;
private const float DEFAULT_DPI = 96f; // 기본 DPI를 96으로 고정
private float currentDpi;
private float ratio;
public enum PROCESS_DPI_AWARENESS
{
Process_DPI_Unaware = 0,
Process_System_DPI_Aware = 1,
Process_Per_Monitor_DPI_Aware = 2
}
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("shcore.dll")]
private static extern int SetProcessDpiAwareness(PROCESS_DPI_AWARENESS value);
public Form1()
{
InitializeComponent();
SetDpiAwareness();
GetDisplayScalingFactor();
AdjustFormSize();
}
private void SetDpiAwareness()
{
SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware);
}
private void GetDisplayScalingFactor()
{
IntPtr hdc = GetDC(IntPtr.Zero);
currentDpi = GetDeviceCaps(hdc, LOGPIXELSX);
ReleaseDC(IntPtr.Zero, hdc);
ratio = currentDpi / DEFAULT_DPI;
textBox1.Text = "기본 DPI: " + DEFAULT_DPI.ToString();
textBox2.Text = "현재 DPI: " + currentDpi.ToString();
textBox3.Text = "배율: " + ratio.ToString();
}
private void AdjustFormSize()
{
int originalWidth = 1892; // 설정한 초기 폼 너비
int originalHeight = 1030; // 설정한 초기 폼 높이
this.Size = new Size((int)(originalWidth / ratio), (int)(originalHeight / ratio));
textBox4.Text = "폼 크기: " + this.Size.Width.ToString() + ", " + this.Size.Height.ToString();
}
}
}
요약
WinForm 개발을 할 때, 텍스트 배율과 사용자의 모니터 해상도를 고려해야 함
1. WinAPI 함수 호출
- GetDC, ReleaseDC, GetDeviceCaps, SetProcessDpiAwareness 함수를 사용하여 현재 DPI 값을 얻고 애플리케이션의 DPI 인식을 설정
2. 배율 계산
- 현재 DPI를 기본 DPI(96)로 나누어 배율을 계산
3. 배율 적용
- 계산된 배율을 컨트롤의 크기에 적용하여 크기를 조정
'Language > C#' 카테고리의 다른 글
[C#] 타입(1) 값 형식의 변수 (0) | 2024.08.06 |
---|---|
[C#] C# 개요 및 입출력 (0) | 2024.08.05 |
[C#] WinForm Console 띄우기 (0) | 2024.07.10 |
[C#] JSON 파싱 및 DLL 배포(1) (0) | 2024.07.09 |