Windows Forms 애플리케이션의 High DPI 지원 구현

High DPI 지원을 추가한 YouTubeBrowserApp / https://github.com/rkttu/YouTubeBrowserApp

개인적인 필요에 의하여, 그리고 macOS의 DeskApp for YouTube에 영감을 받아서 만든 YouTubeBrowserApp을 개선하면서 새롭게 알게 된 내용을 아티클로 정리해보았습니다.

짤막하게 광고를 하나 하면, YouTubeBrowserApp은 웹 브라우저 세션들과 겹치지 않는 독립적인 창에 YouTube 웹 사이트를 띄워주는 매우 단순한 프로그램이지만, YouTube 플레이어의 자체 반복 재생 기능이나 풍부한 여러 기능 덕에 유용하게 비디오나 음악을 시청할 수 있게 해줍니다. 많이 애용해 주시면 감사하겠습니다. :-)

기존의 Windows Forms 기반 애플리케이션에서 사용하는 여러 기능에 High DPI 지원을 추가하면서 겪을 수 있는 문제들의 개선에 도움을 줄 수 있는 내용들을 정리해보았습니다.

Internet Explorer Feature Control

.NET Framework 4.7.x까지 포함된 Windows Forms의 웹 브라우저 컨트롤은 Internet Explorer (Trident) 엔진을 기반으로 웹 페이지를 렌더링합니다. 최신 웹 기술을 사용할 수 없는 제약 사항이 있음에도 다른 적절한 선택지를 찾을 수 없어 부득이하게 여기서 머무를 수 밖에 없는 안타까운 문제가 있습니다.

그래서 시스템에 설치된 실제 Internet Explorer와 근접한 기능을 제공하기 위하여 Internet Explorer Feature Control이라는 특수 기능을 이용하는 경우가 많습니다. 그런데 이 기능은 레지스트리 키를 추가해야 하는 방식이라 권한 상승을 적용해야만 사용할 수 있는 것으로 인지되는 경우가 많습니다. 저도 최근까지는 그렇게 알고 있었습니다.

하지만 다행히 HKLM 영역이 아닌 HKCU 영역에 Feature Control 관련 레지스트리 키를 추가해도 제대로 처리가 된다는 것을 알게 되었습니다.

그리고 흔히 SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION 키에 값을 추가하여 Internet Explorer 11의 렌더링 방식을 따르도록 수정을 하곤 합니다. FEATURE_BROWSER_EMULATION 키 외에, 여러 가지 세부적인 설정을 조절할 수 있도록 많은 옵션을 제공하고 있는데, High DPI에 관련된 설정은 FEATURE_96DPI_PIXEL 키를 사용하는 것입니다.

참고로 키의 이름만 다를 뿐 레지스트리 항목의 형태는 동일하기 때문에, 아래와 같은 도우미 메서드를 하나 정의하면 쉽게 설정을 변경할 수 있습니다.

private static void SetWebViewFeature(string featureName, object value) {
var targetPath = $@"SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\{featureName}";
var execFileName = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName);
var subKey = Registry.CurrentUser.OpenSubKey(targetPath, true);
  if (subKey == null) subKey = Registry.CurrentUser.CreateSubKey(targetPath);
  using (subKey) {
subKey.SetValue(execFileName, value);
}
}

그리고 Main 메서드에 다음과 같이 도우미 메서드를 호출하면 쉽게 적용됩니다.

// Internet Explorer 11의 렌더링 방식 사용
SetWebViewFeature("FEATURE_BROWSER_EMULATION", 11000);
// High DPI 지원 활성화
SetWebViewFeature("FEATURE_96DPI_PIXEL", 1);

그 외에 사용 가능한 Feature Control 레지스트리 키들의 목록은 아래 웹 페이지를 참고합니다. (2018.10 현재 docs.microsoft.com으로 마이그레이션되지 않은 페이지만 남아있습니다. 추후 링크가 업데이트될 수 있습니다.)

High DPI를 지원한다는 것을 명시하기 위한 다양한 수단

웹 상에서 해당 Windows Forms 애플리케이션이 High DPI를 지원한다는 것을 명시하는 다양한 방법들이 기재되어있습니다. 다음은 제가 찾은 방법들을 정리한 것입니다.

Application.EnableVisualStyles 메서드

예전에 Windows XP (Whistler) 부터 도입되었던 시각적 테마 (흔히 Luna 테마라고 말하는 그것)를 응용프로그램에 추가할 수 있도록 제공된 메서드인데, High DPI 지원에도 관련이 있습니다. 다만 호출 순서에 민감한 측면이 있어서, Main 메서드의 제일 첫 줄에 등장하는 것이 좋다는 조언이 있습니다.

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.OleRequired();

SetProcessDpiAwareness P/Invoke 메서드

.NET Framework의 버전과 관계없이 DPI 지원을 직접 지정할 수 있도록 제공되는 Windows API입니다. 다음과 같이 선언할 수 있습니다.

public enum PROCESS_DPI_AWARENESS : int {
PROCESS_DPI_UNAWARE,
PROCESS_SYSTEM_DPI_AWARE,
PROCESS_PER_MONITOR_DPI_AWARE
}
[DllImport("shcore.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.None, EntryPoint = "SetProcessDpiAwareness")]
[return: MarshalAs(UnmanagedType.U4)]
public static extern int SetProcessDpiAwareness(PROCESS_DPI_AWARENESS value);

그리고 실제로 사용할 때에는 Main 메서드에 다음과 같이 호출을 추가합니다.

SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE);

응용프로그램 매니페스트 설정

지원되는 OS의 버전을 app.manifest 파일 (C# 프로젝트에서 같이 만들 수 있는 템플릿에 응용프로그램 매니페스트라는 것이 있습니다.)에 기재하고 DPI Awareness 설정을 역시 추가할 수 있습니다.

<assembly> 태그 아래의 레벨에 다음의 항목을 추가하여 우선 Windows 10에 대한 지원을 명시합니다. (등장 순서는 무관합니다.)

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- 필요에 따라 다른 OS의 GUID를 열거할 수 있습니다. https://docs.microsoft.com/en-us/windows/desktop/sbscs/application-manifests 문서를 참고하세요. -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /
</application>
</compatibility>

그리고 역시 <assembly> 태그 아래의 레벨에 다음의 항목을 추가하여 DPI Awareness 지원 여부를 명시합니다. (등장 순서는 무관합니다.)

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

Form 자체의 설정

위의 설정들을 추가한 후, 실제로 UI를 표시하는 Form에서는 다음의 설정을 추가로 지정합니다.

  • AutoScaleModeDpi로 변경하고, 작업하는 동안 Visual Studio의 High DPI 지원을 끄는 옵션을 사용합니다. (Visual Studio 2017 15.8 업데이트부터 제공되는 기능입니다.)
  • Windows Forms의 스케일링 옵션으로 키워지는 글꼴은 시스템 글꼴과 차이가 날 뿐더러 보기 좋지 않습니다. 시스템 기본 글꼴을 사용한다면 Load 이벤트 처리기의 첫 줄에 다음의 코드를 넣으면 시각적 차이를 줄일 수 있습니다. (https://www.medo64.com/2014/01/scaling-toolstrip-with-dpi/ 에서 인용했습니다.)
    this.Font = SystemFonts.MessageBoxFont;
  • ToolStrip의 경우 Auto Scaling을 적용할 경우 특히 화면에서 깨짐이 심하게 발생하고 훨씬 보기에 좋지 않습니다. 우선 Load 이벤트 처리기에 다음의 코드를 추가해야 하며, 이미지/아이콘을 사용하고 있다면 그래픽 디자이너와 협업하여 지원하려는 최대 DPI를 고려한 고해상도 이미지/아이콘으로 원본을 교체하도록 합니다. (https://www.medo64.com/2014/01/scaling-toolstrip-with-dpi/ 에서 인용했습니다.)
using (var graphics = CreateGraphics()) {
var scale = Math.Max(graphics.DpiX, graphics.DpiY) / 96d;
var newScale = (int)Math.Floor(scale * 100) / 50 * 50 / 100d;
  if (newScale > 1) {
var newWidth = (int)(yourToolStrip.ImageScalingSize.Width * newScale);
var newHeight = (int)(yourToolStrip.ImageScalingSize.Height * newScale);
yourToolStrip.ImageScalingSize = new Size(newWidth, newHeight);
yourToolStrip.AutoSize = false;
}
}

마무리

Windows Forms의 High DPI 개선 지원은 추후 .NET Core 3.0과 .NET Framework 4.8에 포함될 새로운 버전의 Windows Forms 컨트롤에서 크게 개선될 가능성이 있습니다. 하지만 새 버전의 프레임워크를 배포하는 것이 생각보다 간단한 문제가 아니기 때문에, 오늘 소개해드린 내용을 바탕으로 문제 해결을 시도해보시는 것을 권해드립니다.