WPF 연구소

그간의 WPF 오픈소스, 밋업, 교육, 책, 유튜브 튜토리얼 등을 통해 공유했던 내용들을 다시 한 번 리뷰하고 정보들을 공유하기 위한 공간을 만들었습니다. 오랜 시간 순으로 과거 내용들을 가져와 재구성하고 보완하는 식으로 진행될 계획입니다.

작성된 목록


  • 2023.02.11 - 1회 WPF 스터디 교육 :heavy_check_mark:
  • 2023.02.18 - 2회 WPF 스터디 교육
  • 2023.02.25 - 3회 WPF 스터디 교육
  • 2023.03.04 - 4회 WPF 스터디 교육
  • 2023.03.18 - 5회 WPF 스터디 교육
  • 2023.03.23 - 1회 WPF 밋업 (with Infragistics)
  • 2023.03.25 - 6회 WPF 스터디 교육
  • 2023.04.01 - 7회 WPF 스터디 교육
  • 2023.04.08 - 8회 WPF 스터디 교육
  • 2023.04.27 - 2회 WPF 밋업 (with Infragistics)
  • 2023.04.29 - 9회 WPF 스터디 교육
  • 2023.05.06 - 10회 WPF 스터디 교육
  • 2023.05.13 - 11회 WPF 스터디 교육
  • 2023.05.18 - .NET L!VE 2023 Spring (아발로니아)
  • 2023.05.20 - 12회 WPF 스터디 교육
  • 2023.05.24 - 3회 WPF 밋업 (with Infragistics)
  • 2023.06.22 - 4회 WPF 밋업 (with Infragistics)
  • 2023.07.27 - 5회 WPF 밋업 (with Infragistics)
  • 2023.08.24 - 6회 WPF 밋업 (with Infragistics)
  • 2023.08.26 - 13회 WPF 스터디 교육
  • 2023.09.02 - 14회 WPF 스터디 교육
  • 2023.09.09 - 15회 WPF 스터디 교육
  • 2023.09.16 - 16회 WPF 스터디 교육
  • 2023.09.26 - 7회 WPF 밋업 (with Infragistics)
  • 2023.10.26 - 8회 WPF 밋업 (with Infragistics)
  • 2023.11.23 -.NET Conf 2023 @ Blazor Korea (WPF for Blazor)
  • 2023.12.21 - 9회 WPF 밋업 (with Infragistics)
  • 2024.01.25 - 10회 WPF 밋업 (with Infragistics)
  • 2024.03.28 - 11회 WPF 밋업 (with Infragistics & Microsoft)
5개의 좋아요

1회 WPF 스터디 교육 - 오프라인 (이재웅)

2023.02.11일 토요일 13:00~17:00 (4시간)


교육 내용은 WPF 키워드를 통해 WPF의 주요 기술들을 빌드업 해가며 리뷰해 가는 과정이었습니다.

  1. Project v
  2. Application v
  3. Window v
  4. StackPanel v
  5. Grid v
  6. Border v
  7. DataContext v
  8. Button v
  9. Property v
  10. Style v
  11. Template
  12. ControlTemplate
  13. ContententTemplate
  14. DataTemplate
  15. Binding
  16. Trigger
  17. IValueConverter
  18. CustomControl
  19. Themes
  20. Generic

1. Project


WPF프로젝트는 닷넷 프레임워크 또는 코어 선택에 따라 구조와 구성이 달라집니다. 프레임워크의 경우에는 윈도우에 설치된 프레임워크 DLL 참조를 통해 WPF를 사용하지만 코어의 경우에는 프로젝트 속성을 통해 사용이 가능해집니다.

<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>True</UseWPF>

그리고 아무리 코어라 할지라도 WPF는 윈도우에서만 동작이 가능하기 때문에 반드시 TargetFramework에서 -windows 옵션을 추가해야 합니다.

참조 방식과 코어 전환

닷넷 프레임워크와 닷넷 코어의 참조 방식은 WPF 프로젝트의 구조와 구성에 큰 차이를 보입니다. 프레임워크에서는 시스템에 설치된 DLL을 직접 참조하는 반면, 코어는 NuGet 패키지를 통한 의존성 관리를 활용합니다. 이러한 차이는 프로젝트의 설정, 배포 및 유지 관리에 중대한 영향을 미칩니다.

특히 닷넷 코어는 모듈성, 유지 관리의 용이성, 보안성 및 최신 .NET 기능 접근성 측면에서 우위를 가지고 있습니다. 이러한 이유로 기존에 닷넷 프레임워크를 사용 중인 프로젝트의 경우, 닷넷 코어로의 전환을 적극적으로 고려해볼 필요가 있습니다. 전환은 프로젝트를 더욱 현대적이고 유연하게 만들 뿐만 아니라, 향후 확장성 및 관리 측면에서도 큰 이점을 제공할 것입니다.

프로젝트의 요구 사항과 환경을 면밀히 검토하여, 코어 기반으로의 전환 가능성을 확인하는 것이 좋습니다, 이는 기술적인 발전뿐만 아니라 장기적인 프로젝트 유지 관리 측면에서도 이익이 될 것입니다. 이러한 결정은 팀의 기술 역량과 프로젝트의 특정 요구 사항(레거시 등)에 따라 달라질 수 있으므로, 모든 사항들을 확인한 후에 진행하는 것이 좋습니다.

2. Application


Application 클래스는 전반적인 애플리케이션 수명 주기 관리 및 전역 이벤트 처리를 담당합니다. 주로 다음과 같은 기능을 수행합니다.

  • 애플리케이션 수명 주기 관리: WPF Application 클래스는 애플리케이션의 시작과 종료를 관리합니다. 일반적으로는 초기 WPF 애플리케이션 프로젝트의 기본 구성인 App.xaml 파일의 Application 인스턴스가 애플리케이션 실행과 함께 내부적으로 생성됩니다. 따라서 Application을 직접 매뉴얼로 구성할 경우 프로그램 시작점에서 Application을 직접 인스턴스 생성 및 Run 실행해야 합니다.

  • 전역 리소스 관리: 기본 구성인 App.xaml을 사용할 경우 Resources 컬렉션 안에서 전역 스타일 리소스들을 관리할 수 있습니다. 만약 매뉴얼로 클래스만 구현할 경우 인스턴스에 직접 접근하여 .Resources에 리소스를 포함시키거나 ResourceDictionary를 병합(Merge)할 수 있습니다. 만약 여러분이 DynamicResource의 특성을 이용해서 테마 또는 다국어 처리와 같은 기술을 구현한다면 ResourceDictionary를 스위칭할 수 있어야 하겠죠.

  • 메인 윈도우 설정 및 실행: Application 클래스는 애플리케이션의 메인 윈도우를 설정하고 실행할 책임이 있습니다. MainWindow 속성을 통해 애플리케이션의 주 윈도우를 지정할 수 있으며, Run() 메서드를 호출하여 지정된 윈도우를 실행할 수 있습니다. 이는 OnStartup 이벤트를 오버라이드하여 커스텀 로직으로 구현할 수 있습니다. MainWindow 속성을 직접 할당하지 않는 경우, Run() 메서드가 호출될 때 애플리케이션에 의해 자동으로 첫 번째 생성된 윈도우가 MainWindow으로 지정됩니다.

  • 종료 조건 설정: ShutdownMode 속성을 통해 애플리케이션의 종료 조건을 설정할 수 있습니다. 예를 들어, ShutdownMode.OnMainWindowClose 옵션을 선택하면 메인 윈도우가 닫힐 때 애플리케이션도 자동으로 종료됩니다. 다른 옵션으로는 ShutdownMode.OnLastWindowClose가 있어 모든 윈도우가 닫힐 때 애플리케이션을 종료할 수 있습니다. 물론 애플리케이션의 성격에 따라 이 옵션을 전혀 사용하지 않을 수도 있습니다.

  • 이름 혼동 주의: 프로젝트에서 자동으로 생성되는 MainWindow.xaml 파일은 단순히 WPF에서 제공하는 기본 이름일 뿐이며, Application 클래스의 MainWindow 속성과는 직접적인 관련이 없습니다. 개발자는 이를 혼동하지 않도록 주의해야 하며, 필요에 따라 다른 이름을 사용할 수 있습니다.

저는 이런 오해를 줄이기 위해 의도적으로 윈도우 이름을 MainWindow가 아닌 다른 이름으로 정하는 편입니다.

3. Window


Window는 Application을 통해 처음 시작되는 윈도우를 만들거나 팝업을 만들기 위해 사용되는 컨트롤입니다. 이 클래스는 ContentControl를 상속 받으며, FrameworkElement로부터 파생된 모든 컨트롤 중에서 유일하게 Parent 속성을 지정할 수 없는 특별한 컨트롤입니다.

기본적으로 전통적인 디자인의 윈도우 스타일을 제공 받지만 ContentControl의 특성을 이용하여 새로운 디자인의 윈도우를 만드는 것이 일반적입니다.

4. StackPanel


StackPanel은 주로 한 방향으로 자식 요소들을 정렬할 필요가 있을 때 사용되는 레이아웃 컨트롤입니다. 이 컨트롤은 특히 간단한 상황에서 유용하며, ListBox나 DataGrid와 같은 ItemsPresenter를 활용하는 ItemsControl 기반의 컨트롤 내에서 자식 요소를 추가하는 컨테이너로도 자주 활용됩니다.

StackPanel의 Orientation 속성은 자식 요소들이 세로(Vertical) 또는 가로(Horizontal) 방향으로 배열되는지를 결정합니다. 그리고 StackPanel 외에 다른 컨트롤들도 Orientation 속성을 포함하고 있어, 자식 요소들의 배열 방향을 유연하게 설정할 수 있습니다.

이에 해당하는 대표적인 트롤은 다음과 같습니다.

  • WrapPanel
  • UniformGrid

이들 각각은 특정 사용 상황에 따라 UI 구성에 매우 강력한 편의성을 제공합니다. 하지만 Grid와 같은 더 복잡하고 유연하고 강력한 레이아웃 컨트롤의 존재로 인해, 개인적으로는 StackPanel을 그렇게 자주 사용하지 않습니다. (개인 취향)

Orientation을 사용하는 WrapPanel과 UniformGrid컨트롤도 아주 특이하고 재밌으니 StackPanel과 함께 살펴보면 아주 좋습니다.

5. Grid


WPF 레이아웃 요소에서 Grid의 존재는 가장 높은 비중과 중요성을 가집니다. 특히 모든 시나리오에서 사용될 수 있을 정도의 유연함과 확장성을 가지며 Canvas와 더불어 유일하게 중첩이 가능하도록 설계되어 있기도 합니다. ~StackPanel 같은 건 빼고~

중첩이 가능하다는 것은 계층 형태의 제한적인 구조에서 벗어나 창의성을 더욱 발휘할 수 있다는 것을 의미합니다. 따라서 이 컨트롤을 통해 적절하게 복잡한 계층 구조를 단순하고 가독성 높게 리펙토링할 때에도 핵심적인 역할을 합니다.

Grid는 행과 열을 (각각 RowDefinition, ColumnDefinition)정의할 수 있는 것이 특징입니다. 더 나아가 Width 또는 Height의 Stretch를 자유롭게 지정할 수 있기 때문에 반응형 레이아웃 설계를 할 때에도 매우 효과적이고 직관적입니다. 단 CSS와 같이 조건부로 반응형 크기 제어를 할 수 있는 수준 까지는 기본적으로 제공하지 않습니다. 이런 부분은 직접 Grid를 상속 받아 구현하면 되겠습니다. (막 아이디어가 떠오르네요.)

그리고 Grid는 Attached 타입의 Row/Column, RowSpan/ColumnSpan 과 같은 속성을 제공하여 자식 컨트롤에서 설정을 할 수 있도록 제공하기 때문에 자식의 행/열 위치를 간편하게 지정하는 것이 특징입니다.

Grid.Row
Grid.Column
Grid.RowSpan
Grid.ColumnSpan

WPF 입문자라면 Grid는 질리도록 해도 좋으니 레이아웃을 다양하게 만들어보기를 권합니다.

6. Border


Border 컨트롤은 WPF에서 시각적으로 표현할 수 있는 가장 강력한 디자인 요소 중 하나입니다. 이 컨트롤은 단순한 경계를 생성하거나 라인을 디자인적 요소로 활용할 때 매우 유용합니다. 또한 자식 요소를 포함할 수 있어, 메인 레이아웃의 경계선, ItemsControl 기반 컬렉션의 구분, GroupBox 영역, Button의 경계, TextBox의 포커스 영역 등 다양한 요소에 활용됩니다. 프로참석러

Border는 Grid와 같은 Panel에서 파생되지 않는 독립적인 Decorator 컨트롤로, 자식을 중첩해서 다룰 수는 없지만, 대부분의 경우 Grid와 함께 사용되어 더 풍부한 레이아웃 구성을 가능하게 합니다.

이 컨트롤은 Background와 같은 기본적인 디자인 요소뿐만 아니라, 다른 레이아웃 클래스에서는 찾아볼 수 없는 특별한 속성들을 제공합니다.

  • BorderThickness
  • BorderBrush
  • CornerRadius
  • Background

Border 컨트롤의 매력은 그 유연성에 있습니다. BorderThickness 속성을 조절하여 테두리의 두께를 자유롭게 설정할 수 있으며, 이는 디자인에 따라 세밀하게 조정될 수 있습니다. 예를 들어, 두께를 조절하여 더욱 뚜렷하게 테두리를 강조하거나, 미묘하게 처리하여 세련된 느낌을 줄 수 있습니다.

그리고 BorderBrush 속성을 이용해 색상을 선택하거나, 그라데이션을 적용하여 재미있는 스타일을 구현할 수 있습니다.

Border의 특성과 중첩을 잘 표현한 샘플이 마침 있어요.

단 색상은 한 가지 색으로만 가능하다는,

Html에서는 border-top, border-left 이런 식으로 각각 지정할 수도 있죠, 즉 WPF에서도 이와 같이 구현하기 위해서는 Border를 여러 개 중첩하여 표현하는 방식으로 해야 합니다.

CornerRadius 속성은 Border의 모서리를 둥글게 처리하여 부드러운 느낌을 줄 수 있습니다. 이는 디자인에 따라 클래식하거나 현대적인 느낌을 줄 수 있어, 다양한 사용자 경험을 만들어내는 데 아주 유용합니다.

결론적으로, WPF에서는 대부분의 컨트롤들이 경계선을 직접적으로 표현할 수 없기 때문에 Border를 다양하게 활용하는 것이 중요합니다. 이를 통해 CustomControl과 같은 복잡한 기능의 컨트롤을 구현할 때도 더 풍부하고 다양한 요구사항을 효과적으로 구현해 낼 수 있습니다.

7. DataContext


WPF의 DataContext는 매력적인 아키텍처를 가지고 있으며, 다양한 데이터 유형을 지원하여 애플리케이션의 유연성, 확장성과 같은 설계 적인 면에 있어 핵심적인 요소로 사용됩니다. DataContext를 이용하면 단순한 객체부터 시작하여 문자열, 정수, 인스턴스 객체, ViewModel, 심지어는 컨트롤까지 다양한 데이터 유형을 다룰 수 있습니다. 이는 MVVM 패턴을 통해 데이터와 UI를 효과적으로 분리하여 분산화 개발을 용이하게 합니다.

뿐만 아니라, DataContext는 부모-자식 관계를 통해 계층적인 구조를 가지고 있습니다. 부모 요소의 DataContext가 자식 요소에게 상속되는 특성은 데이터의 일관성을 유지하고 코드의 재사용성을 높입니다. 이를 통해 데이터의 흐름을 쉽게 파악하고 관리할 수 있습니다.

DataContext는 자신에게 직접 할당된 데이터뿐만 아니라 더 상위의 부모 요소의 DataContext를 찾아 사용할 수 있습니다. 이를 통해 하위 요소들은 필요한 데이터를 부모 요소에서 가져와 사용할 수 있습니다. 이는 데이터의 재사용성과 일관성을 높이는 데 도움이 됩니다.

DataContext의 데이터 전파는 일종의 상속 체인으로 이해할 수 있으며, 각 요소는 필요한 데이터를 부모 요소에서 찾아 사용할 수 있습니다. 이는 데이터의 유연한 활용과 코드의 간결성을 높이는 데 있어 핵심적인 요소입니다.

그러나 DataContext의 탐색은 상위 계층으로의 데이터 전파에만 적용됩니다. 하위 계층이나 같은 레벨의 요소로의 탐색은 불가능하며, 이러한 작업이 필요한 경우에는 다른 접근 방법을 고려해야 합니다.

의존성 주입, 이벤트 등 다양한 방법이 존재합니다.

MVVM 패턴과 모듈화를 통한 프레임워크 설계를 잘 해내기 위해서는 DataContext의 개념에 대해 계속해서 연구해나가는 것이 중요합니다. DataContext를 올바르게 이해하고 활용하는 것은 애플리케이션의 아키텍처를 설계하고 개발하는 데 있어서 핵심적인 부분입니다. 올바른 DataContext의 사용은 코드의 가독성을 높이고 유지보수를 용이하게 만들어주며, 또한 애플리케이션의 확장성을 향상시킵니다.

결론적으로, 우리는 DataContext를 이해하고 이를 깊이 있게 활용하기 위해 노력해야 합니다. DataContext의 개념을 지속적으로 연구하고 활용하여 MVVM 패턴과 모듈화를 통한 프레임워크 설계를 더욱 효과적으로 수행할 수 있습니다.

8. Button


Button의 설계 구조는 WPF의 방향성과 그 철학을 반영합니다. Button은 ContentControl 클래스로부터 상속받아 다양한 형태의 콘텐츠를 담을 수 있는 유연성을 가지고 있습니다. 개발자는 이를 통해 문자열, 이미지, 또는 더 복잡한 레이아웃을 Button의 내용으로 쉽게 할당할 수 있습니다.

더 나아가, Template 속성을 통해 Button의 외형을 완벽하게 제어하고 재구성할 수 있는 가능성을 제공합니다. 이와 같은 설계적 원리는 Button에만 국한되지 않고 WPF에서 제공하는 거의 모든 컨트롤에 적용되어 있습니다. 따라서, Button의 구조를 분석하고 사용자화(customizing)하는 연습은 WPF를 이해하고 배우는 데 아주 좋은 출발점이 될 것입니다.

Button은 'Click’이라는 중요한 이벤트를 가지고 있습니다. 이 이벤트는 내부적으로 Button에서 MouseDown과 MouseUp 이벤트가 성공적으로 발생한 후에 처리됩니다. Button은 이 마우스 이벤트들을 내부적으로 (Click을 위해) 소비하므로, 상위 컴포넌트로의 추가적인 이벤트 전파는 일어나지 않습니다.

따라서 버블링/터널링을 핸들링할 때 Button 요소가 중간에 포함되어 있다면 이를 주의해야 합니다. 또한 기본적으로 Click 시 ICommand를 바인딩할 수 있도록 Command 속성도 제공하고 있기 때문에 MVVM 패턴에서도 이벤트 로직을 ViewModel로 손쉽게 가져올 수도 있습니다.

결론적으로, Button 컨트롤을 통해 ContentControl 구조에 대해 깊이 있게 연구하고 다양하게 구현해본다면 WPF 기술력을 높이는 데 있어 아주 중요할 것입니다.

9. Property


WPF에서의 의존성 프로퍼티 사용

WPF에서 컨트롤은 의존성 프로퍼티를 통해 확장성과 유연성을 가지게 됩니다. XAML에서 컨트롤의 Background, Margin, Content 등의 시각적인 속성은 일반적인 프로퍼티가 아닌 의존성 프로퍼티로 설정되며, 이를 통해 XAML 상에서 속성 값을 조정할 수 있습니다. 그리고 의존성 프로퍼티는 값이 변경될 때 내부적으로 콜백 이벤트를 정의할 수 있도록 되어있기 때문에, 데이터 바인딩과 애니메이션 같은 WPF의 핵심 기능을 가능하게 하므로, 복잡하고 풍부한 UI 구현을 위해 필수적입니다.

뷰모델과의 데이터 바인딩 (일반 프로퍼티)

뷰모델에서 사용되는 일반 프로퍼티와 (UI 컨트롤) 의존성 프로퍼티 간의 연결은 WPF의 데이터 바인딩을 통해 이루어집니다. 뷰모델 내 프로퍼티는 INotifyPropertyChanged 인터페이스를 구현하여 값 변경을 알릴 수 있으며, 이를 통해 UI에 바인딩이 된 의존성 프로퍼티 속성 값이 동적으로 업데이트됩니다. 따라서 뷰모델에서는 의존성 프로퍼티가 아닌 일반 프로퍼티를 사용합니다. 이것은 흔히 실수 하는 혼동 중 하나입니다.

주요 의존성 프로퍼티의 사용 및 계층적 설계 이해

DataContext, Background, Content, Margin과 같은 주요 의존성 프로퍼티들은 FrameworkElement와 Control 클래스에서 파생되며, 이러한 프로퍼티들은 애플리케이션에서 데이터 표현과 레이아웃 구성에 핵심적인 역할을 합니다. 의존성 프로퍼티를 다양하게 커스터마이징하는 것도 유용하지만, 계층적 구조를 잘 이해하고 기존의 의존성 프로퍼티를 활용하는 것이 중요합니다. 이 접근법은 WPF의 기본 구조를 더 잘 이해하고 다룰 수 있게 해 줍니다.

이러한 의존성 프로퍼티들은 WPF의 특징적인 부분으로, 개발자가 보다 세밀하고 효율적인 사용자 인터페이스를 구현할 수 있게 돕습니다. 프로퍼티들의 계층적 구조와 특성을 이해하고 활용하는 것은 WPF 개발에서 중요한 기술입니다. 이러한 이해는 효과적인 커스텀 컨트롤 개발에도 도움이 됩니다.

10. Style


FrameworkElement는 컨트롤의 모든 속성을 정의하는 데 사용되는 Style을 제공합니다. 따라서 FrameworkElement에서 파생된 모든 클래스는 이 스타일을 활용할 수 있습니다.

스타일은 컨트롤에 직접 StaticResource 또는 DynamicResource를 적용하거나, App.xaml이나 Application.Resources 컬렉션에 x:Key를 사용하지 않고 스타일을 추가함으로써 일반적으로 적용할 수 있습니다.

// 기본 App.xaml:

<Application>
    <Application.Resources>
    </Application.Resources>
</Application>

// 매뉴얼 App:

internal class App : Application
{
    public App()
    {
        // Resources.Add(스타일 리소스);
        // 또는
        // Resources.MergedResourceDictionaries.Add(리소스 딕셔너리);
    }
}

또한,CustomControl의 Generic.xaml을 통해 기본 스타일을 적용하는 방법도 있습니다 (이에 대한 설명은 후반부에 있습니다).

DynamicResource 응용

테마나 다국어 구현을 위해 DynamicResource를 사용할 때, ResourceDictionary를 수동으로 등록하고 제거하는 것이 유연해야 합니다. 따라서 기본적으로 제공되는 App.xaml을 사용하기보다는 매뉴얼 App 클래스 구조를 구성하여 ResourceDictionary의 등록과 제거를 직접 유연하게 관리할 수 있는 상태를 만드는 것이 바람직합니다.

생각해보니 ResourceDictionary를 스위칭 하는 트리거 형태의 스타일을 만들어보는 것도 아주 유용할 것 같네요?

다만 저는 개인적으로 CustomControl을 통한 모듈화, 분산화를 기반으로 아키텍처 설계를 하기 때문에 공용리소스를 늘리는 것보다는 CustomControl의 개수를 늘리는 방식을 선호합니다.

관련 링크 보기


6개의 좋아요