WinForms의 고DPI 대응 ── 4K 모니터에서 흐려지거나 레이아웃이 깨지는 원인과 현실적인 대처

· 업데이트: · · WinForms, 고DPI, Windows, .NET, C#, .NET Framework, 레거시 유지보수, UI, 기술 상담

수정 이력(7건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
관련 기사 링크를 한국어 permalink에 맞추는 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
기사 맨 앞에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건)에 대응해 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
용어 설명과, 증상을 직접 재현·확인하는 절차를 추가했습니다. 설정 조작 절차, 검증 환경 구성도, 설계 시 값이 어긋나는 사고가 diff에 어떻게 나타나는지 예시도 함께 넣었습니다.
본문의 관련 기사 링크 문구가 링크 대상의 현재 제목과 어긋나 있던 부분을 실제 제목에 맞췄습니다. 본문 내용은 바꾸지 않았습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.21635350)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「WinForms의 고DPI 대응 ── 4K 모니터에서 흐려지거나 레이아웃이 깨지는 원인과 현실적인 대처」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/winforms-high-dpi-guide/

DOI(등록된 아카이브)
10.5281/zenodo.21635350
DOI(마지막 등록 버전)
10.5281/zenodo.21635351

새 노트북으로 바꿨더니 업무 앱의 글자가 번져서 읽기 어렵다, 4K 모니터에 연결했더니 버튼과 레이블이 겹친다, 고객사 125% 환경에서만 장표 미리보기가 깨진다. 최근 몇 년, PC 교체 시기에 이런 문의가 급증하고 있습니다. Full HD를 넘는 고해상도 디스플레이와 125〜200% 표시 배율이 업무용 PC에서도 표준이 되면서, 96 DPI·100% 표시 시절에 만든 WinForms 앱을 그대로 두면 쾌적하게 표시되지 않게 되었기 때문입니다. 오래된 WinForms 앱의 유지보수 상담 가운데, 지금 가장 빈도가 높은 주제라고 해도 좋을 것입니다.

골치 아픈 점은 증상이 「흐리다」「깨진다」「일부만 작다」처럼 제각각으로 보인다는 것입니다. 실제로는 원인을 몇 종류로 정리할 수 있고, 어느 증상이 어느 원인에 대응하는지 알면 고치는 방법도 「어디까지 할지」 판단도 할 수 있습니다. 이 글에서는 Windows의 DPI 스케일링 구조와 DPI 인식 모드를 바탕으로, WinForms 앱(.NET / .NET Framework 모두)의 고DPI 대응을 설정 방법·흔한 함정·단계적 대응 전략까지 실무 관점에서 정리합니다.

이 글에서 쓰는 용어

결론에 앞서, 본문에서 반복해서 나오는 말만 먼저 잡아 둡니다. 각각 본문의 어느 장에서 다루는지도 함께 적었으니, 의미가 안 잡히면 여기로 돌아오세요.

용어 의미 상세
DPI / 표시 배율 1인치당 도트 수. 96 DPI를 100%로 두고, 125% = 120 DPI, 150% = 144 DPI에 대응합니다 2장
DPI 가상화 DPI 대응을 선언하지 않은 앱에 대해 OS가 「화면은 96 DPI」라고 속여 그리게 한 뒤, 그 결과를 비트맵으로 확대해 표시하는 보완 조치. 레이아웃은 깨지지 않는 대신 전체가 번집니다1 2장
DPI 인식 모드 앱이 「자신은 어디까지 DPI를 다룰 수 있는지」를 OS에 신고하는 구분. Unaware / System Aware / Per-Monitor / Per-Monitor V2의 4가지2 3장
System Aware 「로그인 시점의 주 모니터 DPI에 맞춘다」는 모드. 주 모니터에서는 선명하고, 다른 모니터로 옮기면 OS가 확대해 번집니다 3장
PMv2 Per-Monitor V2의 약칭. 모니터마다 DPI를 따라가는 모드. 타이틀 바 등 비클라이언트 영역은 OS가 자동으로 스케일합니다2 3장
GDI 스케일링 앱은 Unaware인 채로, GDI로 그려진 글자와 도형만 OS가 벡터 수준에서 확대하는 개량형 비트맵 확대. 코드를 바꾸지 않고 글자 번짐만 줄일 수 있습니다2 3장·4.3절
매니페스트 실행 파일에 넣는 XML. DPI 인식 모드를 선언하는 곳 중 하나입니다 4장
AutoScaleMode DPI 인식 모드와는 별개인, WinForms 폼 자신이 가진 자동 스케일링 메커니즘 5장

1. 먼저 결론

  • 고DPI 환경에서 오래된 앱이 흐려지는 것은 버그가 아니라 OS의 보완 조치(DPI 가상화)입니다. DPI 미대응 앱을 Windows가 96 DPI로 그리게 한 뒤 비트맵 확대하므로, 레이아웃은 깨지지 않는 대신 번집니다. 1
  • 반대로 「선명한데 레이아웃이 깨진다」는 DPI 대응을 선언했는데 레이아웃이 따라가지 못하는 상태입니다. 흐려짐과 레이아웃 붕괴는 원인이 정반대이므로, 대처하기 전에 어느 쪽인지 가려 주세요.
  • 대응의 첫걸음은 지금 앱이 어느 DPI 인식 모드(Unaware / System Aware / Per-Monitor V2)로 동작하는지 파악하는 것입니다. 매니페스트·app.config·API 호출 중 무엇으로(또는 아무것도 없이) 선언했는지 확인합니다. 3
  • 설정 방법은 세대에 따라 갈립니다. .NET(Core 3.1〜.NET 8)이라면 프로젝트 파일 또는 Application.SetHighDpiMode, .NET Framework 4.7 이후라면 app.config, 4.6 이하는 매니페스트로 System Aware까지가 현실적인 범위입니다. 45
  • 디자이너는 반드시 100%(96 DPI) 모니터에서 열 것. 150% 환경에서 폼을 열어 저장하면 AutoScaleDimensions가 바뀌어, 팀 전원의 레이아웃이 깨지는 흔한 사고가 됩니다. 6
  • 전면 대응(Per-Monitor V2)은 공수가 듭니다. 「System Aware로 주 모니터만 선명하게」를 1단계, PMv2 완전 대응을 2단계로 두는 2단계 구성으로, 앱의 수명과 이용 환경에 맞는 투자를 하는 편이 현실적입니다.
  • 자체 수정이 어렵거나 기한에 못 맞출 때의 잠정 대책으로, exe 속성 → 호환성에서 이용자 측이 덮어쓸 수 있는 「고DPI 설정」도 알아 두면 문의 대응이 한결 수월해집니다. 2

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 21건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. 왜 흐려지는가 ── DPI 가상화의 구조

Windows의 표시 배율은 96 DPI를 100%로 두는 배율로 표현됩니다. 125% = 120 DPI, 150% = 144 DPI, 200% = 192 DPI입니다. 4K(3840×2160) 27인치 모니터를 100%로 쓰면 글자가 너무 작아서, Windows는 기본값으로 150% 전후를 권장합니다. 즉 고해상도 디스플레이의 보급은 곧 「96 DPI가 아닌 환경의 보급」을 뜻합니다.

문제는 오래된 데스크톱 앱 상당수가 「화면은 언제나 96 DPI」라는 전제로 쓰여 있다는 점입니다. 좌표도 폰트도 아이콘도 픽셀에 직접 적어 두었기 때문에, 150% 환경에서 그대로 그리면 모든 것이 물리적으로 2/3 크기로 보입니다. 그래서 Windows는 DPI 대응을 선언하지 않은 앱에는 「화면은 96 DPI다」라고 거짓말하고, 앱이 96 DPI라고 생각하고 그린 결과를 비트맵으로 확대해 표시합니다. 이것이 DPI 가상화(비트맵 스트레치)입니다. 1

이 구조를 전제로 하면 증상과 원인이 깔끔히 대응합니다. 첫 분류에 이 표를 쓰세요.

증상 원인 상태
전체가 한결같이 번지거나 흐려진다. 레이아웃은 깨지지 않는다 DPI 가상화(OS가 비트맵 확대 중) DPI 미대응(Unaware). 안전하지만 지저분하다
글자는 선명한데 컨트롤이 겹치거나 잘린다 DPI 대응을 선언했지만 레이아웃이 따라가지 않음 어중간한 DPI 대응. 여기부터가 개수 대상
주 모니터에서는 선명하고, 서브 모니터로 옮기면 흐려진다 System Aware(시작 시점의 주 모니터 DPI 고정) 사양대로. PMv2로 갈지 판단 대상
글자 크기는 괜찮은데 아이콘·이미지만 작거나 거칠다 비트맵 자산이 96 DPI용인 채 이미지 자산 미대응(6장)
특정 화면·컨트롤만 깨진다 고정 좌표 레이아웃, 자체 그리기, 서드파티 컨트롤 개별 개수 대상(6장)

중요한 것은 「흐려 보이는」 앱은 오히려 망가지지 않았다는 점입니다. OS의 보완 조치가 제대로 작동하고 있습니다. 수정 작업이란 이 보완 조치를 끊고 「스케일링은 스스로 한다」고 선언하는 것(=DPI 인식 모드를 올리는 것)이므로, 선언하는 순간 레이아웃 문제가 전부 자신의 책임이 됩니다. 「일단 PMv2로 바꿨더니 더 심해졌다」는 문의는 이 구도로 일어납니다.

2.1 직접 증상을 재현·확인하는 절차

이 글에는 화면 캡처를 싣지 않았습니다. 「흐리다」「깨진다」의 차이는 정지 화면으로 보여 주기보다, 자기 PC에서 전환해 비교하는 편이 확실히 알 수 있습니다. 다음 절차로 위 표의 1행과 2행을 직접 가려 주세요. 모니터 1장이라도 여기까지는 확인할 수 있습니다.

  1. 확대율을 100%와 150%로 전환한다. 시작 > 설정 > 시스템 > 디스플레이 >「확대/축소 및 레이아웃」>「텍스트, 앱 및 기타 항목의 크기 변경」에서 고릅니다. 7 차이가 가장 크게 나는 비교는 100%와 150%입니다.
  2. 반드시 앱을 다시 시작한다. Unaware도 System Aware도, 실행 중인 앱은 새 확대율을 따라가지 않습니다. 변경 후에는 매번 다시 시작합니다. System Aware인 경우, 주 모니터를 바꿨을 때만 로그아웃·로그인도 필요합니다(System DPI는 로그인 시점에 고정되기 때문입니다).
  3. 돋보기로 세부를 비교한다. Windows 로고 키 + +(플러스)로 돋보기가 시작됩니다. 8 400% 정도까지 올리고, 다음 중 어디에 해당하는지 판정합니다.
    • 글자 윤곽이 한결같이 흐려지고, 선이 중간색으로 번진다 ── DPI 가상화(표의 1행)입니다. 글자도 눈금선도 아이콘도 똑같이 뭉개진다는 점이 결정적이고, 앱 측 문제가 아닙니다.
    • 글자는 선명한데 버튼과 레이블이 겹치거나 끝이 잘린다 ── DPI 대응을 이미 선언했는데 레이아웃이 따라가지 못하는 상태(표의 2행)입니다. 여기부터가 6장의 개수 대상입니다.
  4. 아이콘만 본다. 글자는 선명한데 ToolStrip 아이콘만 콩알만 하고 거칠다면, 표의 4행(96 DPI용 비트맵을 그대로 붙임)입니다.
  5. 모니터 사이를 이동시킨다(2장 이상인 경우). 확대율이 다른 모니터로 창을 드래그해, 옮긴 곳에서만 번진다면 System Aware(표의 3행)이고, 사양대로의 동작입니다.

덧붙여, 지금 앱이 어느 모드로 도는지 위 보이는 방식에서 거의 거꾸로 추정할 수 있습니다. 100%와 150% 모두에서 선명하고, 모니터 사이를 옮겨도 선명한 채라면 PMv2 상당, 주 모니터만 선명하면 System Aware, 어느 환경에서든 한결같이 번지면 Unaware입니다. 선언 위치(매니페스트·app.config·API 호출) 확인은 4장에서 합니다.

3. DPI 인식 모드 정리

앱은 프로세스(정확히는 Windows 10 이후는 최상위 창) 단위로, 자신이 어디까지 DPI를 다룰 수 있는지를 OS에 선언합니다. 모드는 실질 4가지입니다. 12

모드 도입 OS 앱에서 보이는 DPI 모니터 간 이동·DPI 변경 시 WinForms에서의 현실적 위치
Unaware ── 항상 96 OS가 비트맵 확대(흐려짐) 아무것도 선언하지 않을 때의 기본값. 흐려지지만 깨지지 않음
System Aware Vista 로그인 시점의 주 모니터 DPI 고정 주 모니터 이외·변경 후에는 OS가 확대(흐려짐) 전 세대에서 사용 가능. 현실적 타협의 정답
Per-Monitor (V1) 8.1 창이 있는 모니터의 DPI 최상위에 알림만. 스케일링은 전부 자체 프레임워크 지원이 없어 실용성이 떨어짐. 고르지 않음
Per-Monitor V2 10 (1703) 창이 있는 모니터의 DPI 자식 창까지 알림. 비클라이언트 영역은 OS가 따라감 완전 대응의 정답. .NET Framework 4.7+ / .NET에서 이용 가능

Per-Monitor V1과 V2의 차는 실무에서 결정적입니다. V1은 「알림은 오지만 나머지는 전부 스스로」라는 순수 Win32용 구조라 WinForms에서 쓸 이유가 없습니다. V2는 타이틀 바·스크롤바·메뉴 등 비클라이언트 영역은 OS가 자동으로 스케일하고, WinForms 측 프레임워크 지원(후술)도 V2 전제로 만들어져 있습니다. Per-Monitor를 고른다면 V2 한 가지입니다. 2

하나 더, GDI 스케일링(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, Windows 10 1809〜)이라는 모드가 있습니다. 앱으로서는 Unaware인 채로, GDI로 그려지는 텍스트나 도형만 OS가 벡터 수준에서 확대해 주는 개량형 비트맵 확대입니다. 2 코드를 전혀 바꾸지 않고 글자 번짐만 줄일 가능성이 있으므로, 개수할 수 없는 앱의 잠정 대책으로 알아 둘 가치가 있습니다(4.3절).

덧붙여 Windows 10 1607 이후는 최상위 창 단위로 다른 모드를 섞는 Mixed-Mode도 있어, 「메인 화면만 PMv2, 고치기 어려운 대화상자는 Unaware인 채로 OS에 확대시킨다」는 단계적 전환이 Win32 수준에서는 지원됩니다. 9 WinForms에서 손쉽게 쓸 수 있는 것은 아니지만, 「전체 화면을 한 번에 고치지 않아도 된다」는 설계 사상은 7장의 단계 전략으로 이어집니다.

4. 설정 방법 ── 세대별 정답

지금 앱이 어느 세대인지에 따라 DPI 인식 모드 선언 방법이 다릅니다. 여기를 착각하면 「설정했는데 안 먹힌다」로 시간을 허비하므로, 세대마다 나눠 적습니다.

4.1 .NET (Core 3.1〜.NET 8)의 WinForms

.NET에서는 Application.SetHighDpiMode가 준비되어 있고, 템플릿이 생성하는 ApplicationConfiguration.Initialize()(.NET 6 이후)가 이를 호출합니다. 기본값은 SystemAware입니다. 4 설정은 프로젝트 파일에 쓰는 것이 권장이며, Visual Studio 디자이너도 이 설정을 참조합니다.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <!-- SystemAware(기본값) / PerMonitorV2 / DpiUnaware / DpiUnawareGdiScaled -->
    <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
  </PropertyGroup>
</Project>

주의할 점으로, 프로젝트 파일의 ApplicationHighDpiMode와 ApplicationConfiguration.Initialize()는 .NET 6에서 들어온 구조입니다. 4 .NET Core 3.1 / .NET 5 프로젝트에서는 이 속성을 적어도 효과가 없으므로, 다음과 같이 Application.SetHighDpiMode를 코드에서 직접 호출합니다(ApplicationConfiguration.Initialize()를 쓰지 않는 오래된 스타일의 Main에서도 같습니다). 어느 쪽이든 창을 하나라도 만들기 전에 호출해야 합니다.

[STAThread]
static void Main()
{
    Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

.NET 6 이후는 PMv2일 때 컨테이너 컨트롤이나 MDI 자식 창의 스케일링이 개선되어, 「200% 모니터에서 100% 모니터로 옮기면 컨트롤이 어긋난다」 같은 .NET 5까지의 문제가 상당히 해소되었습니다. 4 PMv2로 본격 대응한다면 .NET Framework보다 새 .NET 쪽이 분명히 수월하다는 것이 실제 느낌입니다. .NET Framework에서의 이행을 검토할 재료는 「.NET Framework에서 .NET으로의 마이그레이션 전 체크리스트」에 정리해 두었습니다.

4.2 .NET Framework 4.7 이후

.NET Framework는 4.7에서 고DPI 대응이 크게 강화되었습니다. 컨트롤 스케일링 개선, DPI 변경 이벤트(DpiChanged 계열), DeviceDpi 속성 등이 들어갑니다. 다만 opt-in이며, 다음 2가지를 세트로 설정해야 비로소 유효해집니다. 5

먼저 매니페스트에서 Windows 10 호환을 선언합니다(이것이 없으면 4.7의 고DPI 기능 자체가 켜지지 않습니다).

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- Windows 10 호환 선언 -->
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  </application>
</compatibility>

다음으로 app.config의 System.Windows.Forms.ApplicationConfigurationSection에서 DPI 인식 모드를 선언합니다.

<configuration>
  <System.Windows.Forms.ApplicationConfigurationSection>
    <add key="DpiAwareness" value="PerMonitorV2" />
    <!-- 이미 자체 스케일링한 화면이 있으면 개별 기능을 끌 수 있습니다 -->
    <!-- <add key="EnableWindowsFormsHighDpiAutoResizing" value="false" /> -->
  </System.Windows.Forms.ApplicationConfigurationSection>
</configuration>

아울러 Main 맨 앞에서 Application.EnableVisualStyles()를 호출하고 있는지도 확인하세요. 여기서 주의가 하나 있습니다. 예전부터 쓰던 매니페스트의 <dpiAware> / <dpiAwareness> 기재는 app.config 설정을 덮어쓰므로, 4.7의 WinForms에서는 병용하지 않는 것이 공식 권장입니다. 5 과거에 System Aware화를 위해 매니페스트에 <dpiAware>true</dpiAware>를 적어 둔 앱을 4.7+PMv2로 올릴 때는, 매니페스트 측 선언을 정리하는 것부터 시작합니다. 「app.config에 적었는데 안 먹힌다」의 원인은 대개 이것입니다.

4.3 .NET Framework 4.6 이하·VB6/MFC 혼재

4.6 이하 WinForms에는 PMv2를 지원하는 프레임워크 코드가 없어, 무리하게 선언해도 레이아웃 붕괴를 전부 스스로 처리해야 합니다. 이 세대의 현실적인 해법은 매니페스트로 System Aware를 선언하는 선까지입니다. OS 수준 선언은 매니페스트가 권장 수단이고, <dpiAware>(Vista〜)와 <dpiAwareness>(Windows 10 1607〜)를 함께 적으면 구버전·신버전 OS 모두에서 의도대로 해석됩니다. 3

<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
  <asmv3:windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">system</dpiAwareness>
  </asmv3:windowsSettings>
</asmv3:application>

System Aware로 두면 시작 시점의 주 모니터 DPI에 맞춰 WinForms의 자동 스케일링(5장)이 한 번만 작동하고, 주 모니터에서는 선명하게 표시됩니다. AutoScaleMode.Font가 올바르게 설정된 폼이라면, 이 선언만으로도 꽤 볼 만한 상태가 되는 경우가 많습니다. 참고로 SetProcessDpiAwareness 같은 API로 실행 중에 선언하는 방법도 있지만, 창을 만든 뒤에는 바꿀 수 없고, 매니페스트 선언이 공식적으로 권장됩니다. 10 VB6나 MFC 코드가 섞인 앱에서도 생각은 같고, exe 매니페스트로 프로세스 전체 모드가 정해집니다(MFC 자체에는 자동 스케일링 지원이 없으므로, System Aware 선언 후의 화면 점검은 필수입니다. 이 세대 자산의 취급은 「Windows의 MFC란」도 참조하세요).

개수 자체를 할 수 없을 때의 이용자 측 잠정 대책도 소개합니다. exe 속성에서 그 앱의 DPI 스케일링을 이용자 측이 덮어쓸 수 있습니다. 2 전화로 그대로 읽어 줄 수 있도록, 화면 항목 이름만 순서대로 늘어놓습니다.

  1. 대상 exe 파일을 마우스 오른쪽 단추로 클릭하고 「속성」을 고릅니다. 바로 가기만 있는 경우에는 마우스 오른쪽 단추의 「파일 위치 열기」로 실체가 있는 폴더로 이동하게 합니다.
  2. 「호환성」 탭을 엽니다.
  3. 「고DPI 설정 변경」 단추를 누릅니다.
  4. 열린 대화상자의 「고DPI 배율 설정 재정의」 확인란을 켭니다.
  5. 그 아래 「배율 조정 수행 주체」 드롭다운에서 동작을 고릅니다. 「시스템」으로 하면 DPI 가상화를 강제하고(=깨지지 않지만 번짐), 「시스템(향상됨)」으로 하면 3장에서 다룬 GDI 스케일링이 적용되어 GDI 그리기 텍스트만 선명해집니다. 2
  6. 「확인」으로 대화상자 두 개를 닫고, 앱을 다시 시작해 보이는 모습을 확인합니다.

「시스템」과 「시스템(향상됨)」 중 어느 쪽이 나을지는 앱에 따라 다르므로, 2.1절 절차로 비교해 고르게 하는 것이 확실합니다. 만능은 아니지만, 「고객사에서 오늘 어떻게든 하고 싶다」는 때 전화로 안내할 수단으로 지원 절차서에 넣어 둘 가치가 있습니다. 바로 가기 속성에는 이 항목이 없으므로, 실체 exe를 열게 한다는 한 가지만은 안내할 때 빠뜨리지 마세요.

5. AutoScaleMode와 디자이너의 함정

WinForms에는 DPI 인식 모드와 별도로, 폼 자신의 자동 스케일링 메커니즘이 있습니다. 여기를 이해하지 못하면 System Aware로 바꿔도 올바르게 스케일하지 않습니다.

구조는 이렇습니다. 디자인 시 각 폼(ContainerControl)은 스케일링 기준을 AutoScaleMode에, 디자인한 환경의 기준값을 AutoScaleDimensions에 기록합니다. 실행 시에는 현재 환경 값(CurrentAutoScaleDimensions)과 비교해, 차가 있으면 자식 컨트롤을 한꺼번에 확대·축소합니다. 11 즉 「디자인 시와 실행 시의 환경 차」를 흡수하는 구조이며, 기준값은 코드(Designer.cs)에 기록됩니다.

AutoScaleMode의 선택지는 실질 2가지입니다.

AutoScaleMode 기준 특징
Font(권장·기본값) 폼 폰트의 치수 DPI가 오르면 시스템 폰트의 실측도 바뀌므로 DPI를 따라가는 역할도 겸합니다. 사용자 폰트 설정 변경에도 따라갑니다
Dpi 화면 DPI DPI에만 비례. 그래픽 중심 화면용
None ── 자동 스케일링 무효. 96 DPI에 값을 그대로 적어 둔 앱은 이렇게 되어 있는 경우가 많습니다

고민되면 Font입니다. 주의할 점으로, 기본 폼과 파생 폼에서 서로 다른 모드를 섞으면 예상 밖 결과가 난다는 점이 공식에도 명시되어 있습니다. 11 폼 상속을 쓰는 앱에서는 모든 폼의 모드를 맞추는 점검부터 해야 합니다.

그리고 본론의 함정입니다. AutoScaleDimensions는 「디자인한 환경의 값」을 기록하는 구조이므로, 150% 모니터에서 Visual Studio 디자이너를 열어 폼을 저장하면 Designer.cs의 AutoScaleDimensions가 150% 값(Font 모드라면 6F, 12F가 9F, 18F 등)으로 바뀝니다. 좌표·크기도 1.5배 값으로 저장됩니다. 이 폼을 100% 환경의 멤버가 빌드·실행하면 전체가 줄어들어 표시되고, diff 리뷰에서는 모든 컨트롤 좌표가 바뀐 거대한 diff가 나옵니다. 「고DPI 노트북을 지급받은 신규 멤버가 폼 한 장을 만졌더니 리포지토리 레이아웃이 깨졌다」는, 고DPI 상담에서 흔히 보는 사고입니다.

이 사고는 커밋 전의 git diff에 반드시 나타납니다. 폼을 한 장 열어 저장했을 뿐인데 Designer.cs에 다음과 같은 diff가 나와 있다면, 그것이 150% 환경에서 연 증거입니다.

-            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);

이 한 줄에 이어 ClientSize와 모든 컨트롤의 Location / Size가 줄줄이 1.5배 값으로 바뀐 diff가 이어집니다. 리뷰에서 봐야 할 것은 이 AutoScaleDimensions 한 줄뿐이고, 여기가 바뀌어 있으면 이후 좌표 diff는 읽지 않고 되돌려도 됩니다.

대책은 원칙적으로 하나, 디자이너는 100%(96 DPI)에서 연다는 것입니다. Visual Studio 자체는 DPI 대응 앱이지만, WinForms 디자이너(.NET Framework용)는 DPI 미대응이라 고DPI 모니터에서 열면 노란색 정보 표시줄이 나와 「100% 스케일링으로 Visual Studio를 다시 시작하라」고 재촉합니다. 6 화면 캡처는 싣지 않지만, 흐름은 다음과 같습니다.

  1. 고DPI 모니터의 Visual Studio에서 .NET Framework 프로젝트의 폼을 디자이너로 엽니다.
  2. 디자이너 위쪽에 노란색 정보 표시줄이 나와 100% 스케일링으로의 다시 시작을 재촉합니다. 이 상태에서 폼을 저장하지 마세요. 위의 diff가 발생합니다.
  3. 정보 표시줄 링크에서 Visual Studio를 다시 시작합니다. VS 전체가 DPI 미대응 모드로 뜨므로 VS 자신의 표시는 조금 흐려지지만, 이것이 정상적인 상태입니다.
  4. 그 상태에서 폼을 편집·저장하고, git diff로 AutoScaleDimensions가 바뀌지 않았는지 확인합니다.
  5. 작업이 끝나면 Visual Studio를 평소처럼 다시 시작해 DPI 미대응 모드를 해제합니다.

명령줄에서 바로 이 상태로 시작하려면 devenv /noScale입니다. 참고로 .NET 6 이후 프로젝트에서는 Visual Studio 2022 17.8 이후 프로젝트 파일에 <ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware>를 설정하면, VS 전체를 다시 시작하지 않고 디자이너 탭만 DPI 미대응으로 돌릴 수 있습니다(.NET Framework 프로젝트에서는 쓸 수 없습니다). 6 대상이 .NET 6 이후라면 3〜5의 운용 자체가 필요 없어지므로, 이쪽을 먼저 검토하세요.

점검의 체계화로는 「Designer.cs의 AutoScaleDimensions가 기본값이 아닌 값으로 바뀐 diff를 CI나 pre-commit에서 걸러 낸다」가 손쉽고 효과가 있습니다. 사고는 난 뒤에 고치는 것보다 커밋 입구에서 막는 편이 비용이 적습니다.

6. 레이아웃이 깨지는 전형 패턴과 고치는 방법

DPI 인식 모드를 올렸을 때(또는 올리려 할 때) 깨지는 곳은 대개 다음 패턴에 들어갑니다.

깨지는 것 원인 고치는 방법
컨트롤의 겹침·잘림 고정 좌표·고정 크기 레이아웃 Anchor/Dock, TableLayoutPanel / FlowLayoutPanel로 교체. AutoSize 활용
아이콘·이미지가 작거나 거칠다 96 DPI용 비트맵을 그대로 붙임 여러 해상도 이미지를 마련하고 DPI로 전환. 가능하면 큰 한 장에서 축소
자체 그리기(그래프, 도면, 장표 미리보기) Graphics에 픽셀 값을 그대로 기입 DeviceDpi 기준으로 좌표·선 두께·폰트를 스케일
DataGridView 행이 빽빽하다 RowHeight 등의 픽셀 지정 AutoSizeRowsMode 이용, 또는 DPI로 스케일한 값을 설정
ToolStrip 아이콘이 콩알만 함 16×16 고정 ImageScalingSize를 DPI에 따라 설정
특정 서드파티/ActiveX 컨트롤만 깨진다 컨트롤 자체가 DPI 미대응 벤더의 대응 버전으로 갱신. 없으면 그곳이 대응 수준의 상한

레이아웃 교체가 공수의 중심입니다. 거꾸로 말하면, 원래 TableLayoutPanel과 Dock으로 짠 화면은 DPI 인식 모드를 올려도 손이 거의 가지 않습니다. 이후 신규 화면은 처음부터 레이아웃 패널로 짠다는 규약만 두어도 장래 부채가 줄어듭니다.

자체 그리기의 스케일링은 Control.DeviceDpi(.NET Framework 4.7+ / .NET)를 기준으로 96 DPI 환산 값을 변환하는 것이 기본형입니다.

public partial class ChartPanel : Panel
{
    // 96 DPI 기준의 설계값을 현재 DPI로 변환
    private int Scale(int value96) => value96 * DeviceDpi / 96;

    protected override void OnPaint(PaintEventArgs e)
    {
        using var pen = new Pen(Color.Navy, Scale(2));
        e.Graphics.DrawRectangle(pen,
            Scale(16), Scale(16), Scale(320), Scale(120));
    }

    // PMv2에서는 모니터 간 이동으로 DPI가 바뀌므로 다시 그리기를 유도
    protected override void OnDpiChangedAfterParent(EventArgs e)
    {
        base.OnDpiChangedAfterParent(e);
        Invalidate();
    }
}

System Aware까지라면 DPI는 시작 시점에 고정되므로 Scale 도입만으로 끝나지만, PMv2에서는 모니터 간 이동마다 DeviceDpi가 바뀌므로, 캐시해 둔 폰트·이미지·레이아웃 값을 DpiChanged 계열 이벤트에서 다시 만드는 설계가 필요합니다. 5 「자체 그리기 화면이 몇 개 있는지」는 PMv2 대응 공수 견적에 그대로 영향을 줍니다.

서드파티 컨트롤과 ActiveX/OCX는 이 개수의 상한을 정하는 요소입니다. 컨트롤 자체가 DPI 미대응이면 호스트 측에서 아무리 해도 그 화면은 완전해지지 않습니다. 벤더 대응 상황을 확인하고, 갱신을 기대할 수 없는 ActiveX는 「ActiveX/OCX를 유지할지, 랩할지, 교체할지」의 판단표로 취급을 정한 뒤 고DPI 대응의 목표를 설정하세요.

7. 단계적 대응 전략 ── 어디까지 할지 판단표

여기까지의 내용을 전제로 하면 대응 수준은 3단계로 정리할 수 있습니다. 모든 앱을 PMv2로 만드는 것이 기술적 이상이지만, 수정 예산과 앱 수명을 생각하면 타협이 정답이 되는 장면이 많습니다.

  (1) 아무것도 하지 않음(Unaware) (2) System Aware화 (3) PMv2 완전 대응
보이는 모습 모든 환경에서 흐려짐(깨지지 않음) 주 모니터에서 선명. 서브 모니터·DPI 변경 시에는 흐려짐 모든 모니터에서 선명
주요 작업 없음 매니페스트/설정 선언+AutoScaleMode 점검+전체 화면 표시 확인 (2)+레이아웃 전면 점검+자체 그리기·이미지 자산의 DPI 대응+DpiChanged 대응
공수 체감 제로 소〜중(화면 수에 비례한 확인 작업이 주) 대(깨진 곳을 고치는 일이 주. 서드파티 컨트롤이 상한을 쥐고 있음)
맞는 경우 몇 년 안에 폐지 예정. 이용자가 허용 중 고정 데스크톱·단일 모니터 중심의 사내 앱. 1단계로서 노트북+외부 모니터 혼용. 장수명의 주력 앱. 고객에게 배포하는 제품

판단 축은 세 가지입니다. 앱의 수명(앞으로 몇 년 쓸지), 이용 환경(전원이 같은 고정 데스크톱이면 System Aware로 실질 완결됩니다. 노트북+외부 모니터 조합이 많으면 모니터 간 이동마다 번지는 System Aware는 불만이 남습니다), 그리고 수정 예산. 권하는 진행은 먼저 (2)를 모든 앱의 표준으로 실시하고, 그 과정에서 발견한 깨진 양의 규모와 서드파티 컨트롤 제약을 재료로 (3)으로 갈지·어느 화면만 갈지를 정하는 것입니다. (2)는 「선언+점검」이 중심이라 실패 위험이 작고, 그러면서도 이용자 체감은 크게 좋아집니다.

테스트에 대해서도 짚어둡니다. 고DPI 문제는 개발 기기에서 재현되지 않는 경우가 많아서, 다음 환경을 의도적으로 만들어 확인합니다. 1

  • 서로 다른 배율의 모니터 2장을 연결하고(예: 150% 노트북 내장+100% 외부) 창을 오가게 한다
  • 주 모니터를 바꾼 뒤 다시 로그인한 다음 시작한다(System DPI는 로그인 시점에 고정되므로, 다시 로그인하지 않으면 바뀌지 않습니다)
  • 앱 실행 중에 배율 설정을 변경한다
  • 원격 데스크톱으로 고DPI 클라이언트에서 접속한다(RDP는 클라이언트 측 DPI가 들어오므로, 「서버 콘솔에서는 정상인데 RDP면 깨진다」는 흔한 보고입니다)

구성을 그림으로 그리면 다음과 같습니다. 실기 1대+모니터 2장이 토대이고, 거기서 부족한 배율을 가상 머신으로 보태고, RDP 경유 확인을 마지막에 겹치는 쌓는 방식입니다.

가상 머신 ── 부족한 배율을 보탠다실기 1대 ── 모니터 2장이 토대창을 드래그해 왕복시킨다125%200%내장 디스플레이 150%주 모니터로 쓰는 쪽외부 모니터 100%검증 대상 WinForms 앱원격 데스크톱고DPI 클라이언트에서 접속

그림의 좌우 왕복 화살표가 System Aware와 PMv2의 차가 가장 뚜렷이 나는 조작입니다. 주 모니터 교체는 PHYS 안에서 NB와 EXT의 역할을 바꾸고, 그때마다 다시 로그인해 확인합니다.

「125%에서만 깨진다」는 보고가 있으면, 그 배율을 실제로 만들어 보는 것이 결국 가장 빠르다는 것이 경험칙입니다. 가상 머신에서도 배율 설정은 바꿀 수 있으므로, 100 / 125 / 150 / 200% 검증 환경을 마련해 두면 원인 분리가 빨라집니다.

8. 정리

고DPI 환경에서 「흐려진다」는 OS의 보완 조치(DPI 가상화), 「깨진다」는 어중간한 DPI 대응──이 대응 관계만 잡으면 증상에서 원인과 대응책을 거꾸로 찾을 수 있습니다. 설정 방법은 세대별로, .NET이라면 프로젝트 파일의 ApplicationHighDpiMode, .NET Framework 4.7 이후라면 app.config(매니페스트의 dpiAware와 병용하지 않음), 4.6 이하는 매니페스트로 System Aware까지. 아울러 AutoScaleMode.Font의 통일과 디자이너는 100%에서 연다는 운용 규약이, 소소해 보이지만 사고를 가장 줄입니다.

그 위에서 어디까지 할지는 7장의 3단계에서. 먼저 System Aware화로 주 모니터를 선명하게 하고, 이용 환경과 앱 수명이 맞으면 PMv2로──라는 2단계 구성이, 당사가 실제 수정 건에서 권하는 진행입니다. UI 프레임워크 자체를 다시 볼 시점(WPF는 처음부터 DPI에 강한 설계입니다)이라면 「WinForms / WPF / WinUI를 어떻게 고를지」도 함께 검토 재료로 삼으세요. 지금 다루는 앱을 어느 단계까지 고칠 수 있는지 판단이 어렵다면, 화면 구성과 컨트롤 구성 점검부터 도와드릴 수 있습니다.

관련 기사

관련 상담 영역

合同会社小村ソフト에서는 오래된 WinForms / MFC 앱의 고DPI 대응(현황 조사, 대응 수준 판단, 레이아웃 개수)이나 PC 교체에 따른 표시 불량 원인 조사, UI 쇄신 상담을 다루고 있습니다.

참고 링크

  1. Microsoft Learn, High DPI Desktop Application Development on Windows. DPI 스케일링의 배경, 각 DPI 인식 모드의 동작, DPI 미대응 앱이 비트맵 확대되는 구조, 혼재 DPI 환경의 테스트 관점에 대해. ↩ ↩2 ↩3 ↩4 ↩5

  2. Microsoft Learn, DPI_AWARENESS_CONTEXT handle. Unaware / System Aware / Per-Monitor / Per-Monitor V2 / UNAWARE_GDISCALED(GDI 스케일링, Windows 10 1809〜) 각 컨텍스트의 정의와 동작에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  3. Microsoft Learn, Setting the default DPI awareness for a process. 매니페스트의 dpiAware / dpiAwareness 요소에 의한 선언 방법, 둘의 우선 관계, API에 의한 선언이 권장되지 않는다는 점에 대해. ↩ ↩2

  4. Microsoft Learn, What’s new in Windows Forms .NET 6. ApplicationConfiguration.Initialize에 의한 부트스트랩, 프로젝트 파일의 ApplicationHighDpiMode 설정(기본값 SystemAware), .NET 6에서의 PerMonitorV2 스케일링 개선에 대해. ↩ ↩2 ↩3 ↩4

  5. Microsoft Learn, High DPI support - Windows Forms. .NET Framework 4.7의 고DPI 강화 내용, app.config의 System.Windows.Forms.ApplicationConfigurationSection(DpiAwareness=PerMonitorV2), 매니페스트 선언이 app.config를 덮어쓰므로 비권장이라는 점, DpiChanged 계열 이벤트와 DeviceDpi에 대해. ↩ ↩2 ↩3 ↩4

  6. Microsoft Learn, Fix DPI display issues in Windows Form Designer. WinForms 디자이너가 DPI 미대응이라는 점, 고DPI 모니터에서 표시되는 정보 표시줄에서의 100% 스케일링 다시 시작, devenv /noScale, .NET 6+용 ForceDesignerDPIUnaware에 대해. ↩ ↩2 ↩3

  7. Microsoft 지원, Windows에서 화면 해상도 및 레이아웃 변경. 「시작 > 설정 > 시스템 > 디스플레이」의 「확대/축소 및 레이아웃」에서 「텍스트, 앱 및 기타 항목의 크기 변경」으로 확대율을 고르는 절차에 대해. ↩

  8. Microsoft 지원, 돋보기를 사용하여 화면의 항목을 더 잘 보기. Windows 로고 키 + 플러스 기호로 돋보기를 시작해 화면 일부를 확대해 표시할 수 있다는 점에 대해. ↩

  9. Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. SetThreadDpiAwarenessContext에 의한 최상위 창 단위 DPI 인식 모드 혼재와, GetDpiForWindow 등 DPI 대응 API에 대해. ↩

  10. Microsoft Learn, SetProcessDpiAwareness function. API에 의한 프로세스 기본 DPI 인식 설정과, 매니페스트 선언이 권장된다는 점, 한 번 설정하면 바꿀 수 없다는 점에 대해. ↩

  11. Microsoft Learn, Automatic form scaling - Windows Forms. AutoScaleMode / AutoScaleDimensions / CurrentAutoScaleDimensions에 의한 자동 스케일링 동작, Font와 Dpi 모드 혼재가 미지원이라는 점에 대해. ↩ ↩2

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

4K 모니터에서 WinForms 앱이 흐려지는 이유는 무엇인가요?
버그가 아니라 OS의 보완 조치(DPI 가상화)입니다. DPI 대응을 선언하지 않은 앱에 대해 Windows는 「화면은 96 DPI다」라고 속이고, 앱이 96 DPI라고 생각하고 그린 결과를 비트맵으로 확대해 표시합니다. 그래서 레이아웃은 깨지지 않는 대신 번집니다. 반대로 「선명한데 레이아웃이 깨진다」는 DPI 대응을 선언했는데 레이아웃이 따라가지 못하는 상태로, 원인이 정반대입니다. 대처하기 전에 어느 쪽 증상인지 가려내는 것이 첫걸음입니다.
WinForms의 고DPI 대응은 어떻게 설정하나요?
세대에 따라 방법이 갈립니다. .NET 6 이후라면 프로젝트 파일의 ApplicationHighDpiMode 속성(기본값은 SystemAware), .NET Core 3.1/.NET 5라면 Application.SetHighDpiMode를 창을 만들기 전에 호출합니다. .NET Framework 4.7 이후는 매니페스트에서 Windows 10 호환을 선언한 뒤 app.config의 DpiAwareness 설정을 쓰지만, 매니페스트의 dpiAware 기재는 app.config를 덮어쓰므로 병용하지 않는 것이 공식 권장입니다. 4.6 이하는 매니페스트로 System Aware를 선언하는 선까지가 현실적인 범위입니다.
System Aware와 Per-Monitor V2 중 어느 쪽을 고르는 것이 좋나요?
2단계 구성을 권합니다. 1단계는 System Aware화로, 시작 시점의 주 모니터 DPI에 맞춰 스케일링되며 주 모니터에서는 선명하게 표시됩니다. 선언과 점검이 중심이라 실패 위험이 작고, 고정 데스크톱 중심의 사내 앱이라면 실질적으로 여기서 끝내는 경우가 많습니다. Per-Monitor V2는 모니터 간 이동에서도 화면 전체가 선명해지지만, 레이아웃 전면 점검, 자체 그리기·이미지 자산의 DPI 대응, DpiChanged 이벤트 대응이 필요해 공수가 크고, 서드파티 컨트롤이 미대응이면 그곳이 상한이 됩니다. 앱의 수명·이용 환경·수정 예산으로 판단합니다.
디자이너에서 폼을 저장했더니 레이아웃이 깨진 이유는 무엇인가요?
150% 같은 고DPI 모니터에서 Visual Studio의 WinForms 디자이너를 열어 폼을 저장하면 Designer.cs의 AutoScaleDimensions가 150% 값으로 바뀌고, 좌표·크기도 1.5배 값으로 저장되기 때문입니다. 이를 100% 환경의 멤버가 빌드하면 전체가 줄어들어 표시됩니다. 대책은 디자이너를 100%(96 DPI)에서 여는 것이고, 고DPI 모니터에서는 노란색 정보 표시줄에서 100% 스케일링으로 다시 시작할 수 있습니다. .NET 6 이후 프로젝트에서는 ForceDesignerDPIUnaware 설정도 쓸 수 있습니다. AutoScaleDimensions의 예상 밖 변경을 CI나 pre-commit에서 걸러 내는 절차도 유효합니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기