WPF 같이 배워볼까요?

(이 글은 매우 빈번하게 내용이 변경되고 있습니다!!)


안녕하세요 James입니다. :smile:

저는 2008년부터 쌓아온 저의 모든 WPF 지식을 총 동원하여
여러분에게 처음부터 끝까지 모두 소개 하고자 합니다.

여러분, 지금부터는 긴 여정의 시작이 될 것입니다.
하지만 쉽고 완전 재밌어야 하는 것이 이 글에서 저의 중요한 원칙입니다.

이야기 순서

이틀에서 3일에 한 챕터 씩 추가될 예정입니다. (구라…ㅎㅎ)


TBD…

  1. (가제) 컨트롤 (System.Windows.Controls)
  2. (가제) Xaml과 Html 차이
  3. (가제) DataContext
  4. (가제) Content
  5. (가제) ControlTemplate
  6. (가제) Trigger
  7. (가제) ContentPresenter
  8. (가제) WPF 개발자의 디자인
  9. (가제) WPF 특이점
  10. (가제) ListBox, ListBoxItem
  11. (가제) TreeView, TreeViewItem
  12. (가제) ComboBox, ComboBoxItem
  13. (가제) DataTemplate
  14. (가제) Binding
  15. (가제) MVVM이 중요하지 않은 이유
  16. (가제) DataGrid
  17. (가제) WPF 좋은 질문 하는 방법
  18. (가제) 동기부여
  19. (가제) Window

(주의! 순서와 제목은 계속 바뀌고 있습니다.)

39개의 좋아요

1. 찰스페졸드의 WPF


WPF 시작의 대명사와도 같은 이 책에 대해 이야기 해볼까요?

image

찰스페졸드의 WPF


이 책은 무려 약 16년 전에 출판 되었는데요.

찰스페졸드는 1985년 부터 윈도우 개발을 시작한 역사상 가장 위대한 개발자 중 한명으로 평가받는 인물입니다. 그의 시각에서 펴낸 WPF 또한 누구보다 우아하고 날카로울 것입니다.

이 책에서는 설명하는 객체의 계층 구조를 지나칠 정도로 강조하며, 닷넷프레임워크 설계 구조에 대한 비하인드 스토리를 쉴세없이 이어나가고 있습니다. 그렇기 때문에 WPF의 기본 지식은 물론이고 C# 닷넷 프레임워크에 대한 높은 이해도까지 요구하는 극악의 난이도를 자랑합니다.

저는 이 책이 마치 WPF를 아직 배우지 않은 또다른 찰스페졸드를 위한 책처럼 느껴지는데요.

또한 많은 개발자들이 이 책을 통해 WPF를 시작하지만 이해할 수 없는 설명 때문에 좌절하거나 시작을 포기하지 않기를 바라는 마음입니다.

그러기 위해서는 누군가 이 책의 실체를 파헤쳐야 합니다.

? 이 책의 단점을 살펴볼까요?

  • 1,300페이지가 넘는 페이지 양
  • 2007년 발 개발 스타일. (16년 전)
  • 길고 긴 소스코드 예제
  • 너무나도 많고 방대한 내용들
  • 처음부터 끝까지 일관된 어려움…
  • 재미의 부재…

이러한 단점으로 인해 여러분의 의지를 계속해서 괴롭히고 시험할 것입니다.

? 그래서 James는 이 책을 추천하지 않나요?

아뇨… :smile: 저는 이 책을 3권이나 갖고 있어요!! (배신ㅎ)
심지어 최고의 책인걸요!

사실 앞서 말한 단점은 단점도 아닙니다.
이 책의 수준이 높기 때문에 단점이 된 것이지 정말 무결점 같은 책입니다.

윈도우를 섭렵한 개발자가 해석한 1,300 페이지 분량의 WPF는 참 귀하거든요.
또한 영문 버전의 경우 무료로 제공하기 때문에 컴퓨터로 보고 검색 하기에도 아주 좋습니다.

하지만 이 책을 지금 당장 볼 필요는 없습니다. 첫 시작과 함께 하기 좋은(응원이 되는) 책이 아닙니다. 오히려 이 책은 여러분의 러닝커브를 더욱 더 높일 것입니다.


? 그럼 WPF는 처음에 어떻게 배워야 하나요?

선배에게 질문하기

직장, 학교, 커뮤니티 등 먼저 WPF를 시작한 동료들에게 끊임없이 질문하세요.

오픈소스 만들기

WPF를 통해 만들고 싶은 프로그램을 GitHub에서 시작하고 공유하세요. 부족한 부분과 원하는 것을 더 빨리 얻을 수 있습니다.

찰스페졸드의 WPF

WPF에 대한 자신감을 얻고 이 책을 다시 도전해보세요.

끝으로…


저와 함께 찰스페졸드의 WPF 책을 받아들일 준비를 같이 해보실까요?

16개의 좋아요

TBD… 주의! 작성 중입니다.


2. 색상 표현


? 여러분, WPF를 처음 시작할 때 가장 먼저 배워야 할 것이 무엇일까요?

정해진 순서는 없지만 WPF는 시각적인 UI를 자유롭게 표현할 수 있다는 것이 강점인데요.
그래서 제가 가장 먼저 소개하고 싶은 부분이 바로 색상입니다.

WPF는 색상에 있어 아주 유연하고 자유도 높은 표현력을 자랑해요.

WPF는 생각보다 더 멋지게 색상을 표현할 수 있습니다. 그러기 위해 이 글을 절대 놓치지 마세요! :smile:

1. HEX 색상 코드 (16 진수)


모든 개발 언어에서 널리 사용되는 Hexadecimal(16진수)색상 코드는 WPF에서도 사용되는데요. 여기에서는 더욱 더 빛을 발휘합니다.

WPF는 특히 Hexadecimal(16진수)를 통해 색상을 깊이 있게 표현할 수 있는데요.
저는 지금부터 이 것을 HEX 코드라고 지칭하겠습니다.

? 그럼 정말 자주 사용하는 HEX 코드를 통해 읽는 방법을 알아볼까요?

#000000 // Black 블랙
#FFFFFF // White 화이트

WPF에서 가장 많이 사용되는 컬러는 #000000, #FFFFFF 이 코드일 것입니다.

  • 000000 → 블랙
    숫자가 0과 가까울 수록 어두움을 나타내고 있습니다.

  • FFFFFF → 화이트
    역시 블랙과 반대로 F에 가까울수록 밝음을 나타내고 있습니다.

만약 100% 검정 보다는 조금 덜 어두운 검정색을 원한다면 점점 FF와 가까워지면 됩니다.

HEX 사용 빈도 색상 설명
000000 자주 사용 100% 어두운 블랙 색상
010101 - 100%보다 좀 덜 어두운 블랙 색상 (하지만 사람 눈으로는 절대 구분 불가… :sweat_smile:)
252525 자주 사용 비주얼스튜디오(다크 테마) 배경색 처럼 100% 검정은 아니지만 은은한 진한 블랙
666666 - 검정이라 하기 애매한 블랙 색상
AAAAAA 자주 사용 점점 화이트에 가까워 지는 색상
DDDDDD 자주 사용 촌스럽지 않은 선(Border) 색을 표현할때 이상적인 색상
EEEEEE 자주 사용 100% 화이트는 싫을 때
FFFFFF 자주 사용 100% 화이트 색상

? 그런데 왜 16 진수처럼 보이지 않나요?

사실 이 코드는 16 진수 3 개가 합쳐진 색상 값입니다.

00 + 00 + 00
FF + FF + FF

그리고 이 값들이 16진수에서 → 10진수로 변환할 수도 있습니다.
(이것이 바로 RGB)

WPF에서 표현되는 모든 색상은 RGB 합성 표현색인 Red + Green + Blue 구조를 통해 이루어집니다.

그리고 때로는 HEX 코드를 → RGB로, 또는 RGB를 → HEX 코드 형태로 변환하는 일이 생기는데요.

? 그럼 RGB 형식으로도 한번 변환해볼까요?

000000 -> R: 0, G: 0, B: 0
FFFFFF -> R: 255, G, 255, B: 255

이처럼 HEX 코드와 RGB 값은 서로 변환이 가능한 형태임을 확인합시다!

만약 WPF가 16 진수로 변환한 6 자리 코드를 사용하지 않았다면 최대 9자리 (255 + 255 + 255) 코드형태로 사용했을지도 모르는 일이네요. :pensive:

? 우선 사용 방법을 살펴볼까요?

XAML 상에서 StackPanel 안에 여러 배경 색상의 Border를 만들어 보겠습니다.

<StackPanel>
    <Border Background="#FFFFFF" Width="50" Height="50"/>
    <Border Background="#000000" Width="50" Height="50"/>
    <Border Background="White" Width="50" Height="50"/>
    <Border Background="Black" Width="50" Height="50"/>
</StackPanel>

이처럼 문자열로 간단하게 Background 색상 표현을 할 수 있습니다.
참고로 Brush 속성은 내부적으로 문자열 값을 Brush 객체로 자동으로 변환합니다.

Background(Brush)는 Hex 코드 뿐만 아니라 Brushes 클래스가 제공하는 기본 색상(속성)도 사용이 가능합니다. 또한 Brush에 포함된 IFormattable 인터페이스를 통한 문자열 → Brush 객체 변환(Convert) 처리가 구현되어 있기 때문에 직접 Brush 객체를 만들지 않아도 쉽게 색상을 표현할 수 있습니다.

이 부분이 크게 중요한 부분은 아닐지도 모르지만 실제 소스코드 상에서 .Background를 접근할 때 #FFFFFF과 같은 문자열이 아닌 Brush 객체를 마주하게 될 것이기 때문에 이런 상황을 인지하고 있는 것은 도움될 수 있습니다.
기본적으로 Brushes 클래스에서 Static으로 제공하는 모든 색상(속성)을 Text 형태로 입력할 수 있으며

내부적으로 Brushes.Text 또는 16진수 Hex 값을 Parse 처리하는 internal 기능을 통해 Brush 객체로 변환됩니다. (참고)

? 저는 디자이너 혹은 Blender가 아닌데 이 정도 까지 해야 하나요?

사실 WPF에서는 000000 ~ FFFFFF 범위 내의 블랙/화이트 색상만 잘 표현한다면 어두운 계열 (다크 테마) 느낌의 앱도 손쉽게 표현해낼 수가 있습니다.

WPF는 개발자가 쉽게 UI를 표현할 수 있는 것이 장점입니다.
WPF를 유연하게 표현하기 위해 이 HEX 코드에 익숙해지는 것이 중요합니다 !!!

심지어 아래 앱 사진은 겨우 5~6개의 색상 만으로도 보기 좋은 배색을 만들어 낼 수 있습니다.

(참고이미지)

image

HEX 코드… 너무 중요해서 한번 더 강조 할게요.

보기 좋은 WPF UI는 색상에서 부터 나옵니다.

2. 배경 색상(Background or Fill)


배경 색상은 UI 객체 속성 중에서도 가장 대표적이면서 상징적인 프로퍼티 입니다. 왜냐하면 색상을 표현하는 속성 중에서도 유일하게 모든 UI 객체에 존재하고 있기 때문입니다. (물론 예외도 있지만 대세에 지장 x)

WPF의 배경 색상은 부모 객체의 유형에 따라 Background 또는 Fill 속성으로 제공됩니다.
그리고 이 속성들의 이름은 각각 다르지만 동일한 객체 타입(Brush)임을 확인할 수 있습니다.

프로퍼티 이름 타입 (객체) 선언 형식
Background Brush Brush Background { get; set; }
Fill Brush Brush Background { get; set; }

대부분의 UI는 Background를 사용하지만 Geometry와 관련이 있는 UI(Ellipse, Reactangle, Path 등…)의 경우 Fill을 사용합니다.

다만 둘 다 같은 Brush 객체이기 때문에 표현하는 방식, 사용 방법은 동일합니다.

? 배경 색상을 직접 확인해볼까요?

UI 컨트롤마다 Background(Fill) 속성이 어떤 상위 부모 객체를 통해 존재하는지 의식하고 사용하는 것은 WPF에서의 디자인 구조와 설계 철학을 이해하고 이를 수준 높게 구현하는데 있어 매우 도움이 됩니다.

그렇다면 여러분이 직접 부모 객체를 확인하는 방법에 익숙해져야 합니다.

부모 객체 확인 방법은?

  • 속성선택 → F12
  • 속성선택 → 또는 우클릭 → 정의로 이동(G)

형식 및 멤버 정의 보기
부모 객체를 찾는 방법에 대해 익숙하지 않다면 반드시 먼저 숙지해야 합니다!!!

? 방법을 숙지했다면 부모 객체를 탐색해볼까요?

UI 객체를 아래와 같이 선언하고 Background(Fill) 속성을 통해 부모 객체를 직접 확인할 수 있습니다.

Grid grid = new();
UserControl userControl = new();
TextBlock textBlock = new();
Ellipse ellipse = new();
ViewBox viewBox = new();

grid.Background = Brushes.Red;
userControl.Background = Brushes.Green;
textBlock.Background = Brushes.Blue;
ellipse.Fill = Brushes.Pink;

여러분이 자주 사용하는 UI 컨트롤의 부모 객체를 확인해보셨나요?
그리고 저는 Background 를 사용하는 모든 UI객체를 조사하여 아래 표로 만들었습니다.

컨트롤이 참 많죠? :smile:
(너무 많지만 이번 기회에 저와 함께 전체 목록을 한번 살펴봅시다.)

컨트롤 (UI 객체 클래스) 이름 속성 이름 부모 객체
Grid Background Panel
UniformGrid Background Panel
Canvas Background Panel
InkCanvasInnerCanvas Background Panel
StackPanel Background Panel
DockPanel Background Panel
WrapPanel Background Panel
TabPanel Background Panel
VirtualizingPanel Background Panel
ToolBarOverflowPanel Background Panel
Calendar Background Control
ContentControl Background Control
DataGridColumnFloatingHeader Background Control
DataGridRow Background Control
DatePicker Background Control
FlowDocumentReader Background Control
FlowDocumentScrollViewer Background Control
ItemsControl Background Control
PasswordBox Background Control
CalendarItem Background Control
DocumentViewerBase Background Control
RangeBase Background Control
ResizeGrip Background Control
TextBoxBase Background Control
Thumb Background Control
Separator Background Control
StickyNoteControl Background Control
(ButtonBase) Button Background Control
(ButtonBase) GridViewColumnHeader Background Control
(ButtonBase) DataGridColumnHeader Background Control
(ButtonBase) DataGridRowHeader Background Control
(ButtonBase) RepeatButton Background Control
(ButtonBase) ToggleButton Background Control
(ComboBox) TextBlockComboBox Background Control
(ContentControl) DataGridCell Background Control
(ContentControl) Frame Background Control
(ContentControl) GroupItem Background Control
(ContentControl) HeaderedContentControl Background Control
(ContentControl) Label Background Control
(ContentControl) ListBoxBoxItem Background Control
(ContentControl) ButtonBase Background Control
(ContentControl) StatusBarItem Background Control
(ContentControl) ScrollViewer Background Control
(ContentControl) ToolTip Background Control
(ContentControl) UserControl Background Control
(ContentControl) Window Background Control
(TextBoxBase) RichTextBox Background Control
(TextBoxBase) TextBox Background Control
(HeaderedContentControl) Expander Background Control
(HeaderedContentControl) GroupBox Background Control
(HeaderedContentControl) TabItem Background Control
(HeaderedItemsControl) TreeViewItem Background Control
(ItemsControl) HeaderedItemsControl Background Control
(ItemsControl) DataGridCellsPresenter Background Control
(ItemsControl) DataGridColumnHeaderPresenter Background Control
(ItemsControl) MenuBase Background Control
(ItemsControl) Selector Background Control
(ItemsControl) StatusBar Background Control
(ItemsControl) TreeView Background Control
(ListBox) ListView Background Control
(ListBoxItem) ComboBoxItem Background Control
(ListBoxItem) ListViewItem Background Control
(MenuBase) ContextMenu Background Control
(MenuBase) Menu Background Control
(MultiSelector) DataGrid Background Control
(Selector) ComboBox Background Control
(Selector) ListBox Background Control
(Selector) MultiSelector Background Control
(Selector) TabControl Background Control
(ToggleButton) CheckBox Background Control
(ToggleButton) RadioButton Background Control
Border Background Border (Self)
TextBlock Background TextBlock (Self)
Rectangle Fill Shape
Ellipse Fill Shape
Path Fill Shape
Polygon Fill Shape
Polyline Fill Shape
Line Fill Shape
HighlightSegment Fill Shape

Background(Fill) 속성은 다음과 같은 상위 객체에서 제공되고 있습니다.

  • Panel
  • Control
  • Border
  • TextBlock
  • Shape

? 속성을 이렇게 자세하게 살펴봐야 하나요?

아닙니다!! 다만 Background 속성을 통해 모든 UI 객체를 확인하는 것이 의미가 있기 때문에 함께 확인해 본 것입니다.

WPF와 객체 개념에 아직은 능숙하지 않더라도 Background 속성이 어느 부모 객체를 통해 받게 된 것 인지를 계속해서 인지하고 부모 객체의 특성을 이해하려는 시도를 해보는 훈련이 중요합니다!!!

또한 이와 같은 분석 방법을 이용해 앞으로 더 다양한 UI 속성(Content, Margin, Padding 등)에 접근하고 이해하는데 있어 중요한 밑거름이 될 것입니다.

15개의 좋아요

아니 저 구하기도 어려운 고전 책을 3권이나 가지고 계시다구요? 대박이네요.

8개의 좋아요

3. Geometry


Overview

Geometry가 WPF에 있어 시각적으로 차지하는 부분은 놀라울 정도로 방대합니다. 왜냐하면 우리가 실행한 WPF의 모든 시각적인 정보가 이것을 통해 표현되고 있기 때문인데요. 그런데 이렇게 깊은 부분까지 이해하고자 노력할 가치가 있을까요, 그렇다면 과연 이것을 제대로 이해하고 잘 활용한다면 WPF를 구사함에 있어 얼마나 도움이 될 수 있을까요?

베일속에 가려져 있는 Geometry 클래스에 대해 자세하게 알아보는 시간을 준비했습니다.

Geometry란 무엇인가?

WPF에서 Vector 기반의 그래픽을 그리기 위한 클래스입니다. 대표적으로 Path 객체를 통해 Geometry 타입의 Data 속성을 통해 아이콘 등의 그래픽을 표현할 수 있습니다.

그리고 Geometry와 비교 대상이 되는 이미지는 그림이 픽셀으로 표현되기 때문에 사이즈가 고정적인 반면, Geometry는 점과 선의 위치를 통해 표현되므로 크기에 구애받지 않고 반응형으로 깨지지 않고 표현할 수 있습니다.

Geometry 형식
M17,8.5L12.25,12.32L17,16V8.5M4.7,18.4L2,16.7V7.7L5,6.7L9.3,10.03L18,2L22,4.5V20L17,22L9.34,14.66L4.7,18.4M5,14L6.86,12.28L5,10.5V14Z

그렇기 때문에 Geometry는 사진 과 같은 Pixcel 기반의 표현에 적합하지 않습니다. 대신 아이콘이나 , 특히 Material 풍의 디자인 요소를 표현하기에 아주 적합하며 다양한 해상도의 디스플레이가 제공되는 현 시점에서 가장 트랜디한 SVG Vector 기반의 표현을 Geometry를 통해 할 수 있는 것입니다.

축소 확대
image image

Path 활용

Geometry는 자기 스스로 표현할 수가 없습니다. 그래서 Geometry를 사용할 수 있는 부모 객체가 필요한데 이게 바로 Path입니다. 우리가 흔히 알고 있는 SVG와 같은 Vector 기반의 값을 사용할 있기 때문에 사용 빈도가 매우 높은 UI 객체 중에 하나 이기도 합니다.

일반적인 사용 방법은 간단합니다.

<Path>
    <Path.Data>
        M17,8.5L12.25,12.32L17,16V8.5M4.7,18.4L2,16.7V7.7L5,6.7L9.3,10.03L18,2L22...
    </Path.Data>
</Path>

또는

<Geometry x:Key="Icon1">M17,8.5L12.25,12.32L17,16V8.5M4.7,18.4L2,16.7V7.7L5,6.7L9.3,10.03L18,2L22...</Geometry>
...
<Path Data="{StaticResource Icon1}"/>

Path를 통해 Geometry를 사용하는 것은 이렇게나 간단합니다.

또 Geometry 사용이 가능한 객체는 Path 외에 몇 개가 더 있습니다.
Path를 포함해 Geometry 사용이 가능한 모든 객체들이 Shape으로부터 상속받고 있습니다.

  • Path
  • Rectangle
  • Ellipse
  • Line
  • Polyline
  • Polygon

사실 Shape를 상속받는 클래스 중에서는 이미 사용해본 객체가 있을 것입니다. 사각형 Rectangle, 원 Ellipse 등 친숙한 도형들이 있는데 이것들도 사실 내부적으로 Geometry를 포함하고 있으며 그 형태가 Rectangle, Ellipse를 통해 정적으로 정해져있고 With, Height를 통해서만 사이즈를 변경할 수 있는 것입니다.

그래서 Path와 Shape를 더 깊이있게 알기 위해서는 Geometry의 세부 계층 구조를 들여다 봐야 합니다.

Geometry 클래스 계층

Geometry 클래스는 총 7개의 파생된 Geometry를 가지고 있습니다.

  • LineGeometry
  • RectangleGeometry
  • EllipseGeometry
  • GeometryGroup
  • CombinedGeometry
  • PathGeometry
  • StreamGeometry

다음은 Geometry를 필두로 한 계층구조 모습입니다.

- Object
  - DispatcherObject (abstract)
    - DependencyObject
      - Freezable (abstract)
        - Animatable
          - Geometry
            - LineGeometry
            - RectangleGeometry
            - EllipseGeometry
            - GeometryGroup
            - CombinedGeometry
            - PathGeometry
            - StreamGeometry

Charles Petzold 中… 위의 클래스 계층도는 이번 장에서 다룰 순서대로 Geometry 파생 클래스들을 정렬해 보여준다. 이 클래스들은 WPF 그래픽이 순수 분석 기하학을 캡슐화하기 위해 기울인 노력을 가장 근접하게 설명해주는 결과물들이다. Geometry 객체는 점(points)과 길이(lengths)로 표현된다. Geometry 객체는 자신을 그리지 않는다. 원하는 브러시(Brush) 프로퍼티와 펜(pen) 프로퍼티로 Geometry 객체를 그리려면 별도의 클래스를(대부분의 경우 Path 클래스를) 사용해야 한다.

Charles Petzold는 점(points)과 길이(lengths)를 통해 모든 그래픽 표현을 그릴 수 있도록 기하학을 캡슐화한 WPF 설계에 대해 위와 같이 묘사하고 있습니다. 그래서 보통 일반적으로는 Geometry를 사용하겠지만 파생된 7개의 다양한 하위 Geometry로 세분화되어 WPF를 표현하고 있다는 언급을 확인할 수 있습니다.

사실 Geometry를 상속받는 하위 객체들은 각각의 이름이 의미하는 것 처럼 사용하는 용도와 방식이 명확하게 정해져 있습니다. EllipseGeometry는 Ellipse와 같은 원을 표현하기 위해 특화된 Geometry이며, Rectangle은 사각형, Line은 선(Border 등에 사용되는데 이 얘기는 또 나중에…)을 그릴 때 쓰이고 있습니다.

그럼 이 중에서 가장 눈에 띄는 EllipseGeometry를 한번 살펴볼까요?

EllipseGeometry

EllipseGeometry는 실제 Ellipse 객체 안에서 동작하고 있는 Geometry입니다.

그리고 실제 Ellipse 객체의 Geometry 관계를 살펴보면 이렇습니다.

Ellipse > EllipseGometry > Geometry

EllipseGeometry는 Center, RadiusX, RadiusY 3개의 프로퍼티 속성을 제공합니다. 이 객체에서 가장 중요한 Center 속성은 원의 가운데를 x축과 y축으로 결정하기 위한 Point Struct 타입입니다.

Center RadiusX RadiusY
Point { x: double, y: double } double double
원의 가운데 지점 X, Y 좌표 센터를 기준으로 X축의 길이 센터를 기준으로 Y축의 길이
Center=“50, 50” RadiusX=“48” RadiusY=“48”

Xaml

<Window x:Class="EllipseGeometryTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:EllipseGeometryTest"
        mc:Ignorable="d"
        Title="MainWindow" SizeToContent="WidthAndHeight">
    <Viewbox Margin="10">
        <Path StrokeThickness="4" 
              Stroke="#222222" 
              Width="100" 
              Height="100" 
              Fill="#353535">
            <Path.Data>
                <EllipseGeometry Center="50, 50" RadiusX="48" RadiusY="48"/>
            </Path.Data>
        </Path> 
    </Viewbox>
</Window>

EllipseGeometry는 실제 점과 선의 값을 노출하지는 않지만 Center, RadiusY, RadiusX 값을 통해 내부적으로 Geometry를 그려 제공하게 됩니다.

image

나머지 6개의 Geometry는 천천히 연재됩니다!

17개의 좋아요

항상 이 글을 들여다 보고 있습니다. 업데이트될 내용들이 너무 기대되네요

8개의 좋아요

@helpandplay 좋게 봐주셔서 감사합니다. :smile:
앞으로도 잘 부탁드립니다~!

2개의 좋아요

4. Blend


TBD…
(언제 작성할련지… 하트 3개나 받았는데…)
(Figma와 내용을 연관 지어서 해볼 까 고민중…)

9개의 좋아요

WPF 4대 요소 (짧게) 살펴보기


WPF는 기술적으로 분명 UWP, Xamarin, Win UI3, MAUI, Uno Platform, Avalonia, OpenSilver와 같은 다양한 플랫폼, 크로스 플랫폼의 시작 점이기도 합니다.

먼저 항목을 살펴보면…

System.Xaml.dll
PresentationCore.dll
PresentationFramework.dll
WindowsBase.dll

이렇게 4대 요소들이 존재합니다. 잘 알고 계시는 분들도 분명 있으시겠지만 조금은 생소한 분들도 계시리라 생각됩니다. 그래서 이번에는 이 4대 요소들에 대해 한번 알아보고 각각의 역할을 이해하고자 합니다.

참고로 일반 클래스 라이브러리에서는 이 4대 요소가 기본으로 참조 추가되어 있지 않기 때문에 WPF를 사용할 수 없습니다. 단 수동으로 참조 추가 시 사용이 가능합니다. (닷넷 프레임워크 기준…)

그럼 가장 먼저 PresentationFramework.dll을 살펴보도록 하겠습니다.

1. PresentationFramework


이 어셈블리에 포함된 대표적인 내용은 다음과 같습니다.

System.Windows.Window
System.Windows.FrameworkElement
System.Windows.Controls.UserControl
System.Windows.Controls.TextBox
System.Windows.Controls.ListBox
System.Windows.Controls.Primitives.ToggleButton

거의 모든 UI 객체들이 바로 이 PresentationFramework.dll 어셈블리 안에 포함되어 있는 것을 확인할 수 있습니다.

그리고 여기서 한 가지 재미있는 사실을 알 수 있습니다. 어셈블리 명과 네임스페이스는 전혀 다를 수 있다는 사실인데요. 닷넷은 어셈블리와 네임스페이스의 네이밍을 동일하게 맞추어 설계하지 않는 점을 처음 알았을 때 아주 큰 충격이었습니다. 그리고 거의 모든 UI 객체를 자식으로 두고 있는 FrameworkElement 클래스 또한 이 어셈블리에 포함되어있습니다. 그렇다면 FrameworkElement 한 단계 위인 UIElement도 이 어셈블리 안에 포함되어 있을까요?

아닙니다.

2. PresentationCore


PresentationFramework 어셈블리 이름과 상당히 유사한 이 DLL은 Core라는 이름에서 느낌을 알 수 있듯이 좀 더 Base 적인 측면을 담당하는 어셈블리 입니다.

이 어셈블리에 포함된 대표적인 내용은 다음과 같습니다.

System.Windows.UIElement
System.Windows.Visual
System.Windows.Animatable
System.Windows.Media.Geometry
System.Windows.Media.PathGeometry

PresentationFramework를 이루는 상대적으로 평범한 UI 객체들과는 달리 Geometry를 필두로 한 근본 있는 그래픽 객체들이 등장합니다. 실질적으로 WPF 표현을 담당하는 Base 객체들의 집합이라 생각하면 됩니다.

3. WindowsBase


PresentationCore를 지나 좀 더 깊은 곳에 도달했습니다.

과연 어떤 내용이 포함되어 있을까요?

DependencyObject
DispatcherObject
DependencyProperty

이 어셈블리는 WPF의 가장 하단에 존재하는 DLL이며 DependencyObject, DependencyPropery와 같은 WPF의 가장 상위 클래스를 포함하고 있습니다.

그렇다면 지금까지 확인한 어셈블리간 종속 계층 구조를 한번 유추해볼 수도 있겠습니다.
굳이 표현해 보자면 그 관계는 다음과도 같습니다.

WindowsBase
    PresentationCore
        PresentationFramework

마지막 남은 어셈블리도 살펴볼까요?

4. System.Xaml


이 어셈블리는 Xaml 문법과 변환 등에 대한 모든 처리를 담당하는 DLL입니다. 사실 WPF를 오래 접해왔다 하더라도 이 어셈블리 안에 포함된 내용은 매우 생소할 수도 있습니다.

대표적으로 확인해볼 수 있는 부분은 다음과 같습니다.

System.Windows.Markup.IComponentConnector

이 부분은 실제 Window, UserControl과 같은 Xaml 파일을 비주얼스튜디오를 통해 생성할 경우 InitializeComponent 부분을 통해 Partial로 생성되는 객체 쪽에서 확인할 수 있습니다.

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
    }
}
public partial class MainWindow 
    : System.Windows.Window, System.Windows.Markup.IComponentConnector 
{
    ...
}

이처럼 Xaml에 관련된 모든 인터페이스, 문법 검사, 변환 등의 처리를 담당하는 기능을 포함하고 있습니다.

System.xaml을 끝으로…
WPF 4대 요소들을 짧고 간단하게 살펴보았습니다. 어떠셨나요?

제가 준비한 내용은 여기까지 입니다.
읽어주셔서 감사합니다. :smile:

22개의 좋아요

감사합니다 WPF를 처음 배워보려고 하는데 너무 도움이 될 것 같아요 찬찬히 읽어볼게요 감사합니다!!

2개의 좋아요

DependencyProperty

안녕하세요. 이재웅입니다.
3일 마다 연재 하려했던 제 계획은 3주로 자연스럽게 변경되었습니다. :joy:


DependencyProperty는 의미처럼 의존프로퍼티를 말하며 WPF의 특성을 가장 잘 설명해주는 중요한 요소 중 하나입니다. 저는 이것을 줄여서 DP라고 명칭하도록 하겠습니다.

DP는 일반 속성을 static으로 등록하여 사용하는 것에 목적을 두고 있습니다. 그럼 우리는 무엇을 어디에 등록해야 한다는 것일까요?

먼저 등록 대상은 바인딩이 가능한 프로퍼티 속성입니다. 예를 들어 ToggleButton에는 IsChecked 속성이 존재하며 MarkupExtension 특성을 사용해서 Binding 사용이 가능합니다.

아래처럼 말이죠.

IsChecked={Binding IsUsed}

그럼 IsChecked 속성은 어떻게 선언되어있을까요?

단, 이 소스코드는 ToggleButton에 존재하는 IsChecked 속성이지만 DP 설명에 이해를 돕기 위해 불필요한 로직이 생략되었으므로 실제 원본과는 다를 수 있습니다. 원본 보기

public static readonly DependencyProperty IsCheckedProperty =
    DependencyProperty.Register(
        "IsChecked",
        typeof(bool?),
        typeof(ToggleButton),
        new FrameworkPropertyMetadata(...,
            new PropertyChangedCallback(OnIsCheckedChanged)));

public bool? IsChecked
{
    get => ...
    set => ...
}

private static void OnIsCheckedChanged(DependencyObject d,    
    DependencyPropertyChangedEventArgs e)
{
    ToggleButton button = (ToggleButton)d;
    bool? oldValue = (bool?) e.OldValue;
    bool? newValue = (bool?) e.NewValue;
    ...
}

IsChecked DP가 선언된 구조를 살펴보면 크게 3개의 요소로 구성되어 있습니다.

  • static DependencyProperty (Register 등록)
  • bool? IsChecked (Proeprty 속성)
  • static OnIsCheckedChanged (콜백 메서드)

여기서 콜백 메서드는 사용 목적에 따라 구현하지 않아도 되는 선택사항입니다.

Register 등록


DependencyProperty 클래스는 sealed 접근 제한자를 통해 봉인되어 있으며 생성자(cotr)도 마찬가지로 private으로 제한되어 있기 때문에 Register 메서드 내부에서만 DP 인스턴스 생성이 가능합니다. 이런 제약 사항 때문에 DP를 생성하는 방법은 DependencyProperty.Register (or Attached) 호출이 외길입니다. 그리고 생성자(cotr) 과정에서는 RegisteredPropertyList라는 static 컬렉션 객체에 DP 자신을 추가하도록 되어 있기 때문에 자연스럽게 모든 DP를 수집해서 관리할 수 있도록 로직이 구현되어 있습니다.

DP 생성자(cotr)에서 RegisteredPropertyList 항목에 this가 추가되는 모습…

private DependencyProperty(...)
{
    RegisteredPropertyList.Add(this);
}

DP는 여기에 모여집니다.

internal static ItemStructList<DependencyProperty> RegisteredPropertyList;

그래서 만약 동일한 객체에 중복해서 DP를 선언할 경우 아래와 같은 익숙한 에러 메시지가 발생하는 것입니다. :smile:

ArgumentException: 'ToggleButton’이(가) ‘IsChecked’ 속성을 이미 등록했습니다.

Property 선언


수 없이 사용했지만 애써 외면했던 DP 속성 선언 부분 입니다. 언뜻 보면 클래스 안에 선언된 public 속성으로 볼 수 있겠지만 내부 get, set을 보면 실제 사용되는 field는 표면적으로 존재하지 않습니다. 대신 GetValue, SetValue를 사용하죠.

public bool IsChecked
{
    get => (bool)GetValue(ContentNameProperty);
    set => SetValue(ContentNameProperty, value);
}
  • GetValue();
  • SetValue();

이 메서드들은 DependencyObject를 통해 사용할 수 있는데 모든 WPF UI 클래스가 DependencyObject를 상속받기 때문에 어디서든 DP를 등록하고 프로퍼티를 구성할 수 있는 것입니다. 이제 조금 씩 WindowsBase에 있는 Dependency 객체들의 역활들이 드러나고 있는 느낌이죠.

그럼 도대체 Field는 어디에 있는 것인가?

사실 DP를 등록할 때 이미 내부적으로 Field를 관리하기 위한 준비가 다 되어있었습니다. 그래서 결론은 DependencyProperty 내부적으로 (static) Field를 관리하여 GetValue/SetValue을 통해 Field를 사용할 수 있도록 제공하고 그 값을 프로퍼티를 통해 사용하는 것입니다.

(거의 다 왔습니다.)

의존성 속성


DP는 DependencyProperty와 DependencyObject, 내부적인 로직들에 의해 모든 값이 static으로 관리됩니다. 그렇다면 DP는 왜 값을 static으로 관리하는 것일가요?

모든 WPF 컨트롤은 아래처럼 FontSize DP 속성을 가지고 있습니다.

<Window FontSize="15">
    <StackPanel FontSize="15">
        <Button FontSize="15"/>
        <TextBlock FontSize="15"/>
        <MyCustomControl FontSize="15"/>
    </Grid>
</Window>

여기서 FontSize DP는 컨트롤마다 각각 선언되었기 때문에 총 5개의 값이 내부적으로 (컬렉션 형태로) 관리되게 됩니다.

  • Window.FontSizeProperty
  • StackPanel.FontSizeProperty
  • Button.FontSizeProperty
  • TextBlock.FontSizeProperty
  • MyCustomControl.FontSizeProperty

여기까지는 일반적인 구조이겠지만 여기서 만약 Window를 제외한 나머지 컨트롤에서 FontSize를 제거하면 어떻게 될까요?

<Window FontSize="15">
    <StackPanel>
        <Button/>
        <TextBlock/>
        <MyCustomControl/>
    </Grid>
</Window>

이렇게 될 경우 내부적으로 가지고 있는 FontSize 값은 기존 5개가 아닌 단 하나의 Window FontSize만 가지게 됩니다. 그렇다면 나머지 4개의 컨트롤은 FontSize 값이 Empty 상태일까요?

맞기도 하고 아니기도 합니다. DP는 자신의 값이 없을 경우 VisualTree 계층적 구조 관점에서 자신보다 상위에 위치한 가장 가까운 직계 부모 컨트롤의 DP 값을 물려받게 됩니다. 그러므로 각각의 값은 존재하지 않지만 부모의 값을 참조 받기 때문에 static Field를 넘겨받은 DP 프로퍼티는 감쪽같이 값을 따로 갖고 있는 것 처럼 시치미를 뚝 떼고 있죠. 이러한 구조 덕분에 메모리 관리에 있어서도 효율을 기대할 수 있으며 성능 상으로도 좋은 영향을 주고 있으며 이것이 바로 의존성 프로퍼티의 핵심 개념인 것입니다.

그래서 WPF의 모든 컨트롤은 DataContext DP 속성을 가지고 있기 때문에 자신의 DataContext를 변경하지 않는 한 가까운 상위 부모 DataContext를 물려받기 때문에 언제 어디서나 손쉽게 상하 계층적인 Binding 구조를 가질 수 있게 된 것입니다.

제가 준비한 내용은 여기까지 입니다.
읽어주셔서 감사합니다. :smile:

17개의 좋아요

Blend 기대되어요 :slightly_smiling_face:

2개의 좋아요

@Teno 응원 감사합니다. :smile: :smile:

3개의 좋아요

FrameworkElement

처음 접한 기능인데 쓰고 보니
초심자들에게 배지를 제공하기 위한 배려이신가 싶네요

5개의 좋아요

@BitAce 오타까지 잡아주시니 일석이조네요. :smile:

3개의 좋아요

잘보고 있어요~ 다음도 기대됩니다

3개의 좋아요

3. WindowsBase 부분에 DependencyPropery 이부분 오타가 있습니다

Register 등록 부분에 생성자(cotr) 이부분 오타가 있습니다

2개의 좋아요

안녕하세요 WPF, C#의 생산성과 코드의 간결성에 빠져서 WPF를 기반으로 작업하는 직장인 입니다.
xaml 부터 WPF 애플리케이션의 생명주기 등 정말 많은 것이 궁금했는데 가려운 곳을 긁어주는 글이네요.
앞으로의 글 기대하고 하나도 빠짐없이 정독하겠습니다. 감사합니다 :slight_smile:

5개의 좋아요