요즈음 File-based App을 통해서 많은 것들이 가능함을 여러 모로 계속 보여드리고 있습니다. 그런데 가장 기본이 되는 Windows Forms와 WPF에 관한 이야기를 하지 않았었네요!
#:sdk Microsoft.NET.Sdk
#:property OutputType=WinExe
#:property TargetFramework=net10.0-windows
#:property PublishAot=False
#:property UseWPF=False
#:property UseWindowsForms=True
// https://github.com/dotnet/winforms/issues/5071#issuecomment-908789632
Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown);
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
Application.OleRequired();
Application.EnableVisualStyles();
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
Application.SetCompatibleTextRenderingDefault(false);
using var form = new Form()
{
Text = $"Hello, World! - {Thread.CurrentThread.GetApartmentState()}",
Width = 640,
Height = 480,
StartPosition = FormStartPosition.CenterScreen,
};
form.Controls.AddRange([
new Label
{
Text = $"Hello, World! - {Thread.CurrentThread.GetApartmentState()}",
Font = new Font(FontFamily.GenericSansSerif, 30f),
TextAlign = ContentAlignment.MiddleCenter,
Dock = DockStyle.Fill,
}
]);
form.Controls.AddRange();
using var appContext = new ApplicationContext(form);
Application.Run(appContext);
File-based App을 이용하되, Windows Forms의 경우에는 Native AOT가 지원되지 않는 관계로, 그리고 Windows 타겟으로 애플리케이션을 빌드해야 한다는 지침을 지정해야 하는 부분, 그리고 Top Level Program의 문법을 그대로 유지하면서 STA Thread를 명시적으로 지정하기 위한 방법을 새롭게 찾게 되어 하나로 묶어 정리해 보았습니다.
몇 가지 특징적인 부분을 살펴보면…
- OutputType: 콘솔 핸들을 부여하지 않고 WInMain 메서드를 이용해서 시작하도록 WinExe로 지정했습니다.
- TargetFramework: net10.0-windows로 지정하여 WPF 또는 Windows Forms를 참조하도록 구성합니다.
- PublishAot: 아쉽게도 Windows Forms, WPF 모두 AOT 빌드를 지원하지 않으므로 명시적으로 끕니다.
- UseWPF, UseWindowsForms: 필요에 따라 True/False로 지정하여 끕니다. 둘 다 켜는 것도 가능합니다.
그리고 Windows Forms와 WPF 모두 STA 모드에서 실행되는 스레드가 필요한데, Microsoft에서 제안하는 Top-Level Program을 위한 대안으로 아래와 같이 실제로 코드를 작성하도록 기능을 포함했다는 이야기가 있어서 그대로 적용해봤습니다.
참고로 여기서 언급된 것과 달리, STA Thread 설정 동작은 실제로 소스 제너레이터에서는 형성되지 않는 것으로 보입니다. (가상 프로젝트로 만들어지는 Main 메서드 파트가 TLP 스타일이 아니어서 그럴 수도 있을 것 같긴 합니다.) 그래서 다소 번잡하지만 여기서는 명시적으로 초기화 코드를 넣었습니다.
그 다음에 오는 코드는 보통 Windows Forms 기반의 애플리케이션 도입부에 들어가는 초기화 코드를 그대로 사용하면 잘 동작합니다.
개인적으로 한 가지 아쉬운 점은, 애초에 Windows Forms나 WPF 모두 디자이너 사용을 전제로 만들어진 것이어서 Fluent Pattern이나 Builder Pattern으로는 코드를 작성하는 것이 잘 안됩니다. 그나마 이를 보완할 수 있는 수단은 C# 14부터 도입되는 Extension Member를 활용하는 것이고, 이를 이용하면 Windows Forms나 WPF에서도 Swift 스타일의 UI 코드 작성이 본격적으로 가능해 질 것으로 보입니다.