Language/C#

[C#] 반응형 GUI

nowkoes 2024. 7. 7. 17:47

Responsive GUI

개요

 

 1892 x 1030 크기의 프로그램을 개발하고 있다고 가정해 보자. 만약 해당 프로그램을 실행하는 환경이 변화, 이를테면 디스플레이 배율이 변동하면 설계한 UI의 밸런스가 깨질 수 있다. 이러한 변화를 반영하여 프로그램을 개발하려면 반응형 GUI가 필요하다. 반응형 GUI는 앞서 언급한 환경에 따라 사용자 인터페이스가 자동으로 조절되는 디자인 룰을 의미한다. 이를 C# Winform에서 어떻게 구현할지 알아보는 시간을 가지도록 해보자.


본문

원인 분석

 

 다음과 같이 1900 x 1050 크기의 프로그램을 Full HD(1920 x 1080, 텍스트 배율 100%) 환경에서 실행시키면 별 이상 없이 동작한다. 하지만 텍스트 배율을 125%로 변경하면 어떻게 될까?

 

 

 텍스트 박스의 위치와 크기는 변하지 않았지만, 폼의 크기가 1.25배 되어서 화면을 벗어나는 것을 확인할 수 있다. 이를 해결하기 위해서 현재 화면에 표시되는 픽셀 수의 밀도와 텍스트 배율을 이용할 수 있다.


DPI

출처: https://itwiki.kr/w/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