Introduction

VisualStudio 혹은 Expression Blend에서 동작하는 WPF DesignTime Extension을 개발하려고할때 현재 작업중인 Assembly에 대한 정보가 필요할 때가 있습니다. Runtime상에서는 System.Reflection.Assembly.GetEntryAssembly를 이용해 간단하게 현재 실행중인 Assembly의 정보를 가져올 수 있지만, DesignTime에서는 EntryAssembly를 가져올 수 없기 때문에 문제가 발생합니다. 이번시간에는 DesignTime에서 현재 작업중인 Assembly에 대한 정보를 가져오는 방법에 대해 소개합니다.

GetEntryAssembly



신고

Introduction

Application을 개발할때 목록에 항목이 추가되거나, 제거 되었을때와 같은 동적인 변경내용을 수신기에 알리기 위해 일반적으로 목록객체에 INotifyCollectionChanged를 상속받아 구현하거나  INotifyCollectionChanged가 구현된 ObservableCollection<T>등을 사용합니다. 동적인 목록의 변경에 대하여 즉각 반응하기 때문에 매우 유용하게 사용될 수 있지만, 많은 양의 목록이 추가/제거 될경우 매번 목록의 변동이 있을대마다 CollectionChanged이벤트가 발생하기 때문에 퍼포먼스에 크게 영향을 미칠 수 있습니다. 이번시간에는 많은 양의 아이템을 INotifyCollectionChanged가 구현된 목록에 추가/제거할때 CollectionChanged가 발생하는것을 방지하고 처리가 모두 끝난뒤에 호출되도록 하는 방법에 대해 소개합니다.

List<T> VS ObservableCollection<T>

위 동영상은 INotifyCollectionChanged가 정의되지 않은 List<T>와 INotifyCollectionChanged가 정의된 ObservableCollection<T>를 이용해 ListBox의 Item목록을 변경 했을때의 퍼모먼스 비교입니다. 데모에서는 동일한 갯수의 목록(30만 건)을 목록에 추가하고 ListBox를 갱신하는 과정까지의 시간을 측정했습니다.

결과는 INotifyCollectionChanged를 정의하지 않은 List<T>가 INotifyCollectionChanged가 정의된 ObservableCollection<T>보다 10배정도 빠른 결과를 나타내고 있습니다. 이유는 ObservableCollection에서 항목이 추가될때마다 CollectionChanged이벤트가 발생하고 이를 수신하는 ListBox에서는 매 항목이 추가될때마다 View를 갱신하고 있지만, List<T>는 항목이 추가되더라도 View를 갱신하지 않기 때문입니다. List<T>는 View에 영향을 미치지않기 때문에 목록추가가 완료된뒤 View의 ItemsControl에 Refresh메서드를 호출하는것으로 View를 갱신할 수 있습니다.

하지만, List<T>를 이용했을때가 항상 성능이 뛰어난 것은 아닙니다. 위에서 언급했듯이 List<T>는 INotifyCollectionChanged가 구현되지 않았기 때문에 목록에 아이템을 추가할때마다 직접 View의 ItemsControl을 Refresh 메서드를 호출해줘야하는데, ItemsControl의 Refresh메서드를 호출하게 되면 View를 다시 그리기 때문에 이는 더큰 부작용을 일으킬수 있습니다. 

Create SuspendObservableCollection

가장 좋은 방법은 작은 건의 항목이 변경될때에는 INotifyCollectionChanged를 이용해 해당 항목이 추가/제거 되었음을 알리고, 많은 항목이 변경될때에는 CollectionChanged이벤트 발생을 일시적으로 중단하고, 모든 항목의 변경이 완료되었을때 CollectionChanged를 발생시키는 방법입니다. 하지만, ObservableCollection의 경우 CollectionChanged 이벤트에 대한 중지여부를 직접적으로는 제어 할 수 없기 때문에 ObservableCollection을 상속받아 CollectionChanged이벤트 발생여부를 제어 할 수 있는 SuspendObserableCollection을 구현할 수 있습니다. 아래는 SuspendObservableCollection의 소스코드입니다.


ObservableCollection의 OnCollectionChanged에서 내부적으로 발생시키는 CollectionChanged이벤트 호출을 제어하기 위해 IsSuspend 속성을 구현해 CollectionChanged 이벤트의 호출을 제어 했습니다. 실제 적용될 때에는 다음과 같이 사용할 수 있습니다.


이제 한두건 정도의 간단한 목록 변경에서는 INotifyCollectionChanged를 이용해 변경을 알리고 많은건의 목록변경에서는 IsSuspend와 Refresh메서드를 이용해 CollectionChanged이벤트를 제어 할 수 있습니다. 아래 동영상은 위에서 실시한 퍼포먼스 비교를SuspendObservalbleCollection에도 적용한 내용입니다.

Reflection for INotifyCollectionChanged

추가로, Reflection을 이용해 SuspendObservableCollection을 구현하지 않고 CollectionChnaged이벤트에 대한 제어를 수행할수 있습니다. 아래 코드는 Reflection을 이용한 CollectionChanged이벤트 제어를  Extension Method로 구현한 코드는 입니다.


CollectionChanged를 제어하기 위해서 PasueNotifyCollectionChanged메서드와 ResumeNotifyCollectionChaged를 이용합니다. 퍼포먼스는 SuspendObservableCollection보다 다소 느리지만, Type을 수정할 수 없는 상황이거나, INotifyCollectionChanged을 구현한 다른 Type의 Collection에서 유용하게 사용할 수 있습니다.

 

신고

Introduction

MVVM 패턴등을 이용해 Application을 개발할때 View에서 호출되는 RoutedEvent를 처리하는데 있어 몇가지 이슈가 발생하게 됩니다. 일반적인 방법으로 RoutedEvent를 Command로 연결하기 위해서는 View와 ViewModel간의 종속적인관계가 유지 View와 ViewModel의 분리를 원칙으로하는 MVVM패턴을 이용할 떄 이 방법은 좋은 방법은 아닙니다. 이번시간에 소개할 내용은 이러한 MVVM 패턴을 이용해 RoutedEvent를 처리해야 하는 상황에서 View와 ViewModel간의 연결 없이 RoutedEvent를 Command로 연결할 수 있는 방법에 대해 소개합니다.

CommandBinding MarkupExtension

이번시간에는 MarkupExtension을 이용해 RoutedEvent와 Command를 연결할 수 있는 CommandBinding MarkupExtension을 구현합니다. 구현 완료시 아래 코드와 같이 XAML을 이용해 RoutedEvent를 쉽게 Command에 연결 할 수 있습니다.


CommandBinding MarkupExtension은 크게 2가지 부분으로 나뉩니다. 첫번째 부분은 CommandBiding MarkupExtension을 정의하는 MarkupExtension과 CommandBinding객체를 이용해 RoutedEvent와 Command를 연결시켜주는 역할을 하는 CommandBinding Attached Property부분입니다.

먼저 CommandBinding MarkupExtension은 아래와 같이 아주 단순한 구조로 Command와 RoutedEvent에 대한 정보를 저장할수 있는 형태를 갖습니다.


그리고 Attached Property에서는 입력받은 CommandBinding으로부터 Target의 RoutedEvent를 처리합니다. CommandBinding Property에서는 CommandBindingManager를 통해 CommandBinding MarkupExtension과 Target을 관리하고 Target의 ViewModel(DataContext)이 갱신되었을 때 CommandBinding에서 지정한 RoutedEvent를 Command에 연결하는 과정을 수행합니다.

이상 Reflection과 Attached Property, MarkupExtension을 이용해 CommandBinding을 구현하는 방법에 대해 소개해 드렸습니다.
신고

Introduction

WPF에서는 기본적으로 TextBlock나 FlowDocument 와 같은 텍스트 관련 컨트롤을 제공하고 있습니다. 이와 같은 컨트롤들은 텍스트와 관련된 일련의 과정을 개발자가 쉽게 사용할 수 있도록 고수준에서 제공되지만, 디테일한 텍스트조작을 지원하지 않습니다. 

TextBlock과 같은 고수준 컨트롤보다 좀더 디테일한 조작이 가능한 FormattedText를 제공하고 있지만, FormattedText는 WPF의 텍스트 기능 관점에서 텍스트를 그래픽 요소로 처리하기 때문에 DrawingContext의 DrawingText를 통해 텍스트를 렌더링 합니다. FormattedText에서는 텍스트의 Geometry를 생성할 수 있기 때문에 여러가지 효과(윤곽선, 모양등)를 적용하는데 용이하지만, Geometry를 생성하는데 있어 많은 처리시간이 소요 된다는 단점이 있습니다.



이번시간에 소개할 내용은 위 동영상과 같이 GlyphRun을 이용해 텍스트를 저수준에서 조작하여 빠르게 자간을 조절할 수 있는 컨트롤을 개발하는 방법에 대해 소개합니다. 

What is the GlyphRun?

GlyphRun은 단일 크기의 단일 글꼴로 구성되어 있고 단일 렌더링 스타일이 적용된 문자 모양을 나타내는 객체로, WPF의 텍스트 렌더링 계층에서 가장 하위 수준에 속하는 객체 입니다. GlyphRun은 텍스트에 대한 저수준 제어가 가능하기 때문에 각각의 문자에 대한 크기나, 위치 등을 Geometry 조작없이 제어할 수 있습니다. 하지만, TextBlock나 FormattedText와 같은 고수준 객체에서 지원하는 텍스트 기능(TextAlignment, TextWraping, TextTriming 등)을 제공하지 않기 때문에 직접 구현해야하는 단점이 있습니다.

Fastest Condensed Textblock

이번시간에는 입력된 텍스트의 자간을 저수준에서 빠르게 조작하여 출력해주는 컨트롤을 제작합니다. 이번시간에 사용하는 GlyphRun은 TextAlignment나 TextWarping과 같은 기능은 기본적으로 제공하지 않기 때문에 단일 라인 텍스트 조작을 목표로합니다.

아래 코드는 Text, FontFamily, FontSize, FontStretch 등을 DependencyProperty로 정의하고, OnPropertyChanged가 발생할경우 GlyphRun을 생성하는 코드입니다.


위 코드에서 중요한 부분은 Typeface로부터 GlyphTypeface를 생성한다음 GlyphTypeface로부터 문자 Glyph 정보를를 추출하는 과정에 있습니다. 그리고 AdvanceWidths를 통해 각문자가 할당 받는 크기를 FontSize와 FontStretch에 맞게 재조정한뒤 이를 기반으로 GlyphRun를 생성합니다.


생성된 GlyphRun은 OnRender에서 DrawGlyphRun을 이용해 화면에 출력하고, ArrangeOverride에서 생성된 텍스트의 크기를 반환합니다. 이것으로 간단하게 GlyphRun을 이용해서 자간조절이 가능한 TextBlock을 생성해 보았습니다. GlyphRun을 이용해 자간 조절 뿐만아니라, 행간, 정렬등의 기능을 구현해보시면 이번 시간 내용을 이해하시는데 많은 도움이 될것 같습니다.

신고

Introduction

이번 시간에는 전역 후킹(Global Hooking)에 대해서 알아보도록 하겠습니다.

C/C++ 에서는 Win32 API를 이용하여 손쉽게 전역 후킹을 할 수 있습니다. 물론 .NET 에서도 Win32 API의 DLL 파일을 Import 하여 후킹이 가능합니다. 하지만 약간의 문제가 있습니다. 지역 후킹이 아닌 전역 후킹에서는 CallBack Procedure 를 DLL 파일로 만들어야 한다는 것이지요. 이런 이유로 .NET 에서는 전역 후킹이 안된다는 이야기가 많이 있습니다. 저 역시 초기에 이런 문제에 부딪혔습니다.


Microsoft 에서 전역 후킹에 대해서 찾아보니 심지어 이런 내용이 있었습니다.

물론 이 이야기가 맞습니다. .NET 에서 전역 후킹(Global Hooking)을 하려면, 이 DLL 을 C/C++ 과 같이 네이트브 코드로 만들고, 이것을 호출하는 방법 밖엔 없습니다.

하지만, 마우스와 키보드에 대한 전역 후킹은 .NET 에서 가능합니다. 다시 말하자면, 마우스와 키보드 이외에는 전역후킹이 불가능 하다는 이야기가 되겠습니다 :) 이 방식은 WH_MOUSE_LL, WH_KEYBOARD_LL 만 지원한다는 이야기입니다.

즉, WH_MOUSE 나 WH_KEYBOARD 는 지원하지 않습니다. 저 역시 예전엔 이 idHook 을 사용하고 왜 안될까 고민했던 것 같습니다.
코드는 다음과 같이 매우 간단합니다. 가독성을 위해서 partial 로 나누어 놓았습니다. 오해 없으시기 바랍니다.


Example


[Win32 API Wrapper]


[구조체 / idHook]


[Start Hook]



 



 

신고