- 이재웅, WPF 개발자 (2006~)
- Microsoft MVP 2023~
- GitHub
- GitHub
- WPF INSIDE OUT 집필
- jamesnet.dev (Blazor)
- WPF 밋업
- WPF 스터디 교육
- 유튜브 채널 Jamesnet
- 유튜브 과거 채널 (RankingHistory)
- League of Legends (WPF)
- Jamesnet.Wpf (NuGet)
- Blog
- 이메일 [email protected]
안녕하세요, 이재웅입니다. 이 글은 2024년 3월 24일 목요일에 한국 Microsoft 본사에서 진행된 Blazor MAUI WPF (aka. BMW) 밋업에서 발표한 내용입니다.
이번 밋업은 지난 해 2월부터 올해까지도 매 달 진행되고 있는 정기 밋업입니다. 이 행사는 인프라지스틱스와 마이크로소프트의 후원으로 무료로 제공되었습니다.
- Blazor Session: 김진석 (Microsoft MVP)
- MAUI Session: 조장원 (흑우마스터)
- WPF Session: 이재웅 (Microsoft MVP)
이 글은 WPF 세션에서 제가 약 70분간 발표를 했던 내용들을 자세하게 자세하게 리뷰하는 형식으로 구성되었습니다.
그리고 1년 넘도록 계속해서 발표할 수 있도록 세미나를 위해 힘 써주시고 계신 인프라지스틱스 조동수님과 김진석 대표님, 그리고 Microsoft 유 저스틴님께 특별히 깊은 감사 인사 드립니다.
저는 2006년 부터 개발을 시작했으며 현재 Microsoft MVP로 활동 중인 이재웅 개발자입니다. 1세대 WPF 개발자로서의 사명감을 가지고 오픈소스와 집필 그리고 무료 세미나 교육, 유튜브 튜토리얼 영상 제작 등 닷넷 기술을 중점으로 다양한 활동을 이어오고 있습니다.
이번 발표에서는 WPF 매니징에 필요한 좋은 습관에 대해 이야기해보고자 합니다. 주제 제목은 뭔가 대단해 보이지만 세부 항목들을 살펴보면 대부분 평범한 것들입니다. 하지만 이러한 규칙들이 탄탄하게 잘 지켜진다면 품격 있는 프로젝트를 리드할 수 있게 될 것입니다.
프로젝트를 관리하고 리드하는 것은 꼭 리더만의 역할에 국한된 것은 아닙니다. 프로젝트에 참여하는 모든 구성원들이 좋은 프로젝트를 매니징해 나갈 수 있습니다.
프로젝트를 시작하고 관리해 나가는 과정에서 우리는 다양한 이슈와 문제에 부딛히고 그 것을 해결해 나아갑니다.
- WPF 구성 요소
- Application
- Current
- MainWindow
- GetWindow
- Current/MainWindow 관리 설계
- 닷넷 버전 선택
- 닷넷 Standard 버전의 차이점 (2.0, 2.1)
- 닷넷 코어 기반의 멀티 타겟팅 (라이브러리)
- 런타임에서의 라이브러리
- 조건부 컴파일 (Conditional Compilation)
- 참조 전략 엿보기 (feat. 엔터티프레임워크)
- 타이트한 접근 제한자
- InotifyPropertyChanged 인터페이스 구현
- nameof 활용
- 람다식 속성 활용
- 어트리뷰트 활용 (CommunityToolkit.Mvvm)
- OnPropertyChanged 방식의 채택
- RelayCommand
- 의존성주입 Reflection
- 모듈화
- 모듈간 인터페이스 활용
- CustomControl
- DependencyProperty (Attached)
- DependencyProperty (Inhertis)
- DependencyProperty (Inhertis + Attached)
- 터널링/버블링
- ICommand 활용
- EventArgs 활용
- 리소스 관리
- CommunityToolkit.Mvvm
- Prim.Unity
- GitHub
WPF는 닷넷 파운데이션의 일원으로 등록되어 있으며, 소스코드 전체가 GitHub 레포지터리를 통해 관리되고 있으며, 누구나 기여자로서 참여 가능합니다.
- PresentationFramework.dll
- PresentationCore.dll
- WindowsBase.dll
- System.Xaml.dll
WPF의 핵심 DLL들 간의 참조 의존성 관계를 이해하는 것은 WPF 애플리케이션의 구조를 파악하고, 효율적으로 구성하는 데 큰 도움이 됩니다. 클래스의 위치와 필요한 종속성을 알면 개발, 최적화, 문제 해결이 용이해지며, 이는 더 견고한 애플리케이션 설계로 이어집니다.
DLL 간의 참조 도식화
이는 각 컴포넌트의 역할과 위치를 명확히 하며, 어떤 기능이 어느 DLL에 포함되어 있는지 알 수 있게 해주어, 필요한 클래스와 리소스를 적절히 참조할 수 있게 합니다. 더 나아가, 의존성을 파악함으로써 개발자는 성능을 최적화하고, 발생 가능한 문제를 예측 및 해결하는 데 필요한 깊이 있는 지식을 갖출 수 있습니다. 이러한 이해는 안정적이고 확장 가능하며 유지보수가 쉬운 WPF 애플리케이션을 설계하는 데 결정적인 요소가 되며, 결국 사용자에게 더 나은 경험을 제공하는 완성도 높은 소프트웨어로 이어지는 길입니다.
- 클래스: Border, Canvas, CheckBox, ComboBox, DockPanel, Expander, Grid, Image, ListBox, Menu, ProgressBar, RadioButton, ScrollViewer, Slider, StackPanel, TabControl, TextBlock, ToolBar, TreeView, WrapPanel, Style, DataTemplate, ControlTemplate, ResourceDictionary, Button, Label, MediaElement
- 인터페이스: IComponentConnector, IStyleConnector
- 열거형: FontStyle, FontWeight, HorizontalAlignment, VerticalAlignment, TextAlignment, Visibility, Stretch
- 구조체: CornerRadius, GridLength, Thickness
- 클래스: BitmapEffect, BlurBitmapEffect, DropShadowBitmapEffect, BitmapImage, DrawingGroup, GeometryDrawing, ImageBrush, LinearGradientBrush, MatrixTransform, PathGeometry, RenderTargetBitmap, ScaleTransform, SkewTransform, SolidColorBrush, TranslateTransform, VisualBrush
- 인터페이스: IRenderInfo, IResourceDictionary, IVisual
- 열거형: BrushMappingMode, GeometryCombineMode, GradientSpreadMethod, PenLineCap, PenLineJoin, TileMode, TextHintingMode
- 구조체: Color, Matrix, Point, Rect, Size, Vector
- 클래스: Application, CommandManager, DependencyPropertyRegister, DispatcherOperation, DoubleAnimation, KeyGesture, KeyBinding, PropertyChangedEventHandler, RoutedCommand, RoutedEvent, RoutedEventHandler, RoutedUICommand, Timer
- 인터페이스: ICommand, INotifyPropertyChanged, INotifyCollectionChanged
- 열거형: ComponentResourceKey, DispatcherOperationStatus, ModifierKeys, PropertyChangedEventArgs, UpdateSourceTrigger, DispatcherPriority
- 구조체: Duration, Int32Rect
- 클래스: XamlSchemaContext, XamlType, XamlMember, XamlDirective, NamespaceMapEntry, XamlNodeList, XamlXmlReader, XamlXmlWriter, MarkupExtension, StaticResourceExtension, BindingExtension
- 인터페이스: IXamlLineInfoConsumer, IXamlLineInfoProvider, IXamlSchemaContextProvider, IXamlObjectWriterFactory, IXamlMember
WPF를 구동시키기 위한 객체인 Application 클래스는 메인으로 실행될 Window 생성 시점을 포함하고 있으며, 공용 리소스를 등록할 수 있도록 설계되어 있습니다.
OnStartup: 윈도우 생성 시점
internal Class App : Application
{
[STAThread]
private static void Main(string[] args)
{
Window window = new();
window.ShowDialog();
}
}
Run: 애플리케이션 구동
internal Class Starter
{
[STAThread]
private static void Main(string[] args)
{
_ = new App().Run();
}
}
Application 인스턴스를 생성하고 이를 구동시키는 것은 아주 간단합니다. Main과 같은 프로그램 시작점에서 App 인스턴스를 생성하고 Run 메서드를 호출하기만 하면 됩니다.
처음 Application 인스턴스를 생성하고 Run (구동) 시점에서 Current가 내부적으로 함께 구성됩니다.
Application app1 = Application.Current
Current는 Static 맴버이며 Application 타입입니다. 따라서 형변환을 통해 App으로 사용하는 것도 가능합니다.
App app2 = Application.Current as App;
Current와 같은 맥략으로 OnStartup을 통해 실행된 Window 객체 또한 별도의 지정 없이도 Current.MainWindow를 통해 가져올 수 있습니다.
// 기본 윈도우
Window window1 = Application.Current.MainWindow;
// 특정 UI객체로 형변환
JamesWindow window2 = Application.Current.MainWindow as JamesWindow;
따라서 Application과 Window가 이미 기본 설계에 의해 Singleton으로 등록되어 관리되어 있기 때문에 어디서든 Window를 가져올 수 있습니다.
그리고 Window를 찾을 수 있는 더 재미있는 방법도 있습니다.
Window window = Window.GetWindow(this);
이 방법은 대상 객체를 기준으로 Window 컨트롤이 나올 때 까지 부모 객체를 재귀적으로 찾아 Window 또는 null을 반환하는 기능입니다. 따라서 특정 객체가 논리적으로 어딘가에 로드 되었다면 자신이 포함된 최상위 부모인 Window를 가져오게 됩니다.
Application과 WIndow의 Singleton 인스턴스 계층 구조
하지만 이를 치트키처럼 ViewModel이나 CustomControl에서 직접적으로 사용하는 것은 그리 좋은 방법은 아닙니다. 그 이유는 Application과 Window의 접근으로 인한 관리 포인트가 늘어나기 때문입니다. 따라서 이러한 직접적인 핸들링 작업을 하기 위한 클래스를 아래와 같이 만들어 Singleton 형식으로 관리하는 것이 현명합니다.
예: AppManager
public class AppManager
{
// app;
// AddResource
// Close
// GetWindow
private Application app => Application.Current;
public void AddResource(ResourceDictionary resource)
{
app.Resources.MergedDictionaries.Add(resourceDictionary);
}
public void Close()
{
app.Current.MainWindow.Close();
}
public Window GetWindow(UIElement element)
{
return Window.GetWindow(element);
}
}
예를 들어 AppManager 클래서와 같이 설계하여 관리 포인트를 줄이는 것입니다.
닷넷 버전은 크게 과거의 .NET Framework와 현재의 .NET (Core)로 구분됩니다.
- .NET Framework 4.81
- .NET 8.0
그리고 모두 호환되는 플랫폼도 존재합니다.
- .NET Standard 2.1
- .NET Standard 2.0
재미있는 사실은 .NET Framework에서는 2.1을 참조할 수 없습니다. 따라서 Standard 2.1은 .NET Framework를 제외하고 사용이 가능하다는 것입니다.
결론적으로 여러분이 .NET Framework를 포함한 멀티 타겟팅의 클래스를 만들기 위해서는 2.0을 선택하는 것이 올바른 선택입니다.
그리고 .NET Standard 2.1에서는 WPF를 사용할 수 없을 것처럼 보이지만 실제로는 사용할 수 있습니다. 단 .NET Core 버전의 WPF를 사용할 순 없고 대신 .NET Framework 버전의 WPF를 사용할 수는 있습니다.
.NET Standard을 통한 멀티 타겟팅 라이브러리 구현 방법도 있지만 .NET Core를 기반으로 멀티 타겟팅을 구현하는 방법도 있습니다. 이 방법은 꽤나 현실적이고 직관적이며 구성하기도 쉽습니다.
심플하게도 .NET Core 기반의 라이브러리를 만들기만 하면 됩니다.
기본 WPF 라이브러리 프로젝트 속성의 닷넷 버전 타겟팅 설정
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>
.NET Framework를 포함한 멀기 타겟팅 설정
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net8.0-windows</TargetFrameworks>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>
기존의 TargetFramework 속성을 TargetFrameworks로 변경 후 지금처럼 하나 이상의 닷넷 버전을 추가하기만 하면 됩니다.
멀티 타겟팅을 기반으로 하는 프로젝트 라이브러리를 만들었다면, 이제 닷넷 코어 또는 프레임워크를 통해 이를 각각 사용할 수 있습니다. 그럼 우리는 결국 닷넷 코어를 사용하는 것일까요? 아니면 코어를 사용하는 것일까요?
런타임에서 각각 라이브러리를 확인하면 코어는 코어, 프레임워크는 프레임워크를 참조하게 됩니다. 양자역학처럼 관측(빌드)되는 순간 변하는 것일까요?
농담입니다. 😄
사실 멀티 타겟팅**(TargetFrameworks)**을 통해 설정된 프로젝트 라이브러리는 이미 각각의 버전을 모두 빌드를 해둔 상태입니다. 따라서 참조를 한 프로젝트의 버전에 따라 자동으로 DLL이 매칭됩니다. 따라서 별 다른 설정 없이 멀티 타겟팅 형식의 라이브러리만 잘 참조하고 사용하기만 하면 되는 것입니다.
private void James3Window_Loaded(object sender, RoutedEventArgs e)
{
string windowInfo = GetType().BaseType.Assembly.ToString();
MessageBox.Show(windowInfo);
}
멀티 타겟팅 라이브러리를 구현할 때 지켜야 할 단 하나의 규칙이 있습니다. 그것은 포함된 닷넷 중에서 가장 낮은 버전을 기준으로 구현해야 한다는 것입니다.
만약 .NET Framework 4.8과 .NET 8을 타겟팅으로 한다면 .NET Framework 4.8에서 동작이 가능한 문법만이 사용 가능합니다.
예를 들어 new();와 같은 생성자 축약은 사용할 수 없게 됩니다.
// 사용할 수 없음..
Application app = new();
그리고 CommunityToolkit과 같은 라이브러리는 .NET 6.0 이상에서만 [ObservableProperty]와 같은 소스코드 자동 생성을 지원하는 어트리뷰트를 사용할 수 있기 때문에 이러한 문법들도 사용할 수 없게 됩니다. (.NET Framework 등이 멀티 타겟팅에 포함되었다는 가정 하에...) `
// 이 역시 사용 불가..
[ObservableProperty]
private string _title;
결론적으로, 닷넷 프레임워크를 포함하는 하위 호환의 멀티 타겟팅 라이브러리를 구현한다면 최신 닷넷 코어의 문법은 사용할 수가 없습니다.
하지만...
조건부 컴파일을 통해 특정 버전의 문법을 사용하는 것이 가능합니다.
조건부 컴파일을 통한 문법 활용
Window conditionalWindow = null;
#if NET5_0_OR_GREATER
conditionalWindow = new();
#else
conditionalWindow = new Window();
#endif
conditionalWindow.Title = "Conditional Window";
conditionalWindow.ShowDialog();
물론 이 소스코드는 예시를 위한 것이며 특정 버전에서만 동작하는 매우 동적인 기능을 만들기 위해 사용하는 것이 옳습니다. 단지 이 예는 극단적으로 최신 문법을 사용할 수도 있다는 것을 확인하기 위함입니다.
이처럼 멀티 타겟팅 라이브러리를 구현할 때에는 다양한 시도와 연구, 그리고 시행착오가 필요할 것입니다. 그리고 이미 누겟 패키지를 통해 이러한 전략을 엿볼 수도 있습니다.
그 중에서도 좋은 예시들을 찾아보도록 하겠습니다.
- CommunityToolkit.Mvvm
- Prism.Unity
- EntityFrameworkCore
CommunityToolkit의 경우에는 .NET Standard 2.0으로 구현되었습니다. 앞서 이야기 했던 것처럼 2.1 버전은 .NET Framework에서의 사용이 불가능하기 때문에 아마도 2.0 버전을 선택한 것으로 유추할 수 있습니다.
Prism.Unity의 경우에는 직접적으로 타겟팅이 지정되어 있습니다. 따라서 포함하는 모든 버전에 호환되도록 문법을 선택했을 것이며 경우에 따라 조건부 컴파일이 사용되었을 것입니다.
마지막으로 EntityFrameworkCore의 경우에는 .NET Framework를 제외한 멀티 타겟팅 전략을 취하고 있습니다. 따라서 코어에서는 EntityFrameworkCore를, 그리고 Framework에서는 EntityFramework를 각각 따로 관리합니다.
세 개의 유형 모두 누겟 패키지를 통해 제공되는 수많은 라이브러리들이 채택하고 있는 참조 전략입니다. 따라서 여러분들도 이를 통해 간접 경험과 시행착오를 줄일 수 있을 것입니다.
제 개인적인 의견으로는 모든 유형을 모두 직접 경험해보는 것을 권해드립니다. 답은 직접 사용해봄으로써 찾을 수 있는 것 같습니다.
접근 제한자를 활용하는 것은 아주 저비용 고효율의 가독성 향상 효과를 볼 수 있습니다. 먼저 예시를 통해 일반적인 ViewModel 구현 소스코드를 살펴봅시다.
평범한 ViewModel 소스코드
public partial class MainViewModel : ObservableObject
{
public ICommand SaveCommand { get; set; }
public MainViewModel()
{
SaveCommand = new RelayCommand<string>(Save);
}
void Save(string value)
{
// 처리
}
}
여기 일반적으로 접근 제한자를 별로 신경을 쓰지 않은 코드가 있습니다.
SaveCommand는 기본적으로 Xaml 바인딩이 될 속성이기 때문에 getter가 반드시 필요합니다. 하지만 setter의 접근 제한자를 좀 더 의미 있게 활용할 수도 있습니다.
setter 제거
public ICommand SaveCommand { get; }
이번에는 setter를 아예 제거해봤습니다.
이렇게 되면 생성자 외에서는 SaveCommand를 구현할 수 없게 됩니다. 따라서 아래와 같이 생성자 또는 메서드를 통해 할당이 가능합니다.
예시: 사용 가능/불가능 시점
public partial class MainViewModel : ObservableObject
{
// 가능
public ICommand SaveCommand { get; }
public MainViewModel()
{
// 가능
SaveCommand = new RelayCommand<string>(Save);
}
private OnInitialize()
{
// 가능
// SaveCommand = new RelayCommand<string>(Save);
}
void Save(string value)
{
// 처리
}
}
따라서 여러분이 SaveCommand를 할당하는 시점에 맞게 접근 제한자를 타이트하게 지정하는 것이 소스코드 품질과 가독성 향상 등 여러모로 좋은 선택이 될 것입니다.
그리고 좀 더 다양한 방법도 있습니다.
private set
public ICommand SaveCommand { get; private set; }
이 문법 또한 set을 제거한 것과 마찬가지의 효과를 얻습니다. private을 표시하는 것이 좀 더 나을까요? 여러분의 생각이 궁금해집니다. 😄
그리고 하나 더 있습니다.
init
public ICommand SaveCommand { get; init; }
마지막으로 init은 Initialize 즉 생성자에서만 동작하도록 합니다. 따라서 위의 세 가지 방법 모두 문법은 다르지만 동일한 동작을 나타내기 때문에 이를 잘 이해하여 규칙을 정하기만 하면 됩니다.
제 개인적인 취향으로는 가장 나중에 생겨난 init이 꽤 멋져 보입니다.
MVVM 패턴의 실체와도 같은 INotifyPropertyChanged를 통해 우리는 바인딩 가능한 속성을 만들게 됩니다. 또한 우리는 WindowsBase.dll에 포함된 이 상징적인 인터페이스를 한번 들여다볼 필요가 있습니다. 그리고 생각보다 많은 WPF 개발자들이 이를 직접 구현하여 사용하기도 합니다.
public class ObservableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
이 인터페이스는 OnPropertyChanged 메서드를 인터페이스 멤버로 구현하도록 합니다. 그리고 이를 통해 우리는 Setter를 통해 속성의 변화를 뷰에서 감지하도록 알리는 로직을 구현할 수 있게 됩니다.
OnProperty 호출 방법
private string _title;
public string Title
{
get
{
return _title;
}
set
{
_title = value; OnPropertyChanged("Title");
}
}
이 방식은 아주 클래식한 문법이기도 합니다. Title 속성의 이름을 문자열로 직접 입력하여 보내게 됩니다. 하지만 이 방식은 속성의 이름이 변경되었을 때도 싱크를 함께 맞추어야 하며 오타를 범할 수도 있게 됩니다.
사실, 더 좋은 방법이 있습니다.
속성을 좀 더 현대적인 문법으로 사용할 수 있습니다.
nameof 키워드 활용
set
{
_title = value; OnPropertyChanged(nameof(Title));
}
이렇게 되면 속성 이름의 오타를 범하는 실수를 방지하고 속성 이름 변경에 따른 추가 작업을 줄일 수 있어서 아주 좋습니다.
그리고 get set 문법을 람다식으로 바꿀 수도 있습니다.
람다식 문법 사용
private string _title;
public string Title
{
get => _title;
set { _title = value; OnPropertyChanged(nameof(Title)); }
}
하지만, Setter에서는 value 할당과 OnPropertyChanged 호출 모두 처리하기 때문에 이 문법을 사용하는 것이 불가능해집니다. 따라서 뷰모델의 기반을 좀 더 확장할 필요가 있습니다.
뷰모델 베이스를 통한 OnPropertyChanged 기능 확장
private void SetProperty(string propertyName, ref object value1, object value2)
{
value1 = value2;
OnPropertyChanged(propertyName);
}
이제 get set 모두 람다 식을 사용하는 것이 가능해집니다.
private string _title;
public string Title
{
get => _title;
set => SetProperty(nameof(Title), ref _title, value);
}
그리고 좀 더 줄여볼까요?
CallerName 어트리뷰트 활용
private void SetProperty([CallerName] string propertyName, ref object value1, object value2)
{
value1 = value2;
OnPropertyChanged(propertyName);
}
nameof 키워드를 제거
private string _title;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
이제 모두 한 줄로 표현해도 될 만큼 소스코드의 양이 줄여졌습니다.
한 줄로 선언한 속성
public string Title = get => _title; set => SetProperty(ref _title, value);
그리고 이제는 CommunityToolkit.Mvvm을 통해 속성 자체를 생략하는 단계까지 이르게 되었습니다.
[ObservableProperty] 어트리뷰트 활용
[ObservableProperty]
private _title { get; set; }
이처럼 MVVM Binding을 위한 속성이 놀랍도록 줄어든 모습을 볼 수 있습니다.
여러분은 CommunityToolkit.Mvvm의 이와 같은 극단적인 소스코드 축약이 마음에 드시나요?
저는 사실 이러한 방식을 빨리 받아들이지 못했습니다. 그 이유는 자칫 이러한 생략이 근본적인 MVVM 개념을 왜곡하거나 중요한 부분을 빠트릴 수도 있다는 생각 때문이었습니다.
하지만 앞서 살펴본 INotifyPropertyChanged의 개념을 논리적으로 잘 이해하고 좀 더 유연하게 받아들인다면 반복적인 소스코드를 줄이면서 오히려 더 중요한 영역에 집중할 수 있게 됩니다.
따라서 INotifyPropertyChanged를 잘 이해한다면 ObservableObject를 상속 받아 더욱 강력한 자신만의 프레임워크를 설계하는데 있어 큰 도움이 될 수 있을 것입니다.
다음 RelayCommand부터의 내용들은 2부로 편성하여 추후 밋업을 통해 다시 한 번 발표하는 자리를 만들어보도록 하겠습니다.
오프라인 세미나에 참여해주신 분들께 진심으로 감사드립니다. 온라인으로도 많이 활용해주시기를 기대하겠습니다.
끝,
2부에서는 지난 1부의 내용을 기반으로 계속해서 WPF 프레임워크 아키텍처를 위해 이해해야 할 다양한 요소들을 계속해서 살펴보겠습니다. 떡밥
- RelayCommand
- 의존성주입 Reflection
- 모듈화
- 모듈간 인터페이스 활용
- CustomControl
- DependencyProperty (Attached)
- DependencyProperty (Inhertis)
- DependencyProperty (Inhertis + Attached)
- 터널링/버블링
- IsHitTestVisible 활용
- ICommand 활용
- EventArgs 활용
- 리소스 관리
- CommunityToolkit.Mvvm
- Prim.Unity
- GitHub
INotifyPropertyChanged와 같은 방식으로 직접적인 구현 및 현대적인 사용 방식에 대해 알아봅니다.
의존성 주입을 어셈블리의 Reflection 기능을 활용하여 직접 구현하는 방법에 대해 살펴봅니다.
그리고 모듈화를 위한 프로젝트 설계하고 모듈 간의 연결을 인터페이스를 활용하는 방법에 대해서도 살펴봅니다.
CustomControl 구성 요소 및 활용 방법에 대해 살펴봅니다.
DependencyProperty의 Inheritis와 Attached 그리고 이 둘을 결합한 형태의 활용 방법에 대해 알아봅니다.
버블링/터널링을 통해 이벤트 구조 개념을 이해합니다.
IsHitTestVisible을 활용하여 버블링/터널링을 더 강력하게 활용하는 방버에 대해 살펴봅니다.
버블링/터널링과 CustomControl 기반의 설계를 통해 더 유연하고 파워풀한 이벤트 체계를 설계하는 방법에 대해 살펴봅니다.
CustomControl 기반에서 EventArgs를 활용한 더 풍부한 이벤트 핸들링 체계 기반을 만들어봅니다.
CustomControl 기반의 장점을 살리기 위한 리소스 관리 방법에 대해 살펴봅니다.
1/2부 내용을 기반으로 CommunityToolkit.Mvvm 아키텍처를 분석해봅니다.
마찬가지로 1/2부 내용을 기반으로 Prism.Unity의 사상과 아키텍처를 분석해봅니다.