DependencyProperty Value Inheritance

프로그래밍 2009.08.26 12:00 Posted by 아일레프

의존 프로퍼티에 대해서 하나 더 포스팅 합니다. 사실 이 포스팅을 하면서 두려운 마음이 많이 드는데, 제가 WPF를 만든 사람도 아닐 뿐 더러 아래 내용은 어디까지나 저 개인의 추측과 실험으로 나온 결과이기 때문입니다. 무책임 한 말일 수 있지만, 절대 아래 내용을 완전히 신뢰하지 마시기 바랍니다. 옳고 그름을 스스로 판단해주시고, 혹시 틀린 부분이 있나, 더 개선될 수 있는 부분이 있다면 댓글 남겨주시면 감사드리겠습니다. 그리고 아래의 내용은 결론을 얻기 까지 제 사고의 흐름입니다. 너무 길어 보기 싫으신 분은 가장 아래의 결론 부분만 읽어 주세요.

일단 의존 프로퍼티에 대한 정의를 한번 하고 넘어가겠습니다. 제가 가장 좋아하는 의존 프로퍼티에 대한 정의는 페졸드 아저씨의 정의입니다.

"It is a property that depend on number of other properties and outside influences"

다른 환경에 의해 영향을 받는(또는 의존하는) Property라는 것이죠. 이런 의존 프로퍼티에 대한 개념을 잡아 주기 위해 대부분의 책들이 Property Inheritance를 예로 듭니다. 오늘의 이슈는 바로 이녀석입니다.

 위와 같이 VisualTree가 구성되어있을 때 Window의 FontSize를 30으로 만들면 Button의 FontSize도 자동으로 30으로 설정되죠. 즉 Button의 FontSize라는 Property는 Button의 부모인 Window의 FontSize Property에 영향을 받고 있습니다. 그런데 이게 어떻게 가능할까요?

   

Property Inheritance를 위한 부모와 자식간의 관계는 어떻게 설정되는 것일까?

위와 같은 그림에서 Window와 Button은 직접 부모, 자식 관계로 이어져 있을 까요? 답은 그럴 수도 있고, 아닐 수도 있습니다. 위 Window와 Button은 VisualTree내에서 직접적인 부모, 자식 관계가 아닙니다. 가장 최소화된 VisualTree라 하더라도 Window->ContentPresenter->Button으로서 직접적인 부모, 자식으로 이어지지 않습니다. 하지만 Logical Tree내에서는 직접적인 부모, 자식간의 관계로 이루어져 있습니다. 조금 만 더 깊게 들어가보면 VisualTree는 Window의 Template과 ContentTemplate에 의해 영향을 받게 된다는 것을 알 수 있습니다. 그렇다면 LogicalTree는 어떻게 구성되는 걸까요? 질문 1. LogicalTree는 단지 VisualTree를 단순화한 Tree일까요?

여기서 알아야할 중요한 사실이 있는데, 특정 Control A의 Logical Tree내의 자손이 Control A의 VisualTree내의 자손이 아닐 수도 있다는 사실입니다. 이것을 가장 크게 보여주는 예가 바로 Grid입니다. Grid의 LogicalTree내에는 Grid의 Child로 지정된 UIElement들 뿐 아니라 Visual객체가 아닌 RowDefinition, ColumnDefinition도 존재하고 있습니다. 질문 1의 해답. LogicalTree는 VisualTree를 단순화한 Tree가 아니다. LogicalTree와 VisualTree를 별개로 보는 것이 오히려 타당하다.

   

질문 2. VisualTree는 Template에 의해 결정된다고 말한 바 있습니다. 그렇다면 LogicalTree는 어떻게 구성되는 걸까요?

질문 3. Property Inheritance의 혜택은 Visual Tree를 통해서 얻어 질 수 있을까요, 아니면 Logical Tree로 얻을 수 있을 까요? 그게 아니면 둘 다일까요?

위 질문들의 해답은 Of logical and visual trees in WPF를 보면 얻을 수 있습니다. 결론만 말하겠습니다.

질문 2의 해답 : LogicalTree는 Template이 아닌 명시적으로 구현해 주어야 합니다. FrameworkElement를 상속받은 객체는 AddLogicalChildren이라는 메소드를 가지는데 이 메소드를 통해서 특정 객체를 자신의 LogicalChildren으로 넣을 수 있고, LogicalChild의 모든 이점을 얻을 수 있습니다. 그런데 이렇게 AddLogicalChildren을 이용해 넣은 후에도 LogicalTreeHelper를 통해 검색해도 해당 객체가 보이지 않을 수 있는데, 이것은 명시적으로 LogicalChildren을 override해주지 않았기 때문입니다.

  1. AddLogicalChildren을 통해 LogicalChild에 추가하고 싶은 객체를 추가한다.(필수)
  2. LogicalChildren을 override해 LogicalTreeHelper를 통해 해당 객체가 검색될 수 있도록 한다.(optional)

질문 3의 해답 : VisualTree, LogicalTree 모든 Tree를 통해서 Property Inheritance의 혜택을 얻을 수 있습니다. 단 해당 member가 LogicalTree에도 속해있고, VisualTree에도 속해있을 경우 LogicalTree의 Parent의 Property가 Child에게 Inherit됩니다.

이는 Of logical and visual trees in WPF에도 나와 있는 간단한 실험을 통해 확인 할 수 있습니다.

위와 같은 상황에서 ContentControl의 Content인 TextBlock은 ContentControl의 Logical Child이며, Label의 Visual Child이기도 합니다. 따라서 TextBlock의 FontSize는 40이 됩니다. 만약 ContentControl의 FontSize를 40으로 설정하지 않았다면 TextBlock의 FontSize는 20이 되겠죠.

   

그럼 실제 Property Inheritance의 구현을 유추해봅시다. (아래의 내용은 실제 WPF가 의존 Property를 설정하는 방식이 절대 아닙니다.)

Public double FontSize
{
  Get{ return (doble)GetValue(FontSizeProperty);}
  Set{ SetValue(FontSizeProperty, value);}
}

이는 곧 위 코드의 SetValue의 구현을 유추해본다는 의미입니다.(오직 PropertyInheritance만 지원하는) 다음과 같은 논리라면 가능할 것 같습니다.

Private void SetValue(DependencyProperty dependencyProperty, value)
{
    1.
value를 "dependencyProperty value Dictionary에 넣는다." (무슨 말인지 모르시는 분은 이전 포스팅 왜 Dependency Property는 public static readonly로 선언하는가?를 읽어 보시기 바랍니다.) 

    2.
dependencyProperty가 Inheritance를 허용하지 않는다면 메소드를 빠져나온다. :: 이는 해당 DependencyProperty의 metadata를 통해 알 수 있습니다.

    3.
VisualTree상의 자기 자식들이 해당 dependencyProperty를 소유하고 있는가? 
       
만약 해당 dependencyProperty를 소유하고 있으며, 해당 Property의 value가 명시적으로 설정되지 않았다면(Property Inheritance보다 우선순위가 높은 환경에 의해 설정되지 않았다면) 자식의 Property값을 변경한다. 

    4.
LogicalTree상의 자기 자식들에 대해 3번과 동일한 논리를 수행한다.
}

위 작업으로 앞서 알아본 요구사항을 만족할 수 있습니다. 여기서 중요한 것은 바로 3번과 4번의 다음 질문입니다. "자기 자식들이 해당 dependencyProperty를 소유하고 있는가?" 지금 당신이 생각하는 그것이 맞습니다. 예, 바로 Owner의 개념입니다. DependencyProperty 그 자체는 value와 관계 없이 metadata로 이해되거나 value와 metadata를 얻을 수 있는 특정 key로 이해되어야 한다고 이전 포스팅에 말한 바 있습니다. 그렇기에 Owner의 개념이 필요합니다. 우리가 DependencyProperty를 register메소드를 통해 "등록"할때 지정하는 owner가 바로 그것입니다.

위 그림을 다시 보겠습니다. Window의 FontSize가 30으로 변경되었습니다. 이 때 FontSize에 대응되는 DependencyProperty는 곧 Control.FontSizeProperty이며 owner가 Control입니다. Button은 Control을 상속받았기에 Button도 곧 FontSizeProperty의 owner입니다. 따라서 Property Inheritance가 성립되는 것입니다.

    

자 그러면 다음과 같은 것을 해봅시다.

Control이 아닌 FrameworkElement라를 상속 받는 녀석을 만들고, 이 녀석이 Window의 FontSize Property를 상속받게 하는 것입니다.

Window의 FontSize가 상속된다면 testObj의 FontSize도 30이 되겠죠. 그런데 PropertyInheritanceTest는 FontSizeProperty의 Owner가 아닙니다. 어떻게 하면 될까요? Bravo! 맞습니다. 당신은 멋쟁이! PropertyInheritanceTest를 아래와 같이 변경하면 됩니다.

 

위와 같이 한 후 breakPoint를 사용해 PropertyInheritanceTest의 FontSize를 확인해보세요, 분명 30이라는 값이 들어있을 겁니다.

그런데 끝이 아닙니다. 만약 PropertyInheritanceTest가 FrameworkElement가 아닌 DependencyObject를 상속받고 있다면 Property Inheritance상속이 일어나지 않습니다. 왜 이렇게 되는지는 잘 모르겠습니다. ㅜ.ㅜ

   

Anyway, 결론입니다.

1. 의존 속성에 의한 상속을 지원받고 싶다면 Logical Tree, 또는 Visual Tree상의 관계가 맺어 져야 한다.
2. 부모와 자식은 반드시 FrameworkElement, 혹은 FrameworkContentElement를 상속 받은 객체여야 한다.
3. 자식이 상속받고 싶은 DependencyProperty의 Owner가 아니라면 AddOwner를 통해 자신을 해당 DependencyProperty의 소유자로 등록한다.

그리고 하나 더, 위 사항 외에 다른 케이스가 있는데 이는 What's an inheritance context? 라는 글에서 확인할 수 있습니다. 그리고 Artificial Inheritance Contexts in WPF는 이를 이용한 간단한 예입니다.

이 다른 케이스란 것은 Freezable 객체를 상속받은 녀석의 특이성에서 오는 것입니다. 너무 머리가 아퍼 명확하게 알지는 못했지만 다음과 같은 놀라운 케이스가 성립한다는 사실입니다. 보세요, SolidColorBrush는 분명 Button의 LogicalTree상의 자손도 아니고, VisualTree상의 자손도 아닌데 Button의 DataContext 객체를 사용하고 있습니다. 재미 있죠?

  <Button DataContext="Red">
    
<Button.Background>
        
<SolidColorBrush Color="{Binding}"/>
    
</Button.Background>
 
</Button>

   

신고

나쁜 사마리아인들 - 10점장하준 지음, 이순희 옮김/부키



   Intro

존던이 그날 말했다.

--- 바닷물에 바닷가의 모래가 쓸려가면 대륙의 크기는 그만큼 작아집니다. 오늘 한명이 죽으면 인류의 크기는 그만큼 줄어듭니다. 따라서 어떤 사람의 죽음도 나를 줄어들게 합니다. 나는 인류속에 포함되어있기 때문입니다. 그러니 누구를 위하여 종이 울리나 묻지 마세요. 종은 당신을 위해 울립니다.

그것은 예전의 경제학 수업시간으로 거슬러 올라간다. 그는 칠판에 다음과 같은 표를 그린후에 다음 중 하나의 의사 선택을 하라고 했다.


  1. 의사 선택의 표

  

선택 1

선택 2

선택 3

선택 4

A 집단

4

10

6

7

B 집단

4

2

4

3

 위 표에서 각 숫자는 개개의 선택을 했을 때 각 집단이 얻을 수 있는 이득을 수치화 한 것이다. 만약 당신이라면 어떤 선택을 하겠는가? 약 100명중 90%에 가까운 사람이 "선택 1"에 표를 던졌다. 그는 미소를 지으며 말했다.

"이 표를 통해서 하나의 선택을 하는 것은 쉬운 일이 아니다. 다만 하지 말아야 하는 선택을 찾는 것은 굉장히 쉬운 일이다. 절대 하지 말아야 하는 선택은 '1'번이다."

A와 B집단의 각 이득을 생각해 보았을 때 선택 1은 당연히 잘못되었다. 왜냐하면 B집단이 손해 보지 않고도 A집단이 이득을 볼 수 있는 선택 3이 존재하기 때문이다. 선택 4나 선택 2를 선택하는 것은 B에게 손해를 강요하는 일이기에 어려울 수 있지만 적어도 1을 선택하는 것이 얼마나 비 합리적인 것임을 그는 꼬집었다. 더 나아가 우리의 마음속에 "평등"을 원하는 인간의 마음이 더 나은 선택을 하지 못하게 하는 장애물이 될 수 있다는 사실을 그는 가르쳐 주었다. 마음속의 벽이 서서히 무너져 내리기 시작했다.

   

  1. 비교우위론과 자유무역

    한미 FTA 반대 시위 소식이 속속 들려왔다. 학교에는 FTA를 반대하는 유명 인사가 강의를 했다. 이런 일들은 차츰 번져가 학교 내에서 FTA를 찬성하는 무리는 "악"으로 단정시키는 거대한 흐름이 되기도 했다. 바로 그 적절한 시점에 그에게 비교 우위론과 그로 인한 자유무역의 이점에 대해 배웠다. 아, 얼마나 놀라운 일인가. 자신 내에서의 비교우위로 남과 자유무역을 했을 때 나와 너가 모두 최고의 이득을 얻을 수 있다는 그 꿈같은 결과. 그래프와 수식아래 정의된 깔끔하고 명료한 사실 아래 난 무릎 꿇어야 했다.

    "스크린 쿼터를 예로 들어볼까? 잘봐, 너희들이 보고 싶어 하는 외화가 있어. 그런데 영화가 모두 매진되서 넌 그 영화를 보지 못하게 되었어. 스크린 쿼터가 없었더라면 몇 개의 상영관이 더 열릴 수 있었을 거고, 너희들은 영화를 볼 수 있었을 꺼야. 따라서 너희들에게 손해지. 그리고 영화관도 마찬가지야. 스크린 쿼터 때문에 재미없는 한국영화를 강제로 올리지 않았더라면 더 많은 이득을 낼 수 있었을 테니까. 그렇다면 이득을 본건 누구지?"

    FTA를 반대하던 대부분의 학생들이 학기 말에는 80%가량 찬성하는 결과를 낳았고 20%에 해당하는 학생 중 자신이 왜 반대하는 지에 대한 이유를 명확히 말할 수 있는 사람은 아무도 없었다. 스크린 쿼터를 지켜야 한다고 말하는 사람들은 소비자들의 권리를 우롱하는 파렴치한 기득권자들이 되었다. 우리는 눈앞에서 한국의 농민들은 전혀 경쟁력을 가지지 못하는 일을 하는 사람이 되어, 자신의 밥줄을 지키기 위해 다른 국민들에게 손해를 끼치는 사람이 되어 버렸다. 난 내 옆자리에 앉아있던, 반박하고 싶지만 반박할 논리가 없음을 알기에 어금니를 꽉 물어야 했던 한 농민의 아들의 옆 얼굴 모습을 아직 기억한다.

    그리고 그 사실을 알게 되었다. "선"을 행하는 순수하고도 청결한 마음이 "악"의 결과로 나타날 수 있다는 것을. 난 이때 부터 선의 동기로 행하고 말하는 사람들 보다 "돈"과 "이득"의 논리로 말하는 사람을 더 신뢰할 수 있게 되었다. 이것은 무섭고도 불편한 진실이었다. 새로운 지식 앞에 환희를 느끼면서도 마음속 찜찜함을 벗을 수 없었던 것은 인간의 "선함"이 아니라 인간의 "이기심"에 의존해 잘도 굴러가고 있는 이 사회에 대한 힘없는 불만이었는 지도 모른다.

       

  2. 죄수의 딜레마

    그러나 개개인의 바른 선택이 사회적으로 반드시 옳은 결과를 보여주지는 못한다. 그 사실은 굉장히 오래된 이론인 죄수의 딜레마에서 찾을 수 있다.

  

죄수 B의 침묵

죄수 B의 자백

죄수 A의 침묵

죄수 A, B각자 6개월씩 복역

죄수 A 10년 복역, 죄수 B 석방

죄수 A의 자백

죄수 A 석방, 죄수 B 10년 복역

죄수 A, B각각 5씩 복역

위와 같은 상황에서 죄수 A와 죄수 B가 취해야 하는 선택은 무엇인가? 죄수 A의 입장에서 보자. 만약 죄수 B가 침묵한다면 죄수 A에게 있어 더 나은 선택은 자백하는 것이다. 만약 죄수 B가 자백한다면? 역시 죄수 A의 입장에서 자백하는 것이 최선이다. 이는 B에게도 마찬가지 이므로 머리좋은 두 녀석을 위 상황에 붙여 놓으면 100이면 100 서로 자백하게끔 된다. 서로 더 나은 결과를 얻을 수 있음에도 그렇게 하지 못하는 것이다.

"자, 어떻게 하면 죄수 A와 B는 이 문제를 타결할 수 있을까? 의리와 인정에 기대서? 자백하는 놈은 의리없는 놈, 나쁜 놈? 조폭들이 매번 배신하는 것을 봤지? 정에 호소하면 안된다. 악함에 호소해야지. 이렇게 하면 어떨까? 보스가 자백하는 죄수의 가족을 해치기로 했다면? 절대 자백하지 않겠지?"

   

Outtro.

제길, 이렇게 잘도 굴러가는 세상은 나쁜 사마리아 인의 세상이다. 물론 책에서 나쁜 사마리아인이라 함은 거짓말을 일삼는 강대국을 일컫는 말이지만 굳이 강대국에 한정시키지 말자. 우리 모두가 나쁜 사마리아 인이 아니던가. 겉으로는 "positive-sum" 싸움이라고 하면서 속으로는 서로들 "zero-sum" 싸움을 하고 있는 우리들, 아니 성급히 일반화하지 말자. 적어도 내가 나쁜 사마리아 인이 아니던가.

과연 해법은 없는가. 나쁜 사마리아인들을 욕하면서 자신은 나쁜 사마리아인이 되고 싶어도 될 수 없는 현실을 미워하지 않을 수 있는 재간이 있는가. 사다리를 차고 싶어도 찰 사다리가 없음에 분노하는 이 상황을 벗어나 너도 좋고, 나도 좋을 수 있는 방법은 없는 것인가. 나에게 최선인 일이 사회와 우주에게도 최선일 수 있는 방법은 없는 것인가.

책 속에는 잠시 지나가는 이야기이지만 난 간절히 거기에 집중하고 싶다.

"더 나아가 나쁜 사마리아인 행세를 하지 않는 것이야 말로 부자 나라들에게 이익이 된다는 명백하게 입증하는 사례가 있다. 그 가장 큰 예는 마셜플랜이다. 이것은 과거의 적국들까지 포함한 다른 나라의 번영이 자국의 이익에 부합하는 것으로 본다는 신호였던 것이다. 이러한 깨인 전략은 자본주의의 황금기를 겪게 했다."

오호라, 남을 돕는 것으로 너도 좋고 나도 좋을 수 있다는 이야기다. 그렇다면 왜 지금은 그렇게 하지 못하는 건가?

  

선택 1

선택 2

A집단

5

3

B집단

-2

5

위와 같은 선택지가 있다고 하고, 선택권이 A집단에게 있다고 했을 때 A집단은 당연히 1을 선택 하게 된다. 이 때 A집단에게 왜 2를 선택 하지 않냐고 따지는 것이 아니다. 그런데… 혹시 판을 잘못 보고 있는게 아닐까?

  

선택 1

선택 2

A 집단

5

7

B 집단

-2

10

현재의 판은 이와 같지 않을까? 2라는 선택으로 더 나은 결과를 얻을 수 있는데 B가 더 큰 이득을 얻고 있다는 사실에 불안감에 계속 1을 선택하고 있는 것은 아닐까?

   

역의 역이다.

'이런 귀염둥이들, 우리가 새로운 지식을 줄께, 이게 정답이야, 새로운 세상이 보이지?' 그 때 난 어렸다. 그들이 보여준 세상에 내 이기심을 올려 태웠다. 그러나 이제 또 다른 세상이 보인다. 이건 내가 만들어낸 잘못된 시험지 같아 보일지도 모른다. 그래프로 그려진 정의도 없고 미분과 적분으로 치장된 수식도 보이지 않는다. 그런데 이 시험지가 옳다고 믿고 싶다. 이 새 시험지를 들고 과감히 2번을 선택하고 싶다. 그래, 하지만 난 알고 있다. 이 선택은 감히 자랑할 것이 하나도 없다는 것을. 왜냐면 이 모든 것들은 바로 나자신의 이득을 위해 행한 일이니까.

신고

라인홀트 니버의 한마디

끄적끄적 2009.06.10 17:42 Posted by 아일레프

우리의 삶 속에서 성취 될 수 있는 가치있는 일은 하나도 없다.

그러므로 우리는 소망으로 구원 받아야 한다.


역사의 정황속에서 완전한 의미를 드러낸 진실이나 아름다움, 선함은 하나도 없다.

그러므로 우리는 믿음으로 구원 받아야 한다.


우리가 아무리 고결하더라도 혼자의 힘으로 환성될 수 있는 일은 하나도 없다.

그러므로 우리는 사랑으로 구원 받아야 한다.






-- 당신의 말에 동의합니다.
저작자 표시
신고

갬블시티

즐거운 책 이야기 2009.06.09 22:32 Posted by 아일레프


갬블시티를 읽고.

- 이상하지 않습니까? 정말 이상합니다. 세상 사람들 중 누구는 이렇게... 만화책 주인공들은 어찌 이렇게 멋있고 강한 것입니까? 그리고 어떻게 이런 용기가 나오는 걸까요? 칼도 맞고 총도 맞고.. 생사의 길을 넘나들면서 어찌 이렇게 가슴을 펼수 있는 겁니까? '이건 만화여서 그렇다'라는 말은 하지 마시구요. 그리고 그와 반대로 나는 왜 이렇게 나약한 겁니까? 너무 약해 돌 팔매 소리에도 기절할 것 같습니다.  그 차이가 어디서 오는 겁니까? -

- 누군가 돌봐줘야 할 대상이 있을 때 사람은 강해질 수 있단다. 돌봐야 할 대상이 없는 사람은, 지켜야 할 것이 없는 사람은 약하다. 길거리 강아지라도 돌보는 사람은, 가련한 꽃한송이라도 돌보는 사람은 강해질 수 있다. 그러나 지켜야 할 것이 자기 자신의 자존심, 자신의 즐거움. 자신의 만족, 오로지 자신만의 정의 뿐인 사람은 강해질 수 없단다. 그리고 또 중요한 것이 있다. 강해지기 위해 돌보는 것이 아니라, 돌보기 위해 강해지는 거란다. 순서를 뒤집지 말거라. -
저작자 표시
신고

Floating Control Arpa

프로그래밍 2009.06.09 18:16 Posted by 아일레프


Floating Control을 만들어 보았습니다. 준비물은 "Dragging Elements in a Canvas" 의 DragCanvas(조금 수정이 필요합니다.) 그리고 자체 구현한 ResizableBorder, FloatingControl, FloatingControlItem입니다.

그리고 위는 "MSDN의 MVVM 예제"를 FloatingControl을 사용해 구현한 예입니다.

하지만 역시 버그가 있고, 몇가지 중요 기능이 빠져있습니다. ^^;;;

Floating Control을 만들면서 견고하고 안전한 컨트롤을 만든다는 것이 참 어렵다는 것을 알게 되었습니다. 3rd Party Control을 만드는 회사들에게 참 감사할 일입니다.

신고

Layout System, Grid & CellPadding

프로그래밍 2009.06.09 18:04 Posted by 아일레프

원래 Grid에 CellPadding 기능을 넣으면서 MeasureOverride와 ArrangeOverride, OnRender를 아우르는 WPF Layout System 의 원리에 대해 포스팅 하려고 했는데 그로부터 기나긴 시간이 흘러, 과감히 포기하기로 했습니다. 일단 제가 "Layout System in WPF"라는 거창한 제목의 포스팅을 할 수 있는 실력이 없다는 것을 뼈져리게 느낀 것이 첫째이고, 몇 가지 수수께끼가 있는데 아직 해당 부분을 알아내지 못했습니다. 지금은 눈에 보일 듯 말듯 흐릿하지만 언젠가 모든 것이 선명히 보이면 공유하도록 하겠습니다. ^^

따라서 이번 글의 내용은 Layout System in WPF가 아니라 그냥 Grid에 CellPadding기능을 추가한 것에 불가합니다. 사실 이전 포스팅과 "WPF - CellPadding 속성을 구현하는 Grid Layout" 를 통해서도 이 기능이 가능합니다. 하지만 해당 방법들은 모두 Children의 Margin을 수정함으로써 이 기능을 구현했는데, 이번 포스팅에서는 Grid의 Children의 Margin 프로퍼티를 수정하지 않고 CellPadding을 구현하는 방법을 소개하도록 하겠습니다.

목적 :: Grid를 상속받아 GridEx란 패널을 정의하고 이 패널에 CellPadding 프로퍼티를 추가한다. 그리고 그에 따른 기능을 추가한다.

음.. 가장 먼저 할 일은 역시 GridEx를 정의하고 CellPadding 프로퍼티를 추가하는 것이겠네요.

   

해당 기능을 구현하기 위해 CellPadding의 값에 따라 자식 UIElement들의 배치(Arrage)가 달라져야 하겠네요. 만약 CellPadding이 0일때 Grid와의 상대 위치가 0,0인 곳에서 너비가 100이고 높이가 100으로 배치된 자식 Element는 상대 위치 5,5에 너비가 90, 높이가 90으로 표시되어야 합니다. 이를 바탕으로 ArrangeOverride 메소드가 할 일을 정리하면 아래와 같겠습니다.

ArrageOverride 메소드에서 해야할 일

  • CellPadding이 0이라면 base.ArrangeOverride()를 호출하고 메소드를 종료한다.
  • 자식 Element가 Arrange될 Rect를 CellPadding 값에 따라 적절히 계산한다.
  • 계산한 Rect로 자식 Element를 Arrange한다.

    위 명세대로 ArrangeOverride를 만드니 아래와 같았습니다. 물론, GetRectOfUIElement메소드는 만들어지지 않았기 때문에 붉은 줄로 표시됩니다. 앞으로 이녀석을 구현하는 것이 주요 작업이겠네요.

     

    그렇다면, uiElement의 크기와 배치될 상대 위치는 어떻게 얻을 수 있을까요? 이는 RowDefinitions과 ColumnDefinitions의 속성으로 얻을 수 있습니다. RowDefinitions에는 ActuralHeight 와 Offset(Y axis 상대 위치) 속성이 있고, ColumnDefinitions에는 ActuralWidth와 Offset(X axis)이 있으니 UIElement를 어디에 배치해야 할 지 알 수 있습니다. 따라서 다음과 같이 메소드를 수정합니다. Column, Row정보를 얻기 위해 해당 Element들의 Grid와 관련된 첨부 프로퍼티를 얻어오고 있음을 알 수 있습니다.

    이제 GetRectOfUIElement는 UIElement의 column, row, columnSpan, rowSpan 값과 ColumnDefinitions, RowDefinitions의 속성을 이용해 Rect 구조체를 반환하면 됩니다. 아래와 같이 만들었습니다. 기존의 값과는 다르게 CellPadding값에 따른 몇 가지 추가적인 논리가 들어갔습니다.

     

    생각보다 쉽게 끝났네요. 하지만 이로서 안심하고 프로그램을 실행하면 Exception이 떨어집니다. 왜냐면 GetRectOfUIElement가 실행될 때 ColumnDefinition과 RowDefinition의 ActualWidth, ActualHeight값이 결정되지 않았기 때문입니다. 먼저 이를 계산해 주어야 합니다.

    그러나 ColumnDefinition과 RowDefinition의 ActualWidth, ActualHeight, Offset값을 직접계산하는 것은 쉬운일이 아닙니다. 그래서.. 그냥 Reflection을 사용해 Grid의 SetFinalSize 메소드를 호출해 주었습니다.

     

    여기 까지 해놓고 샘플을 실행하면 아래와 같이 나옵니다.

    보면 알겠지만  Grid안의 UIElement들이 조금 짤려 있음을 알 수 있습니다. 이는 MeasureOverride를 재정의 하지 않아 생기는 문제입니다. MeasureOverride를 통해 Children은 자신들이 어떠한 크기로 Rendering될지 정하고 ArrangeOverride를 통해 적절한 위치에 배치되게 되는데 MeasureOverride가 재 정의되지 않아 CellPadding와 관계없이 자신의 크기를 정해버려 짤린것입니다. 따라서 MeasureOverride를 재 정의합니다.

    위와 같이 재 정의 해 주었습니다. 그리고 실행하면 다음과 같이 정상적으로 나옵니다.

똑같은 기능을 구현하는 것도 다양한 방법이 있다는 것이 바로 이 일을 하는 즐거움이 아닌가 싶습니다. ^^

신고

나라가 불탄다

즐거운 책 이야기 2009.05.28 18:22 Posted by 아일레프

"나라가 불탄다"를 읽고

 

   

세상에서 가장 무서운 사람은 자신이 옳다고 굳게 믿는 사람이다. 자신이 정의의 편에 서있다고 생각하는 사람은 얼마나 위험한가! 남들을 보며 감히 개념없다 하는 사람은 얼마나 위험한가! 자신의 선을 세계와 우주의 선에 일치시키며 그 선을 행하는 순수하고도 청결한 마음은 얼마나 위험한가.

   

그러나 실수를 저지르지 않기 위해 모든 것을 모른다 하고 아무 것도 믿지 않는다 하고 이것도 좋고 저것도 좋다 하면 난 그 무엇도 될 수 없다. 내게 질문을 던지는 사람들에게 "잘 모르겠지만 지금은 이렇게 생각하고 있어요" 따위 어법은 아무런 도움이 되지 않는다. 지금 당장은 그렇게 피해갈 수 있겠지만 언젠가는 십자가 앞에서서 오른편인지 왼편인지를 정해야 한다. 결국 아무리 작은 한 걸음 이라도 믿어야 한다. 이 발걸음으로 우주가 멸망하는 일은 없다고, 나는 옳은 길을 가고 있다고 믿어야 한다.

   

그렇게… 생각해 보았다.

신고

Layout System in WPF : 프롤로그

프로그래밍 2009.05.21 20:49 Posted by 아일레프

모든 일은 그 메일로 부터 시작되었다.

- "Grid 안의 모든 Element 들의 간격을 일정하게 띄울 수 없을 까요? Html의 CellPadding 같은 역할을 할 수 있는 어떤 방법이 있을 것 같은데요" -

   일단 Grid 내의 모든 Element의 간격을 띄울 수 있는 방법은 있다. Grid내에 선언된 모든 Element의 Margin값을 1~2로 설정하는 것이다. ^^;; 그러나 이것은 중복된 코드를 발생시키게 되고 당연히 솔루션이 될 수 없다. 이 문제로 몇 개의 메일이 왔다 갔고 그나마 중복을 줄일 수 있는 방법이 Grid내에 사용되는 모든 Control들의 Default Style을 지정해 Margin을 1로 주는 것이라는 것을 서로 확인 했다.

 물론 가장 쉬운 방법은 코드상에서 해당 Grid에 Loaded 이벤트를 걸어 Grid Children들의 Margin에 특정 값들을 더해주는 것이다. ^^;; …(죄송합니다.)

 몇 번 이 문제에 대해 묵상을 하니 그나마 가장 좋은 방법은 역시 Attached Property 라는 생각이 들었다. 다음과 같이 구현 될 수 있겠다.

 

일단 FrameworkPropertyMetadataOptions.Inherits를 사용하고 있다. 따라서 이 Attached Property는 상속되게 된다.

위 경우 Grid뿐 아니라 RowDefinition과 Button에도 CellPadding Attached Property가 20으로 지정되게 된다. 따라서 2개의 버튼과 RowDefinition 객체가 로드 될 때 저 이벤트가 호출되게 되고 Margin값에 CellPadding값을 더해주게 된다.

왜 굳이 이렇게 했을까? Inherits를 사용하지 않으면 sender를 Grid로 하는 이벤트만 호출 될 테고 그 때 Grid의 모든 Children들의 Margin값에 CellPadding값을 더해주면 간편할텐데 말이다.

위와 같이 하면 더 간편할 것 같은데 이렇게 하지 않은 것은 이유가 있다. 왜 안되는지 잠깐 생각해 보자. 이건 퀴즈 ^^;;;

위 코드에는 또 이상한 점이 있다. SetCellPadding 메소드를 불러 다시 CellPadding 첨부 프로퍼티의 값을 0으로 만드는 것인데, 이는 이 첨부 프로퍼티의 값이 계속 타고 들어가 Grid 내에 또 다른 Grid가 있다면 이 Grid에도 영향을 주기 때문에 추가된 것이다. 물론 위 XAML에서와 같은 경우라면 명시적으로 해당 Grid에서 CellPadding값을 0으로 주면 된다. 그러나 만약 특정 Element의 ControlTemplate내에 Grid가 있다면 문제가 복잡해 질 수 때문에 이를 방지하고자 꼼수지만 0으로 만들었다.

어찌 되었건 위와 같이 잘 적용 되었다. 하지만 이 솔루션에는 단점이 있다. 일단 해당 Attached Property가 Grid가 아닌 다른 Pannel이나 Control에도 정의 될 수 있다는 것이 마음에 안들고 Attached Property가 Tree를 계속 타고 내려가 실제로는 아무 일도 하지 않는 저 메소드를 계속 실행한다는 것이 또 마음에 안든다.

결국 가장 좋은 해답은 Grid를 상속받아 재 정의해 CellPadding 프로퍼티를 추가시키는 것이라는 생각이 들었다. 그래서 과감하게 Grid를 상속받은 GridEx란 클래스를 정의하고 MeasurreOverride와 ArrangeOverride 메소드를 재 정의 했는데... 아… 이 때는 알지 못했다. Grid가 모든 Panel중에서 가장 많은 일을 하고 있는 복잡한 Panel이라는 것을… 결국 멈출 수 없는 수렁으로 빠져 들어가게 되는데…

   

   

   

… 이 시리즈가 계속 될 수 있을까요? ^^;;;

신고

What's New in XAML 2009

프로그래밍 2009.05.20 21:38 Posted by 아일레프

요즘 .Net Framework 4.0 beta가 나와 관련 리뷰가 블로그 여기저기서 쏟아 지고 있다. 상황이 이렇다 보니 차기 WPF의 기능에 대해서 관심을 가지지 않을 수가 없다. What's New in the .NET Framework 4 페이지로 가보니 "In the .NET Framework 4 Beta 1, Windows Presentation Foundation (WPF) contains changes and improvements in many areas. This includes controls, graphics, and XAML." 라고 쓰여있는 것을 확인 할 수 있었다. 기대심에 What's New in Windows Presentation Foundation Version 4  페이지로 가보았다. Control, Multi-touch, Esing Function, Designer에 대한 링크와 짤막한 글이 포함되어 있었는데… 어찌된 일인지 내가 가장 궁금해했던 "XAML"에 대한 부분만 쏙 빠져있다. .. 설마 이게 끝은 아니겠지.. MSDN 쓰는 사람이 조금 바쁜가보다. 하고 생각해보았다.

 

이 적절치 않은 시점에 작년 PDC 2008에 리뷰된 차기 XAML의 기능을 살펴 보았다. 해당 내용은 Microsoft .NET Framework: Declarative Programming Using XAML 동영상으로 확인 할 수 있다. 참 오래된 이야기이다.

 

1.     Easy Object References with {x:Reference}

2.     Built-in Types

3.     Generics in XAML with x:TypeArguments

4.     Support for Arbitrary Dictionary Keys

5.     Use of Non-Default Constructors with x:Arguments

6.     Use of Static Factory Methods with x:FactoryMethod

 

1 {x:Reference}이다. Markup Extension은 반드시, , 꼭 필요하다. 빨리 빨리 하루빨리 추가 해다오.

<Button Name="button" /> 

<Label Target="{Binding ElementName=button}"/>

 

위 코드는 Label Target Property button이름으로 XAML상에서 정의된 button이라는 객체의 reference를 넣는 구문이다. … 그런데 정말 그런가? 사실 이미 정의된 객체의 reference Target Property에 넣는 단순한 작업을 하기 위해 위 코드는 복잡한 일을 하고 있다. 그 이유는 Binding이라는 Markup Extension이 반환하는 객체는 button 객체의 reference가 아니라 BindingExpression이기 때문이다. 물론 Path가 지정되어있지 않기 때문에 결국 reference가 반환되는 것과 동일한 효과를 가지지만 좀 떨떠름하다. 그리고 더 중요한 이유가 있으니 바로… Binding MarkupExtension 이 반환하는 것이 BindingExpression 객체 이기 때문에 이 구문을 사용할 때 Target은 반드시 의존 프로퍼티여야한다는 것이다. 이 사실에 얼마나 분개해 왔던가. 왜 진작 {x:Reference}와 같은 것을 MS는 생각하지 못하고 이제서야 추가한단 말인가. 하여간 추가 해준다니 감지덕지할 뿐이다.

 

   사족 :: 제가 {x:reference}에 대한 필요성을 가장 크게 느낄 때는 Converter ConverterParameter xaml에 정의된 element의 참조를 넘겨 주고 싶을 때, 그리고 새롭게 정의한 MarkupExtension Constructor Parameter xaml에 정의된 element의 참조를 넘겨주고 싶을 때였습니다. 그리고 오늘 제 옆자리에 계신 분도 이 키워드의 필요성을 강하게 느겼다고 합니다.

 

 

2번이다. Built-In Type이다. 이젠 불필요하게 mscorlib System namespace xmlns 에 추가해주지 않아도 XAML namespace를 이용해 다양한 Type을 곧바로 사용할 수 있다. 제법 많구나.

<x:Object/><x:Boolean/><x:Char/><x:String/><x:Decimal/><x:Single/><x:Double/><x:Int16/><x:Int32/><x:Int64/>

<x:TimeSpan/><x:Uri/><x:Byte/><x:Array/><x:List/><x:Dictionary/>


Element 뿐 아니라 MarkupExtension도 허용해 주었으면 좋겠다. 예를 들어 Button ContentInt32 Type 12라는 value를 넣고자 한다면

 

<Button>

<x:Int32>12</x:Int32>

</Button>

 

이것 뿐 아니라 다음과 같이 사용할 수도 있도록 해주면 더 좋겠다.

<Button Content="{x:Int32 12}"/>

 

 

3 Generics in XAML 이녀석도 엄청나게 유용할 것 같다. 다음과 같이 사용할 수 있단다.

 

<ObservableCollection x:TypeArguments="{x:Type Employee}">
   
<l:Employee FirstName="John" Name="Doe" />
   
<l:Employee FirstName="Tim" Name="Smith" />
</ObservableCollection />

 

위 녀석은 다음코드와 같은 의미를 지니게 된다. 벌써 부터 기대된다. 흐흐흐

 

var collection = new ObservableCollection<Employee>();

collection.Add(new Employee{FirstName="John", Name="Doe"});

collection.Add(new Employee{FirstName="Tim", Name="Smith"});

 

 

4 Arbitrary Dictionary Keys string이 아닌 다른 Type Value x:Key에 지정 할 수 있다는 것인데 이것은 PDC에서 다음과 같은 예를 통해 설명되었다.

 

<StreamGeometry>M 0 0 L 12 8 l 9 12 z
   
<x:Key><x:Double>10.0</x:Double></x:Key>
</StreamGeometry>

 

사실  x:Key string이 아닌 Value를 사용할 수 있다는 것 보다 x:Key 키워드를 Element로 사용할 수 있다는 사실이 재미있다.

 

5x:Arguments 이 기능도 유용하게 사용될 수 있을 것 같다. 이 기능으로 인해 XAML 내에서 default constructor를 가지지 않는 Object도 정의할 수 있다. 정말 감사할 뿐이다.. 
 

<DateTime>
   
<x:Arguments>
       
<x:Int64>100</x:Int64>
   
</x:Arguments>
</DateTime>


여러 개의
Arguments를 사용하는 경우도 당연히 허용되어야 할 것이다. 이는 argument 라는 단어에 "s"자가 붙은것으로 봐 당연히 허용 될 것으로 보인다. 그리고 MarkupExtension도 꼭 만들어 줬으면 좋겠다.

 

 

6 x:FactoryMethod 이에 대한 예는 다음과 같다.

 

<Guid x:FactoryMethod="Guid.NewGuid" />

이는 Guid guid = Guid.NewGuid();와 동일한 의미를 가진다. x:FactoryMethod는 그 단어 대로 static factory 메소드를 호출하게 사용된다.  그런데 MarkupExtension이 추가되지 않는한 Property지정에 사용할 수는 없을 것 같다. 만약 MarkupExtension이 있다면 다음과 같이 사용될 수 있을 것이다.

 

<local:MyObject Guid="{x:FactoryMethod Guid.NewGuid}"/> --> 이건 안되는 코드. 단지 바램일 뿐.

 

.. 위와 같이 사용 가능하다면 내친김에 static factory 메소드 뿐아니라 그냥 static method도 범용적으로 사용가능하게 만들면 더욱 더 좋을 것 같다.

 

 

 

위가 XAML PDC 2008에 발표된 XAML namespace에 추가된 키워드를 소개한 것이다.

 

또 괜찮은게 있는데 훨씬 더 다양한 기능을 제공하는 XamlReader이다. 기존의 XamlReader Load, Parse 기능만 가지고 있던 것을 생각하면 이건 정말 훌륭하다. 모르긴 몰라도 XamlReader를 사용한 다양한 응용들이 Web상에 돌아다니게 될 것이다.

 

이에 추가로 몇가지 바램이 있다.

 

1.     Style을 지정할 때 Base 클래스의 Target Type지정으로 Base 클래스를 상속받는 객체들의 Style이 지정되게 해달라.

 

<Style TargetType = "{x:Type Control}">

<Setter Property="Background" Value="Black"/>

</Style>

 

위 와 같은 경우 XAML내에서 Control을 상속받는 모든 Element가 이 Style이 적용 되게끔 해줬으면 좋겠다.

 

2.     DataContext Type지정으로 Binding Source Type에 따라 Path를 정의할 때 CompileTime에 에러체크가 가능하게 해달라.

Binding이 유용하긴 하지만 CompileTime Path에 올바른 값이 들어갔는지 확인 할 수 없고 런타임에도 Exception을 먹어버린다.

<Button Content="{Binding Path=오타}"/>라고 했을 때 Binding Source로 지정된 객체(이 때는 Button 객체의 DataContext가 될 것이다.) "오타"라는 Property가 존재하지 않음에도 CompileTime과 심지어 Runtime에도 이 사실을 알 수 없다는 것은 조금 아쉽다.

x:TypeArguments 키워드도 생겼는데 이왕 Generic Binding 객체도 만들어 버려서 이를 사용할 경우에는 CompileTime이나 Design Time Path를 검사해 에러나 Exception을 발생시켜주면 바랄 바가 없다.

 

<Button Content="{Binding x:TypeArguments={x:Type local:MyObject}, Path=오타}"/> 

 

3.     Resource내에서도 MarkupExtension을 선언할 수 있게 해달라.

.. 이건 좀 힘들다는 것은 알고 있다. 그래도 이게 되면 많이 유용할 것 같은데 어떻게 좀 해주면 안될까?

<Page>

<Page.Resources>

<Binding Path="PropertyName" x:Key="myBinding"/>

</Page.Resources>

</Page>

 

위와 같이 Resource에서 Binding을 미리 정의해놓고 코드에서 불러 사용 할 수 있으면 참 좋을 것 같다. 물론... 이건 힘들다는 것을 알고는 있지만 좀  고려해달라. 아니면 MarkupExtension이 아닌 Binding 클래스 비슷한 녀석을 만들어 주던지.

사족 -- Binding MarkupExtension이기 때문에 위 코드는 다음과 같은 의미가 된다. 따라서 사용 불가능하다.

page.Resource = new Binding{ Path="PropertyName}.ProvideValues();

 

4.     Visual Studio Designer x:Class로 지정된 partial class가 존재할 때 최소한 생성자 만이라도 읽어 줬으면 좋겠다. 그리고 해당 Project App.xaml이 있다면 해당 xaml Resource도 좀 알아서 읽어줬으면 좋겠다.

<Page x:Class="">

<Button Content="{StaticResource 리소스키}"/>

</Page>

 

위 경우 당연히 지금 이 상황에서는 Error이다. 그러나 디자이너 넌 모르고 있다. InitializeComponent로 이 XAML 파일이 로드되기 전에 behind 코드에서 "리소스키"라는 key로 특정 객체가 Page Resource에 추가되어 있을 수도 있다는 것을...

 

5.     왜 유용한 기능을 가지고 있는 Class들은 모두 internal로 되어있는 것인가? 특히 System.Windows.Markup namespace의 많은 Class들을 사용할 수 있게 해주었으면 좋겠다.

 

 

 

이상하다 또 많은 불만 사항이 있었던 것 같은데… 생각날 때마다 추가해봐야겠다.

 

신고

Converter Manager

프로그래밍 2009.03.03 21:11 Posted by 아일레프

WPF Application을 만들다 보면 WPF의 강력한 기능인 DataBinding을 참 많이 사용하게 됩니다. 특히 MVC, MVP, MVVM 패턴 과같이 Model을 정의해 View를 구성하는 패턴을 사용하면 DataBinding은 정말 유용한 도구가 되죠. 그렇게 DataBinding을 사용하다 보면 자연히 많이 사용하게 되는 것이 바로 Converter입니다. 그런데 이 놈의 Converter가 너무 많아 절 속상하게 할 때가 너무 많이 있습니다.

지금 프로젝트 내에서도 많은 Converter가 있는데요, Enum값을 intg형으로 Convert, ConvertBack하는 EnumConverter, Bool값을 반대로 반환하는 BoolToOppsoiteBoolConverter, Double값을 int로 변환하는 DoubleToIntConverter 등 많은 Converter등이 있습니다. 절 가장 가슴 아프게 하는 부분은 단지 Converter가 많아서가 아니라, 특정 시점에 여러 개의 Converter가 동시에 존재하는 것입니다.

 

만약 Application 3개의 View가 존재하고 이 녀석들은 반드시 Singleton으로써 항상 메모리에 존재하게 된다고 가정해 봅시다. 그리고 모든 View가 3개의 공통적인 Converter가 필요하다고 가정해 보겠습니다.

 

<appLib:IntegerConverter x:Key="TimeIntegerConverter"/>

<appLib:BoolToOppositeBoolConverter x:Key="NegativeBoolConverter"/>

<appLib:IntegerToBooleanConverter x:Key="IntToBooleanConverter" />

모든 View는 위 녀석들을 사용하기 위해서 자신의 Resource에 위 Converter들을 정의해 놓고 있습니다.

<UserControl.Resources>

        <appLib:IntegerConverter x:Key="TimeIntegerConverter"/>

        <appLib:BoolToOppositeBoolConverter x:Key="NegativeBoolConverter"/>

        <appLib:IntegerToBooleanConverter x:Key="IntToBooleanConverter" />

</UserControl.Resources>

그리고 이 녀석들은 {Binding Path=TimeData.ZoneType, ElementName=My, Converter={StaticResource TimeIntegerConverter}} 와 같이 사용되게 됩니다.

문제는 메모리 상에 IntegerConverter라는 녀석은 필요한 시점에 단 하나만 있으면 좋을 텐데, 3개나 존재한다는 것입니다. 다음의 그림과 같을 것입니다.

 


 
안타깝지 않습니까? 아무리 메모리가 넘쳐나는 세상이더라도 위와 같은 현상을 가만히 지켜보기에는 너무 가슴이 아픕니다. 그리고 정말 귀찮은 일은 매번 View를 만들 때 마다 Resource Converter를 생성하는 Markup 코드를 Copy & Paste해 줘야 한다는 것입니다. 이젠 더 이상 참을 수 없습니다. 뭔가 해결 해야겠네요.


가장 먼저 생각나는 방법은 다음과 같았습니다. WPF Application StaticResource Markup Extend 구문은 Application Root Tree까지 Resource를 검색하니까 App.xaml에 위 Resource들을 정의해놓고 사용하는 것입니다.
그런데 이 방법은 문제가 있네요. 이 방법으로 실제로 프로그램을 실행하면 잘 되지만 Design Time WPF Designer App.xaml을 찾지 못하고 에러 메시지를 보여주게 됩니다. 이건 좀 아닌 것 같습니다. StaticResource말고 DynamicResource를 사용해 Converter에 대입 하라구요? 아쉽게도 Binding 객체의 Converter Property DependencyProperty가 아니기에 DynamicResource를 쓸 수 없습니다. 그리고 설사 DynamicResource확장 구문을 사용할 수 있다 하더라도 이 방법을 사용하기 망설여 지는 이유는 Compile Time에 에러를 찾아 낼 수 없다는 것입니다. IntegerConverter 키 이름을 실수로 IngetConverter로 써도 Compile Time에 에러를 보여주지 않습니다. 확인 하기 위해서는 직접 실행해 보고 결과를 체크해 볼 수 밖에 없지요.


결국 저에게 필요한 것은 Converter Manager입니다.

 
이 녀석이 해야 할 일은 다음과 같습니다.

1. Application 내에서 Converter를 Singleton으로 관리한다.

2. View에게 XAML상에서 Converter를 사용할 수 있는 방법을 제공한다.

3. Design Time에 잘못된 Converter 사용 요청이 들어오면 Exception을 발생시켜 사용자에게 알 수 있게 한다.

 

자 어떻게 하면 될까요? 저는 다음과 같은 방법을 생각했습니다.

먼저 ConverterManager라는 Static Class를 만듭니다.

 

public static class ConvertManager

    {

        private static KeyedByTypeCollection<IValueConverter> converterCollection; static ConvertManager(){

            converterCollection = new KeyedByTypeCollection<IValueConverter>();

        }

        public static IValueConverter GetConverter(string typeName)

        {

            return GetConverter(Type.GetType(typeName));

        }

        public static IValueConverter GetConverter(Type convertType)

        {

            if (convertType == null)

                throw new InvalidOperationException();

            if (converterCollection.Contains(convertType))

            {

                return converterCollection[convertType];

            }

            else

            {

                IValueConverter converter = Activator.CreateInstance(convertType) as IValueConverter;

                if (converter == null) throw new InvalidOperationException();

                converterCollection.Add(converter);

                return converter;

            }

        }

    }

ConvertManager Type 객체나 Type이름을 받아 Converter로 변환해 반환하는 GetConverter 메소드를 가지고 있습니다. 이 메소드는 결국 Type Parameter로 받은 후 해당 Type이 자신의 converterCollection에 있는지 확인 합니다. 없으면 객체를 만들어 converterCollection에 넣은 후 객체를 반환합니다. 있으면 해당 Converter를 반환하지요. 쉽지요?

 

그리고 XAML에서 쓸 수 있도록 MarkUpExtension을 정의 합니다. 다음과 같이 말이죠.

public class StaticConverterExtension : MarkupExtension

    {

        Type type;

        [ConstructorArgument("type")]

        public Type Type

        {

            get{return type;}

            set{

                type = value;

            }

        }

        public StaticConverterExtension() { }

        public StaticConverterExtension(Type type)

        {

            Type = type;

        }

        public override object ProvideValue(IServiceProvider serviceProvider)

        {

            return ConvertManager.GetConverter(Type);

        }

    }

 

이름이 구리지만 참아 주세요 ^^;; 녀석이 하는 일은 모두 이해하실 것이라 생각 하고 생략하겠습니다. 중요한 녀석은 녀석들을 별도의 Assembly 만들어야 한다는 것입니다. 공통된 Library Assembly 별도로 관리한 다는 의미도 있지만 다른 이유는 Microsoft 팀의 버그 때문입니다. 같은 Assembly안에 있으면 MarkUpExtension XAML Attribute 설정할 없는 버그입니다. (.)

 

그리고 View들은 Assembly 참조하고 다음과 같이 사용하면 됩니다.

<StackPanel>


<
Button Name="button" >123</Button>     

<
Button IsEnabled="{Binding ElementName=button, Path=IsEnabled, Converter={lib:StaticConverterExtension Type={x:Type lib:BoolToOppositeBoolConverter}}}">asdfasdf</Button>


<
Button Content="{Binding ElementName=button, Path=Content, Converter={lib:StaticConverterExtension {x:Type local:IntegerToBooleanConverter}}}"></Button>

 
</StackPanel>

 

BoolToOppositeBoolConverter true이면 false 반환하고, false이면 true 반환하는 Converter이고, IntegerToBooleanConverter 바인딩 Source Path int 변환할 있다면 true 반환하고 그렇지 않으면 false 반환하는 녀석입니다.

 

위와 같이 만들고 보니 디자이너가 다음과 같이 훌륭하게 결과를 표시해 주었습니다.

 

~ 뿌듯하네요. 이제 이상 Converter Resouce 정의해 필요도 없고 Memory 여기저기 존재하는 Converter 때문에 잠 못 잘 걱정도 사라졌습니다. 그리고 코드를 보면 Library Assembly 참조만 하고 있으면 어떤 Converter StaticConverterExtension으로 사용할 있음을 수있습니다.

 

탐구생활 : Converter 아니라 Application 여기저기 널려있는 StaticResource 여러분들을 속상하게 있습니다. 나름대로 해결방법을 생각해봅시다~

신고
TAG converter

끄적끄적 2009.02.26 22:52 Posted by 아일레프

by 민영
꽤 많이 걸어왔어.
이제얼마 남지 않은 것 같아.

저기 지평선 위에 늘어진
키 큰 나무들,
그 밑에 모여 앉은 작은 집들.

보이지?
발갛게 타오르는 눈부신 석양,
그리로 가고 있는 중이야.

잘 있어!

 - 살다보면 무슨 뜻인지 이해가 잘 되지 않지만 가슴을 움직이는 것들이 존재한다는 것을 알게 된다.
오랜만에 마음을 움직이는 시.
그런데 솔직히 무슨말을 하는 건지는 모르겠다. 잘 있어.라는 말이 그냥 좋다.
그리고 나도 님과 함께 석양이 보이는 곳으로,바람이 불어오는 곳으로 가고 싶다.
그런데 님은 "싫어"라고 말할 것 같다. 하하

신고

WPF Command-Part4 : DelegateCommand

프로그래밍 2009.02.26 22:24 Posted by 아일레프

(미루고 미루다 이제야 마지막 포스팅이네요. ㅜ.ㅜ)

지난 포스팅까지 RoutedCommand에 대해서 살펴보았습니다. RoutedCommand라는 이름이 붙은 이유는 Command Target을 찾기 위해RoutedEvent를 사용하기 때문이라는 것도 알 수 있었죠. 그런데 개발자들은 다른 유형의 Command에 대한 필요성을 느끼게 되었습니다.

적절한 UI를 구성하기 위해 개발자들이 먼저 생각하는 것은 바로 View와 논리의 분리일 것입니다. 현재 UI Design Pattern으로 널리 사용되는 MVC, MVP, MVVM의 경우도 View와 Model, Logic의 결합도를 줄임으로써 확장성을 늘리고 테스트 하기 쉬운 구조를 만들어 낸 바 있습니다. 그런데 RoutedCommand의 경우는 RoutedCommand가 CommandTarget을 View의 Visual Tree내에서 찾기 때문에 View와 Logic을 완전히 분리하기 어렵습니다. 때문에 여러 개발자들은 다른 Command에 대한 필요성을 느끼게 되었고 그 결과 DelegateCommand라는 녀석을 만들어 냈습니다. 그리고 이 DelegateCommand는 Composite Application Guidance for WPF에 포함되게 됩니다. MSDN Magazine에 소개된 DelegateCommand의 구현 코드는 다음과 같습니다.

public class StringDelegateCommand : ICommand {
  Action<string> m_ExecuteTargets = delegate { };
  Func<bool> m_CanExecuteTargets = delegate { return false; };
  bool m_Enabled = false;
  public bool CanExecute(object parameter) {
    Delegate[] targets = m_CanExecuteTargets.GetInvocationList();
    foreach (Func<bool> target in targets) {
      m_Enabled = false;
      bool localenable = target.Invoke();
      if (localenable) {
        m_Enabled = true;
        break;
      }
    }

    return m_Enabled;
  }
  public void Execute(object parameter) {
    if (m_Enabled)
      m_ExecuteTargets(parameter != null ? parameter.ToString() : null);
  }
  public event EventHandler CanExecuteChanged = delegate { };
  ...
}
간단하지요? 위 녀석은 Command Parameter로 string 객체를 전달하기 때문에 StringDelegateCommand라는 이름이 붙었습니다. 좀 더 일반적인 녀석을 만들어볼까요? 다음과 같이 만들 수 있겠네요.
public class DelegateCommand<T> : ICommand
{
        Action<T> executeTargets = delegate { };
        Func<bool> canExecuteTargets = delegate { return false; };
        bool m_Enabled = false;
        public bool CanExecute(object parameter)
        {
            Delegate[] targets = canExecuteTargets.GetInvocationList();
            foreach (Func<bool> target in targets)
            {
                m_Enabled = false;
                bool localenable = target.Invoke();
                if (localenable)
                {
                    m_Enabled = true;
                    break;
                }
            }
            return m_Enabled;
        }
        public void Execute(object parameter)
        {
            if (CanExecute(parameter) == true)
                executeTargets(parameter != null ? (T)parameter : default(T));
        }
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
        public event Action<T> ExecuteTargets
        {
            add
            {
                executeTargets += value;
            }
            remove
            {
                executeTargets -= value;
            }
        }
        public event Func<bool> CanExecuteTargets
        {
            add
            {
                canExecuteTargets += value;
            }
            remove
            {
                canExecuteTargets -= value;
            }
        }
    }
위 녀석은 Generic으로 구현된 DelegateCommand입니다. Command Parameter로 사용하기 위한 Type을 T로 지정해 사용하면 됩니다. 그리고 이 경우 ExecuteTargets에 Handler를 등록함으로써 하나의 Command이 실행되었을 때 여러 개의 Command Target이 실행되게 할 수 있습니다.

자, 다음과 같은 질문을 해봅시다. 왜 굳이 Command를 사용하는 것이 좋을까요? 만약 특정 버튼을 눌렀을 때 "Save"란 일을 하고 싶다면 이것은 충분히 이벤트 만으로 가능해 보이는데 말입니다. 다음과 같이 말이죠. 

<Button Click="Button_Click" Content="Save"/>
private void Button_Click(object sender, RoutedEventArgs e)
{

    Save();
}


위와 똑 같은 일을 하는 녀석을 Command로 만들어 보면 다음과 같을 것입니다.

<Button Command="{Binding SaveCommand}" Content="Save"/>
public Window1()
{
      InitializeComponent();
     
this.DataContext = this;
}
public ICommand SaveCommand
{
      
getreturn saveCommand; }
} 
어떤 차이가 있는지 보이십니까? 언뜻 보면 이벤트나 Command나 거기서 거기 같아 보입니다. 그러나 중요한 사실이 있습니다. Application을 구현할 때는 Business Logic을 변경할 때 보다 View를 변경하는 일이 더욱 많다는 사실입니다. Event로 Save Button을 만들었다고 합시다. 만 약 그 후에 고객이 Save Menu를 만들어 달라고 요청했다고 가정해 보겠습니다. 이 경우 event를 사용했다면 각 메뉴항목마다 이벤트를 또 걸어주어야 합니다. 만약 Command를 사용했다면? 단순히 xaml만을 교체해 Command를 Binding하는 것으로 끝나게 될 것입니다. Command로 느슨하게 결합되어 있기 때문입니다.

또한 제가 좋아하는 Command의 강력한 기능은 CanExecute입니다. 위 Save버튼이 활성화 되기 위한 조건을 CanExecute에 만들어 놓으면 모든 경우에 대해 버튼의 활성화 여부에 대해 걱정할 필요가 없습니다. 만약 Login 창을 만든다고 가정해 볼까요? UserID와 Password를 입력할 수 있는 TextBox가 필요하겠네요. 그리고 Login버튼이 있을 것입니다. 이 Login버튼이 활성화 될 수 있는 조건은 UserId와 Password TextBox에 적당한 Text가 쓰여졌을 때일 것입니다. 만약 Command를 사용하지 않는다면? 우리는 사용자가 새로운 문자를 UserId, Password에 입력할 때 마다 Login버튼의 활성화 여부를 확인해야 할 것입니다. 굉장히 우울한 사실이죠.

Command는 또한 Model, View, Logic을 분리하기 위해 많이 사용되는 MVP, MVVM Pattern에도 사용됩니다. MVVM패턴의 경우 직접 UIElement를 만드는 것이 아니라 보통 Template를 활용하는데 이 때 Template내에서 event를 사용하는 것이 아니라 Command를 바인딩 해주는 것이죠. 이 MVVM Pattern을 사용하면 Compile을 다시 하지 않고도 View를 교체하는 것이 가능해집니다. Event를 사용했을 때는 이와 같은 효과를 얻을 수 없습니다. Command Invoker와 Command Target이 Binding을 통해 느슨하게 결합되었기에 얻을 수 있는 이점이라 할 수 있습니다. 간단히 Person정보를 저장하는 녀석을 만들어 보겠습니다. 먼저 Model입니다.

public class PersonModel : INotifyPropertyChanged
{
        private string name;
        private string address;
        public string Name
        {
            set 
            {
                name = value
                if(PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Name"));
                }
            }
            get 
            {
                return name; 
            }
        }
        public string Address
        {
            set
            {
                address = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Address"));
                }
            }
            get
            {
                return address;
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void Clear()
        {
            Name= "";
            this.Address= "";
        }
    }

ViewModel은 다음과 같을 것입니다.
public class PersonViewModel
{
        PersonModel personModel = new PersonModel();
        public PersonModel PersonModel
        {
            get { return personModel; }
        }
        DelegateCommand<string> saveCommand; 
        public DelegateCommand<string> SaveCommand
        {
            get { return saveCommand; }
        } 
       
public PersonViewModel()
       
{
            saveCommand = new DelegateCommand<string>();
            saveCommand.CanExecuteTargets += () => !string.IsNullOrEmpty(personModel.Name) && !string.IsNullOrEmpty(personModel.Address);
            saveCommand.ExecuteTargets += s => this.Save(s);
        }
        private void Save(string parameter)
        {
            //Save Operation 

            //Clear PersonModel
           PersonModel.Clear();
        }
    }

View를 만들어 볼까요? 귀찮아서 그냥 윈도우로 하겠습니다.
<Window.Resources>
        <DataTemplate  DataType="{x:Type local:PersonViewModel}" >
            <StackPanel>
                <TextBox Text="{Binding PersonModel.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                <TextBox  Text="{Binding PersonModel.Address, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                <Button Content="Save" Command="{Binding SaveCommand}"/>
            </StackPanel>
        </DataTemplate>
        <local:PersonViewModel x:Key="PersonViewModel"/>
    </Window.Resources>
    <Window.Content>
        <StaticResource ResourceKey="PersonViewModel"/>
    </Window.Content>

위와 같이 Template를 사용하기 싫으신 분도 있을 텐데 그러면 다음과 같이 만들어 주면 됩니다. Template를 써야 MVVM Pattern이 되는 것은 아니니까요.  

    <
Window.Resources>
        <local:PersonViewModel x:Key="PersonViewModel"/>
    </Window.Resources>
    <Window.Content>
        <StackPanel DataContext="{StaticResource PersonViewModel}">
            <TextBox Text="{Binding PersonModel.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBox  Text="{Binding PersonModel.Address, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Content="Save" Command="{Binding SaveCommand}"/>
        </StackPanel>
    </Window.Content>
엄청나게 간단한 예제네요^^;; 쉽지요?

Command에 대한 Posting을 이제 끝내려고 합니다. . 생각했던 것 외로 제가 확실하게 몰랐던 부분이 있어 시간이 더 걸렸던 것 같습니다. 그러나 Command에 대한 논의는 이것으로 끝나지 않을 것 같습니다. 사실, 정확하게 WPF가 RoutedCommand를 어떻게 처리하고 CommandManager의 역할은 아직 잘 모르겠습니다. 나중에 살펴보고 제가 명확하게 알았다고 인지했을 때 여러분께 공유하도록 하겠습니다.

사족 :
사실, 예제로 Login을 들려고 했었는데 심각한 문제가 있었습니다. Password를 넣기 위해 사용되는 PasswordBox의 Password Property가 DependencyProperty가 아니라 Binding을 지원하지 않는 문제였지요. 잠시 패닉상태였습니다. ㅋ 이 부분에 대해 google에 Search를 해보니 멋진 글이 있어 소개하려 합니다. Attached Property를 이용해 PasswordBox의 Password를 Binding하는 예가 담겨있습니다. 사람들, 참 머리 좋아요~ WPF PasswordBox and Data binding
그리고 MSDN Magazine의 WPF Apps With The Model-View-ViewModel Design Pattern입니다.
마지막으로 The Build Your Own CAB Series Table of Contents 도 실로 멋지죠.

신고

WPF Command-Part3 : RoutedCommand2

프로그래밍 2009.02.02 20:01 Posted by 아일레프

이번 포스팅도 RoutedCommand가 Invoked되었을 때 RotedCommand 객체가 Command Target을 찾는 과정을 설명하도록 하겠습니다.

Command Target을 찾는 다는 것은 곧 ExecuteEvent Handler를 찾는다는 것을 의미하며, 일반적으로 Command가 Invoke되었을 때 ExecuteEvent 이벤트는 VisualTree내에서 Command를 Invoke를 시킨 Object부터 Tree의 루트까지 타고 올라가게 됩니다.

위와 같은 VisualTree에서 Button1이 특정 RoutedCommand를 Invoke를 했다면 ExecuteEvent는 Button1 -> Grid -> Window로 타고 올라가게 되는 것이고 이는 Button2에서도 마찬가지 입니다. 이것이 가장 일반적인 RouteCommand의 라우팅 메커니즘입니다. 하지만 이 Routing 방법으로는 충분하지 않은 경우가 있습니다. 다음과 같은 경우를 생각해봅시다.

 
위 프로그램은 아래와 같은 VisualTree로 구성 되어있습니다.

코드는 아래와 같습니다.

<Window x:Class="CommandTest.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loacl="clr-namespace:CommandTest"
Title="Window1" Height="300" Width="300">
<Grid
    <Grid.RowDefinitions
        <RowDefinition Height="Auto"/> 
        <RowDefinition/> 
    </Grid.RowDefinitions
    <Menu
       
<MenuItem Header="Cut" Command="ApplicationCommands.Cut"/> 
    </Menu
    <TextBox Height="20" Grid.Row="1"/> 
</Grid>
</Window>

TextBox에 글자를 쓰고 선택한 후 MenuItem의 "Cut"을 누르면 선택된 글자가 잘려지는 것을 확인할 수 있습니다. 그런데 두 가지 이상한 점이 있습니다. 일단 ExecuteEventHandler를 등록하는 CommandBinding객체가 정의 되어있지 않네요. 일반적으로 사용자는 Cut, Copy, Paste Command에 CommandBinding으로 ExecuteEventHandler를 등록할 필요가 없는데요, 이는 TextBox가 기본적으로 Cut, Copy, Paste Command에 대한 CommandBinding객체를 내장하고 있기 때문입니다. 따라서 이 질문은 해결 되었네요. 두 번째로 Cut이벤트가 발생했을 때 ApplicationCommands.Cut Command객체는 어떻게 TextBox의 ExecuteEventHandler를 찾아갈 수 있었을 까요? Visual Tree를 따라가 보면 다음과 같이 Routing이 진행될 것입니다.

MenuItem->Menu->Grid->Window

Tree의 Root에 이를 때 까지 ApplicationCommands.Cut에 바인딩 되어있는 Command Target을 찾지 못했으므로 아무 일도 벌어지지 않아야 할 것 같은데 분명히 "Cut" Command가 잘 동작하고 있으니 이상한 일입니다.
이는 RoutedCommand의 Routing의 두 번째 원칙 때문입니다. RoutedCommand는 CommandInvoker 부터 Tree의 Root에 이를 때 까지 CommandBinding된 객체를 찾지 못하면 CommandInvoker와 Tree Root가 동일한 Focus Scope에 있는지 확인 합니다. 만약 CommandInvoker와 Tree의 Root가 동일한 Focus Scope에 있지 않다면 RoutedCommand는 Tree Root의 Logical Focus를 가지고 있는 컨트롤을 기점으로 해서 다시 Routing을 시작합니다. 즉 다음과 같이 Routing이 되는 것입니다.

첫 번째 Routing : MenuItem->Menu->Grid->Window ---- CommandTarget 찾기 실패 
 
MenuItem과 Window가 동일한 Focus Scope에 있지 않으므로 Window의 Local Focus를 가지는 Control을 기점으로 다시 Routing시작

두 번째 Routing: TextBox -> CommandTarget찾기 성공

위와 같은 방법으로 RoutedCommand는 CommandTarget을 찾을 수 있었던 겁니다. 위의 일이 가능하게 하기 위해서는 MenuItem과 Window가 다른 FocusScope내에 있어야 하는데 WPF에서 Menu와 ToolBar와 같은 Control은 기본적으로 FocusScope을 가지고 있기 때문입니다.

이번에는 다음과 같은 시나리오를 생각해 보겠습니다.
 

<Window x:Class="CommandHandler.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Window1" Height="300" Width="300" >

   

    <Grid>

        <StackPanel>

            <Button Command="ApplicationCommands.Cut">Cut</Button>

            <TextBox Name="textBox"/>

        </StackPanel>       

    </Grid>

</Window>

위 프로그램에서 TextBox에 글자를 쓰고 선택해도 Cut 버튼은 절대 활성화 되지 않습니다.
 
Button -> TextBox->Grid->Window
 
Tree의 Root에 이르기까지 아무런 ApplicationCommands.Cut에 바인딩 되어있는 객체를 찾지 못했으므로 Cut버튼이 활성화 되지 않는 것입니다. 이를 해결하기 위해서는? 예, 맞습니다. Button을 Window와 다른 Focus Scope내에 두어야 합니다. 다음과 같은 코드로 이것이 가능합니다.
<Button Command="ApplicationCommands.Cut" FocusManager.IsFocusScope="True">Cut</Button>
 
다시 프로그램을 실행하면 기대하는 결과를 볼 수 있을 겁니다.
 
Command에 대한 이슈는 이것이 끝이 아닙니다. 앞으로 두 번 정도 더 포스팅 할 계획인데, 다음 포스팅에서는 DelegateCommand라는 것을 소개할 것이고, 그 다음 포스팅에서는 Command가 event에 비해 가지는 장점에 대해 알아보도록 하겠습니다.
신고

WPF Command -Part2 : RoutedCommand

프로그래밍 2009.01.20 21:02 Posted by 아일레프
  • 이 포스트는 기본적인 RoutedCommand의 바인딩과 CommandBinding을 통한 Handler 등록 방법을 알고 있을 때 이해하기 쉽습니다.

이전 Post에서 만든 Command 객체는 치명적인 문제를 가지고 있었죠. 그 문제를 살펴보면

  1. Command Object와 Command Target이 분리되어있지 않다.
  2. Command를 실행했을 때의 작업을 수정하려면 Command 객체를 수정하고 재 컴파일 해야한다.

결국 1,2번 문제 모두가 Command Object와 Command Target이 분리되어있지 않기 때문에 생기는 문제입니다. Command Object와 Target을 분리하는 가장 간단한 방법은 무엇일까요? 예, 맞습니다. 바로 Event를 사용하는 것이죠. Command Object가 명령을 받았을 때의 행동을 직접 정의하는 것이 아니라 단지 Command가 실행되었을 때 Command Object가 이벤트를 발생시키고 해당 이벤트를 구독하는 녀석이 실제 명령을 수행하게 하는 것입니다. 그리고 이 이벤트를 구독하는 녀석이 바로 Command Target이 되는 것입니다. 누구나 쉽게 생각할 수 있는 이 방법을 통해 WPF는 RoutedCommand라는 녀석을 미리 구현해 놓았습니다. 그런데 저에게는 그렇게 쉽지 않더군요 ^^;; 

RoutedCommand라는 녀석의 이름답게 RoutedCommand는 RoutedEvent를 사용합니다. RoutedEvent를 사용해 자신의 Command Target을 찾는 것입니다. 다음과 같은 Visual Object Tree를 생각해봅시다.

 Button에 특정 RoutedCommand가 바인딩 되어있다고 합시다.(Button이 Invoker가 됩니다.) 위Button이 눌리면 RoutedCommand 에서 ExecuteEvent를 발생시킵니다. 이렇게 발생된 이벤트는 Button -> Grid -> Windows 순으로 Routing되는 것입니다. 좀 더 일반적으로 말하자면 Command Invoker부터 Visual Tree Root까지 ExecuteEvent가 Routing됩니다.

여기까지는 쉽죠? 예 쉽습니다.

그러면 적절한 시기에 Command 객체의 "CanExecute" 메소드에 대해 알아볼까요? 이 녀석은 일반적으로 Command가 현재 실행 가능한지 가능하지 Command Invoker에게 알려주는 역할을 합니다. 예를 들어 RoutedCommand에 바인딩 된 Command Invoker객체는 자신이 Render되었을 때 RoutedCommand의 CanExecute 메소드를 실행시킨 뒤 그 결과가 true라면 자신을 활성화 시키고 그렇지 않으면 비활성화 시킵니다.

여기까지도 쉬운가요? 쉬웠으면 좋겠습니다.

 그런데 RoutedCommand에서 사용되는 RoutedEvent와 일반 RoutedEvent와의 차이점은 여기서부터 시작됩니다. 이것에 대해 설명하기 위해 이전 포스트에서 사용했던 맥도날드의 Command Pattern을 살펴보기로 하죠.  

CanExecute

자, 주문하는 사람은 Command Invoker, 웨이터는 Command Object, 햄버거 만드는 사람은 Command Target입니다. 주문하는 사람은 웨이터에게 먼저 이렇게 물어볼 것 입니다. "빅맥 햄버거를 먹을 수 있나요?" 웨이터는 현재 빅맥 햄버거를 만들 수 있는 지 알아야 합니다. 때문에 햄버거 만드는 사람들에게 물어보겠죠. "지금 빅맥 햄버거를 만들 수 있는 사람 있습니까?" 중요한 것은 빅맥 햄버거를 만들 수 있는 사람이 적어도 한 명 있으면 주문하는 사람에게 "예, 지금 햄버거를 만들 수 있어요"라고 말한다는 사실 입니다.

자, 이전에 살펴본 Visual Object Tree입니다. 이전과 마찬가지로 Button에 특정 RoutedCommand가 바인딩 되어있다고 가정해 보겠습니다. Button이 Render되면 자신이 실행 가능한 녀석인지 알기 위해 RoutedCommand의 CanExecute 메소드를 실행합니다. 그리고 RoutedCommand는 CanExecute 이벤트를 발생시키죠. 이 이벤트의 Handler의 시그니쳐는 다음과 같습니다.

void CanExecuteRoutedEventHandler(object sender, CanExecuteRoutedEventArgs e)

이 이벤트는 Button -> Grid -> Windows로 타고 올라갑니다. 그리고 만약 Handler 중에서 CanExecuteRoutedEventArgs의 CanExecute Property를 true로 만드는 녀석이 적어도 하나라도 있다면 RoutedCommand는 Button에게 "난 실행될 수 있는 Command야"라고 알려주게 됩니다. 적어도 하나라는 사실을 꼭 기억해 주시기 바랍니다.  

Execute

자 이제 맥도날드에서 주문을 해볼까요? 여러분은 웨이터(Command Object)를 통해 빅맥을 주문할 수 있다는 사실을 알았습니다.(CanExecute 메소드를 통해) 여러분이 빅맥을 주문하면 웨이터는 햄버거를 만드는 사람에게 햄버거를 만들라고 할 것입니다. 주의 해야하는 사실은 웨이터가 단 한 명의 사람에게 햄버거를 만들라고 해야 한다는 사실입니다. 만약 여러 명에게 햄버거를 만들라고 한다면 주문한 햄버거는 하나인데, 여러 개의 햄버거가 만들어져 있겠죠? RoutedCommand도 마찬가지 입니다. Button이 눌러지면 RoutedCommand의 Execute메소드가 실행되고 RoutedCommand는 ExecuteEvent RoutedEvent를 Command Invoker Node부터 발생시킬 것입니다.

이 경우도 Button -> Grid -> Windows순으로 ExecuteEvent가 발생하게 됩니다. 중요한 것은 특정 Command Target의 ExecuteEventHandler가 해당 Command를 처리한다면 e.handled를 true로 만들어 더 이상 Routing을 진행하지 않는 다는 것입니다. 즉 단 하나의 ExecuteEvent Handler만 실행된다는 사실이죠.

MSDN Magazine의 Understanding RoutedEvent and Command는 이 것을 다음과 같이 설명하고 있습니다.

The difference between routed commands and routed events is in how the command gets routed from the command invoker to the command handler. Specifically, routed events are used under the covers to route messages between the command invokers and the command handlers (through the command binding that hooks it into the visual tree).

There could be a many-to-many relationship here, but only one command handler will actually be active at any given time. The active command handler is determined by a combination of where the command invoker and command handler are in the visual tree, and where the focus is in the UI. Routed events are used to call the active command handler to ask whether the command should be enabled, as well as to invoke the command handler's Executed method handler.

위의 굵은 글씨로 표시된 부분이 바로 이를 설명하고 있는 것입니다.

위의 글에는 몇 가지 추가적인 내용이 있는데요, CommandHadler를 선택할 때 CommandInvoker의 VisualTree내의 위치와 현재 Focus가 어느 UI에 있는 지를 고려한다고 합니다. 이 부분은 다음 포스팅에서 설명하도록 하겠습니다.

신고

WPF Command - Part1

프로그래밍 2009.01.12 19:18 Posted by 아일레프

들어가기 전에

얼마 전에 MSDN MagazineUnderstanding RoutedEvent and Command라는 제목의 article이 올라왔습니다. RoutedEvent Command는 자주 사용했던 기능이기에 깊게 읽어보지는 않았습니다. “이 정도는 나도 알고 있다고라는 마음이었죠. 몇 줄 읽어보고 말았습니다..

그 후 Composite Application Guidance for WPF를 공부하기 시작했는데 “DelegateCommand<T>”라는 녀석이 나오더군요. 정말 전혀 모르겠더군요. Command에 대해 정확한 개념이 없다는 것을 알게 된 후 MSDN Magazine Command 부분을 다시 읽어 보았는데 영어가 암호문처럼 느껴지면서 적지 않은 절망감을 느꼈습니다. Article을 이해하기 위해 출퇴근 하는 지하철 안에서 몇 번을 읽었는지 모릅니다. 그리고 집에 와 Command Pattern에 대한 자료와 예제를 반복해 보았습니다. 만약 여러분이 저와 같은 고생을 하지 않고 간단히 WPF Command를 책하나 혹은 MSDN만으로 이해했다면 여러분을 낳아주신 부모님께 감사드릴 일입니다. 그리고 이 Post를 더 이상 읽을 필요도 없을 것입니다.

그러나 Command라는 노래만을 듣고 악보를 그려낼 만할 음감은 없지만 알고 싶다는 호기심과 끈기가 있는 분이라면 이 Post를 읽어주셨으면 합니다.(저는 이런 분들을 더 좋아합니다.) 여러분께 조금의 도움은 될 수 있을 것입니다.

서론은 그만두고, 본격적으로 포스팅을 하도록 하겠습니다. 바라건대 기본적인 RoutedCommand 사용법(CommandBinding, InputBinding) RoutedEvent를 공부하신 후 Magazine Article Understandeing RoutedEvent and Command을 읽어보시기 바랍니다. 그리고 궁금한 부분이나 도무지 풀리지 않을 때 이 포스트를 읽으시면 될 것 같습니다. 그럼 시작해보겠습니다.(해당 Article이 이해가 완전히 되시는 분은 이 포스트을 읽지 않으셔도 됩니다.)

 

Command Overview

RoutedCommand에 대해 알아보기 전에 Command에 대해 알아보도록 하겠습니다. Command1-특정 작업을 서술하는 녀석 그리고 그 2-특정 작업을 요청하는 녀석과 3-특정 작업을 처리하는 녀석을 분리하기 위해 그 필요성이 생겼습니다. ( 1,2,3번은 중요합니다. 모든 커맨드를 사용할 때 항상 1,2,3번에 해당하는 녀석이 무엇인지 마음속으로 추측 해보시기 바랍니다.)

보통 특정 작업을 서술하는 녀석을 Command Object라고 합니다. 그리고 특정 작업을 요청하는 녀석은 Command Invoker 혹은 Command Source, 특정 작업을 처리하는 녀석을 Command Target이라고 합니다. 정리하고 넘어가죠.

1.     특정 작업을 서술하는 녀석 – Command Object

2.     특정 작업을 요청하는 녀석 – Command Invoker, Command Source

3.     특정 작업을 처리하는 녀석 – Command Target


 여러분이 맥도널드에서 특정 햄버거를 주문한다고 가정해보죠. 여러분은 Command Invoker가 되는 것이고, 햄버거 주문을 받는 웨이터는 Command Object, 직접 햄버거를 만드는 사람은 Command Target이 되는 것입니다. 여러분이 햄버거를 주문했을 때 그 내부에서 햄버거를 어떻게 만드는 지 전혀 알지 못해도 되는 이유는 맥도널드가 이 Command Pattern을 사용하고 있기 때문입니다.

 

잠깐 MSDN Article의 예를 보고 넘어가도록 하겠습니다.

<Button Command=”ApplicationCommands.Save”>SAVE</Button>

 

<UserControl …>

           <UserCotrol.CommandBindings>

                     <CommandBinding Command=”ApplicationCommands.Save”

                       CanExecute=”OnCanExecute” Executed=”OnExecute”/>

           </UserControl.CommandBindings>

 

Public class MyControl : UserControl

{

           Private void OnCanExecute(object sender, CanExecuteRoutedEventArgs e)

        {

                   e.CanExecute = true;

           }

 

           Private void OnExecute(object sender, ExecuteRoutedEventArgs e)

        {

                   DoSaveOperation();

           }

           Private void DoSaveOperation()

        {

                  

           }

}

위 예에서 1,2,3번에 해당하는 것이 무엇인지 알아보도록 할까요?

1.     Command Object : “ApplicationCommands.Save” static Command 객체

2.     Command Invoker : Button 객체

3.     Command Target : MyControl 객체

 
 맞추셨습니까? 너무 쉽다 구요? ^^;;; 다행입니다. 이것이 기본입니다. 다시 강조 드리지만 반드시 1,2,3번에 해당하는 녀석이 무엇인지 머리 속으로 쭉 생각하시기 바랍니다.

더 나가서 위 예제가 무엇을 하는 녀석인지 볼까요? WPF에는 미리 정의된 Command객체들이 있습니다. ApplicationCommand.Save도 그 중 하나입니다. Button을 누르면(Invoker가 작업을 요청하면) ApplicationCommand.Save Command Target을 찾습니다.(어떻게 찾는지는 다음 Posting에 설명드리겠습니다.) Command Target으로 MyControl 객체가 선택되고 DoSaveOperation()을 실행합니다.

 보시다시피 작업을 요청하는 녀석과 작업을 기술하는 녀석, 작업을 실행하는 녀석이 분리되어있습니다. 이런 것을 CommandPattern이라 부른답니다.

 

Command 만들어보기

위 예제의 세부적인 내용은 일단 미뤄두고 Command객체를 만들어 보도록 하겠습니다. Command 객체를 만들기 위해서는 ICommand라는 Interface를 상속받으면 됩니다. Interface는 이렇게 생겼습니다.

public interface ICommand

    {

event EventHandler CanExecuteChanged;

bool CanExecute(object parameter);

void Execute(object parameter);

    }

CanExecuteChanged이벤트와 CanExecute메소드는 잘 모르더라도 Execute 메소드의 역할은 알 것 같죠? 아마도 Invoker가 이 Command Invoke하면 Execute가 실행될 것 같습니다. 이 정도의 정보만으로도 충분합니다. 한번 AddCommand라는 녀석을 만들어보겠습니다.

public class AddCommand : ICommand

    {

        private string description ="Add";

        public string Description

        {

            get { return description; }

        }

        public bool CanExecute(object parameter)

        {

            return true;

        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)

        {

            System.Diagnostics.Debug.WriteLine("Add명령 실행");

        }

    }

CanExecute메소드는 일단 true를 반환하게 하고 Execute 메소드는 “Add명령 실행이라는 문자열을 출력하게끔 했습니다. 그리고 Description이라는 Property를 만들어 Command의 내용을 기술하게 했습니다. , 그럼 이 Command를 사용해볼까요?

<Window x:Class="CommandTest.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:loacl="clr-namespace:CommandTest"

    Title="Window1" Height="300" Width="300">

    <Window.Resources>

        <loacl:AddCommand x:Key="AddCommand"/>

    </Window.Resources>

    <Button Command="{StaticResource AddCommand}"

            Content="{Binding RelativeSource={RelativeSource Self}

                              ,Path=Command.Description}"></Button>

</Window>

먼저 Command객체를 XAML로 생성하기 위해 local이라는 prefix namespace를 추가했습니다. 그리고 객체를 생성해 Resources에 등록 했습니다. . 제가 여러분을 너무 무시하나요? 나머지는 그냥 스킵 하겠습니다. 하여간 위 프로그램을 실행하면

 

위와 같은 창이 생기게 되고 Add버튼을 누르면 “ADD 명령 실행이라는 문자열이 Output창에 출력될 것 입니다. 훌륭하군요. 제가 생각하는 결과가 실행되었습니다. 혹시 글을 읽으시면서 Command Object, Command Invoker, Command Target이 무엇인지 생각해보셨습니까? 그랬다면 여러분은 멋진 분입니다. 1,2,3에 해당하는 것이 무엇이지 알아봅시다.

1.     Command Object : AddCommand 객체

2.     Command Invoker : Button 객체

3.     Command Target : AddCommand 객체

? Command Object Command Target이 같네요. 분리되지 않았습니다. 게다가 방금 제가 만든 Command객체는 Command를 실행한 후의 작업이 항상 “Add 명령 실행이라는 문자열을 실행하게 됩니다. 이 작업을 수정하려면 Command객체를 수정하고 다시 컴파일 해야겠네요. 이건 전혀 훌륭하지 않습니다. 망했습니다. .

n  문제 : 어떻게 Command Object Command Target을 분리하지?

다음 장에는 WPF에서 RoutedCommand를 사용해 어떻게 Command Object Command Target을 분리하는 지 알아보도록 하겠습니다. 여러분도 이 문제를 어떻게 해결할 지 생각해보세요 ^^;;


신고
TAG Command, WPF

CodeSafe Hosting Service

분류없음 2009.01.10 21:18 Posted by 아일레프

http://www.randsinrepose.com/archives/2004/07/10/what_to_do_when_youre_screwed.html
위 링크는 조엘이 엄선한 소프트웨어 블로그 베스트 29선 책에 소개된 article입니다. 한국 책에서는 위 Article의 제목을 "엉망 진창 꼬여버린 상황 돌파하기" 라고 멋지게 의역했네요.

저자는 위 글에서 일이 꼬인 상황을 알려주는 몇가지 증상에 대해 살펴보고 그 문제를 해결할 수 있는 방법을 소개하고 있습니다.

첫번째 증상은 "작성하지 않은 문서를 사람들이 요청하는 것"
두번째는 "우리 팀에는 중요한 개발 툴이 없습니다."
세번째는 "관리자와 일하기가 힘들어요, 관리자가 없어요."
등 이 있습니다. 

위 증상들 가운데  두번째 증상 "우리 팀에는 중요한 개발 툴이 없습니다." 라는 글에서 글쓴이는 소프트웨어 팀에게 반드시 필요한 4가지 툴을 소개하고 있지요.

첫째는 편집기
둘째는 컴파일러
셋째는 버전 컨트롤
넷째는 버그 추적 입니다.

편집기와 컴파일러가 없는 소프트웨어 팀은 있을 수가 없기에 글쓴이는 버전 컨트롤과 버그 추적툴의 필요성에 대해 몇가지 사례를 나열합니다. 그리고 해결방법을 제시하지요. "버전 컨트롤과 버그 추적툴을 설치하세요"

서론이 너무 길었나요? 예, 그렇습니다. 저희 회사에서 TFS 호스팅 서비스인 "CodeSafe"를 시작했습니다. ^^;;
위 서비스는 해당 TFS를 필요로 하지만 꽤 나가는 라이선스 비용이 부담스럽고 TFS를 관리하는 것 또한 만만치 않기에 구입을 하지 못하는 회사 혹은 사용자들을 위해 만들어졌습니다. 해외에는 2곳정도가 있지만 한국에는 저희 회사가 최초라고 하네요. CodeSafe의 자세한 설명은  안재우 수석님의 블로그, 그리고 이건복 사장님의 블로그를 보시면 될 것 같습니다. 아무쪼록 많은 개발자들에게 도움이 되었으면 좋겠습니다. ^^

신고

WPF Multiple Namespace mapping

프로그래밍 2008.12.16 22:11 Posted by 아일레프

.. 제목이 적당한지는 잘 모르겠네요. 예전부터 사실 궁금한 것이 있었는데~

 

왜 보통 WPF를 사용할 때 XAML에서 namespace 선언 하지 않습니까?

 

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 

default namespaceWPF 기본 Control들을 사용하기 위한 namespace이고,

x:prefix로 사용하는 namespace XAML 키워드를 사용하기 위한 namespace이지요.

 

여기에 사용하고 싶은 namespace를 추가할 수도 있지요. 가령 mscorlib System namespace를 사용하고 싶다면 xmlns:system="clr-namespace:System;assembly=mscorlib" 요로코롬 사용해주면 되구요. 여러분이 직접 만든 namespace도 위와 같은 식으로 선언해 XAML에서 여러분의 Object를 선언해 줄 수도 있습니다.

 

그런데 clr-namespace:namespace;assembly=assemblyName과 같은 방법 말고 http:// 사용해서 정의할 수는 없을 까요? 왠지 뽀대나는 느낌이 들 뿐아니라 편리하기도 할 것 같은데요,

예를 들어 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" namespace에는 하나 이상의 namespace mapping되어 있습니다. 이와 같이 여러 namespace http namespace를 사용 해 묶어서 정의 할 수있으면 좋을 것 같은데 말이죠.

 

당연히 방법이 있겠죠? 여러군데 찾아 보았지만 발견하지 못했는데 WPFToolKit 소스 코드에 해답이 있더군요. assemblyInfo에 다음과 같이 정의되어 있더군요.

 

 

[assembly:XmlnsDefinition("http://schemas.microsoft.com/wpf/2008/toolkit", "Microsoft.Windows.Controls")]

[assembly: XmlnsDefinition("http://schemas.microsoft.com/wpf/2008/toolkit", "Microsoft.Windows.Controls.Primitives")]

오호라~ 요녀석이군요. 캬캬캬

 

Microsoft.Windows.Controls라는 namespaceMicrosoft.Windows.Controls.Primitives라는 namespacehttp://schemas.microsoft.com/wpf/2008/toolkit 에 매핑하고 있네요. 시험삼아 저도 해보았습니다.

 

Project를 생성하고 AssemblyInfo.cs에 다음을 추가해보았죠

[assembly: XmlnsDefinition("http://illef.tistory.com", "MyProject")]

그리고 이 프로젝트를 다른 프로젝트에서 참조하고 XAML에서 해보니~ 오오 인텔리센스로 http://illef.tistory.com이 튀어 나오네요~


혹시 msdn에 떡하니 나와있는 내용이라면 난 챔피언!!

신고

ListView Sorting

프로그래밍 2008.12.15 21:36 Posted by 아일레프

MSDN이나 여타 블로그를 보면 ListView Colum값을 기준으로 Sorting 하는 방법이 나옵니다.
List
의 순서는 그대로 두고 Row들을 Sorting하는 것 같은데요, 만약 List의 순서를 보존해야 할 필요성이 없다면 List자체를 Sorting하는 것도 한 방법일 것 같습니다
.

일단 Sort를 구현 해야 겠죠? 보통 ListView ItemsSource BindingList를 많이 사용하기에 확장 메소드로 QuickSort라는 녀석을 만들었습니다.


public static class Sort

    {

public static void QuickSort<T>(this BindingList<T>  list,Comparison<T> comparer)

        {

            QuickSort<T>(list, comparer, 0, list.Count-1);

        }

 

        private static void QuickSort<T>(BindingList<T> list,Comparison<T> comparer, int startIndex, int endIndex)

        {

            if (startIndex < endIndex)

            {

                int q = Partition<T>(list, comparer, startIndex, endIndex);

                QuickSort<T>(list, comparer, startIndex, q - 1);

                QuickSort<T>(list, comparer, q + 1, endIndex);

            }

        }

 

        private static int Partition<T>(BindingList<T> list,Comparison<T> comparer, int startIndex, int endIndex)

        {

            T item = list[endIndex];

            int i = startIndex - 1;

            for (int j = startIndex; j < endIndex; j++)

            {

                if (comparer(list[j], item) <= 0)

                {

                    i++;

                    Switch<T>(list, i, j);

                }

            }

            Switch<T>(list, i + 1, endIndex);

            return i + 1;

        }

 

        private static void Switch<T>(BindingList<T> list, int i, int j)

        {

            T item = list[i];

            list[i] = list[j];

            list[j] = item;

        }

    }

 
오랜 만에 보는 Quick Sort의 아름다움이란 ㅜ.ㅜ

이제 Sorting을 해주면 됩니다.

그런데 BindingList를 Sorting하는 것 만으로는 ListView의 Row순서가 바뀌지 않습니다. 이 때는 다음과 같이 CollectionView를 Reflesh해주면 됩니다.

ICollectionView collectionView = CollectionViewSource.GetDefaultView(monitoringView.ItemsSource);

collectionView.Refresh();





 

신고

WPF Object Query ~

프로그래밍 2008.12.15 21:14 Posted by 아일레프

JQuery라는 것을 들어 보셨겠지요? 전 이름은 들어 보았는데 어떤 역할을 하는 놈인지는 어제 알았습니다. 그 중에서 어제 살펴본 것은 JQuery 중에서도 가장 기본적인 기능을 하는 셀렉터였는데요, 그 중 $(“:text”)라고 하는 녀석은 모든 text boxes를 선택합니다.

 

$(“:text”).css(“background-color”, “yellow”);

 

위 명령은 모든 Text Boxes 들의 background ColorYellow로 만듭니다.

 

이 정도는 WPF로 쉽게 만들 수 있겠네요 

public static class WPFQueryExtension{

        public static ElementList<T> SelectObject<T>(this DependencyObject current)

where T : DependencyObject

        {

            ElementList<T> elements = new ElementList<T>();

            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)

            {

                DependencyObject obj = VisualTreeHelper.GetChild(current, i);

                if (obj is T)

                {

                    elements.Add(obj as T);

                }

                elements.AddRange(obj.SelectObject<T>());

            }

            return elements;

        }

    }

 

    public class ElementList<T> : List<T> where T : DependencyObject

    {

        public void PropertySet(string propertyName, object value)

        {

            foreach (T obj in this)

            {

                obj.GetType().GetProperty(propertyName).SetValue(obj, value, null);

            }

        }

    }

 

조건 문을 추가 하고 싶으면

 

public static ElementList<T> SelectObject<T>(this DependencyObject current, Func<T, bool> func) where T : DependencyObject

        {

            ElementList<T> elements = new ElementList<T>();

            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)

            {

                DependencyObject obj = VisualTreeHelper.GetChild(current, i);

                if (obj is T)

                {

                    if (func(obj as T) == true)

                    {

                        elements.Add(obj as T);

                    }

                }

                elements.AddRange(obj.SelectObject<T>(func));

            }

            return elements;

        }

 

 

위의 코드는 아래와 같이 사용 가능 합니다.

 

this.SelectObject<TextBlock>().PropertySet("Text", "test");

this.SelectObject<TextBlock>(s => s.Text == "test").PropertySet("Text", "11");

foreach (TextBlock t in this.SelectObject<TextBlock>().Where(s => s.Text == "11"))

{

t.Text = "33";

}

.. 왠지 너무 시시하네요.. ㅜ.ㅜ 더 재미있는 것도 할 수 있을 것 같은데 ...

신고

영화 행복

분류없음 2008.11.21 23:17 Posted by 아일레프


한 없이 울게 만드는 그런 영화가 있다. 몇 년전에 "번지 점프를 하다"란 영화를 보고 몹시도 울었다. 남들이 들으면 부끄러웠을 만큼 꺼이 꺼이 울었는데 오늘 그런 영화를 또 만났다. "봄날은 간다"란 명작을 만든 허진호 감독의 영화 "행복"이다.

이 영화를 내가 4개월 전에 보았다면 "이런 진부하고 뻔한 영화를 만들다니"하며 비웃음을 지었겠지만 오늘 그 영화는 날 방안이 떠나가게 큰 소리로 울게 만들었다.

날 울게 만든 장면은 영수가 은이에게 이별을 말하던 장면이다. 은이는 "개새끼"라며 방안으로 들어가 운다. 그런 은이를 달래려 영수가 들어가자 은이는 영수에게 빌며 그러지 말라고 말한다. 자기와 계속 살자고. 영수는 고개를 저으며 그럴 수 없다고 하고 지친듯 술과 잠에 취해 쓰러져 버린다.

"봄날은 간다"에서 감독이 말했듯 떠나간 사랑과 버스는 잡을 수 없는 법이기에 그리고 은이도 그 사실을 알고 있었기에 영수가 잠든 밤 은이는 자신이 영수를 보내줘야 한다는 사실을 스스로 잘 알고 있었을 것이다.

그러나 은이는 영수에게 가라는 말을 하기위해 그 만큼의 고통을 또 한번 겪어야 했고 영화는 은이가 자신의 마음을 정리하는 그 짧은 시간을 처절하게 보여주었다.(이 장면은 글로 설명이 불가능하다) 그리고 날 울게 만들었다.

그 울음은 미안함이었다. 나로 인해 그 만큼의 눈물을 흘려야 했을 이에 대한 미안함이었다. 그 이가 그 때 겪었을 그 아픔을 난 겨우 이해만 할 수 있을 뿐 완전히 알지는 못한다. 그래서 또 미안하다.

계절은 다시 돌아오지만 지나간 버스와 떠난 사랑은 돌아오지 않는다. 시간 역시 한번 지나면 돌릴 수 없다. 사람이 만약 인생을 두번 살 수 있다면 지금 보다 더 나은 삶을 살 수 있을 텐데 냉혹하게도 신은 우리에게 연습도 허락치 않은 채 단 한번만의 삶만 허락했다. 때문에 가장 가슴 아픈 말의 종류는 이런 것들. "지금 알았던 것을 그 때 알았었더라면", 혹은 "가장 가슴 아픈말, 그것은 그 때 그랬었더라면..." 영수의 눈물에는 그런 것들이 담겨있지 않을까?


그러나 영화의 마지막에 영수는 "희망의 집"으로 다시 돌아갔다. 나도 희망의 집으로 가야겠다. 영수의 희망의 집에는 은이가 없지만 나의 희망의 집에는 그녀가 있기에 난 운이 좋다.

행복의 나라로 가자.

신고

Heart and Head

분류없음 2008.11.14 22:51 Posted by 아일레프

가슴과 머리 by Paul Krugman

유럽 사람들이 오래 전부터 하는 말 중에 다음과 같은 말이 있다. 30세 이전에 사회주의자가 아니면 가슴이 없는 사람이고, 나이 30이 넘어서도 사회주의자인 사람은 머리가 없는 사람이라고. 이를 요즘 세상에 적용하면 바로 반세계화운동이 여기에 해당될 것이다. 반세계화운동은 1999년 미국 시애틀에서 큰 반향을 일으켰고, 이번 주말 캐나다 퀘벡시티에서 열리는 미주 정상회담을 방해하려고 최선의 노력을 기울이고 있다.

세계화가 항상 아름다운 것은 아니다. 만약 당신이 제3세계에서 만들어진 어떤 물건을 산다면 아마 그 물건은 서방 세계의 기준으로 볼 때는 말도 안 되게 낮은 임금을 받는 근로자가 매우 나쁜 근로 조건 아래서 만든 물건일 것이다. 어떤 사능성에 대해 한 번도 걱정해본 적이 없는 사람은 가슴이 없는 사람이다.

그러나 그것이 반세계화 시위대가 옳다는 의미는 아니다. <오히려 그 반대다> 국제무역에 대한 분노와 반대로 범세계적인 빈곤 문제가 해결될 수 있다고 생각한다면 그 사람은 머리가 없는 사람이거나, 머리 쓰기를 싫어하는 사람이다. 반세계화 운동을 하는 사람들이 이미 그들이 보호하고자 하는 사람들과 명분을 훼손하고 있다는 충분한 증거와 기록이 있다.

어린이 노동을 착취하는 것보다 나쁜 일이 있을까? 안타깝게도 있다. 1993년에 방글라데시에서 어린아이들이 월마트에 공급되는 의류를 만드는 현장이 공개된 적이 있었다. 이에 미국 상원의 톰 하킨 의원이 미성년 노동자를 고용하는 국가에서 만든 물건에 대해 수입을 금지하는 법안을 제출하였다. 이 법안의 직접적인 결과로 방글라데시의 의류 공장들이 어린이 고용을 중단했다.

그렇다면 그 어린이들은 학교로 돌아갔을까? 그 어린이들이 행복한 가정으로 돌아갔을까? 옥스팜의 조사에 따르면 그렇지 않은 것으로 나타났다. 해고된 어린이들은 더 열악한 일자리로 갔거나 아예 길거리에서 노숙하는 신세가 되었다고 한다. 또 상당수의 어린이들은 매춘을 강요당했다고 한다.

여기서 강조하고 싶은 것은 제3세계 국민이 가난한 이유가 그 나라의 수출춤 제조 근로자들이 낮은 임금을 받아서가 아니라는 점이다. 오히려 그 반대다. 그 나라가 워낙 가난하니까 서방 세계의 기준으로 볼 때는 형편없이 낮은 임금을 주는 나쁜 일자리라도 거기서는 다른 일자리보다는 아주 좋은 일자리인 것이다. 수백만명의 멕시코인들이 멕시코 북부 미국 접경 지역으로 몰려오는 것은 바로 NAFTA를 반대하는 사람들을 분노케 하는 저임금의 멕시코 수출품 제조공장에서 일하기 위함이다.

만약 그들이 더 높은 임금을 바란다면 그런 일자리마더 없을 것이다. 낮은 생산성, 열악한 사회시설, 사회적 혼란과 같은 요고사 가난한 나라를 가난하게 만드는 것이다. 따라서 이런 조건 아래 있는 나라들이 세계 시장에서 경쟁하려면 서방 세계의 근로자들보다 낮은 임금을 받을 수밖에 없는 것이 현실이다.

물론 반세계화운동가들도 이런 이야기는 들었을 것이고, 이에 대한 해답도 가지고 있다. 나는 지난주 어떤 학술 모임에서 어떤 사람이 현대화된 도시 생화보다 전통적인 농촌 생활이 얼마나 아름답고 좋은지를 강조하는 것을 보았다. 이런 소리는 많은 후진국에서 수많은 농민들이 기회만 되면 도시 일자리를 찾아 도시 지역으로 탈출하려고 시도하고 있다는 명백한 사실에 대한 외면일 뿐 아니라, 반세계화 시위 군중의 절대 다수가 백인들이라는 점을 볼 때 문화적 교만함마저 베어 있는 것 같다. (여러분은 원시 농경마을에서 살고 싶겠는가?)

또 제3세계 농촌 빈곤은 주로 다국적 기업들 때문이라는 소리도 들었다. 이것은 한마디로 잘못된 생각이지만, 세계화는 어떤 이유에서든 나쁜 일이라고 생각하는 사람들에게는 아주 편리한 주장이다. 가장 그럴듯한 주장은 반세계화운동이 수출을 금지시키는 것이 아니라, 단지 제3세계 근로자들에게 높은 임금과 좋은 근로 조건을 제공하도록 하기 위한 것이라는 점이다.

그러나 이것은 귀담아들을 만한 주장은 아니다. 제3세계 국가들은 수출산업이 절대적으로 필요하다. 그 나라들이 어떤 상상속의 농경 낙원으로 돌아갈 수는 없다. 그들 나라의 수출품이 서방 세계 국민들이 놀랄 만한 근로 조건과 낮은 임금 속에서라도 만들어져 수출되지 못한다면 그들 나라의 수출산업은 존재할 수 없다. 그리고 이것이 바로 반세계화운동가들이 받아들이지 못하는 사실이다.

그렇다면 누가 진정 나쁜 사람들인가? 수천 명의 경찰과 경호 철조망 속에 갇힌 각국의 지도자들과 밖에서 아우성치는 분노한 대중. 반세계화운동가들은 지금 퀘벡시티에서 그들이 원하는 이미지를 만들고 있다. 그러나 이미지가 진실일 수는 없다. 경찰 경호 철조망 속에 갇혀 논의하고 있는 사람들은 전세계의 빈민들을 돕기위해 짐심으로 노력하는 사람들이다. 그리고 철조망 밖에서 아우성치는 사람들은 그들의 의도가 무엇이든지 가난한 사람들을 더 가난하게 만들기 위해 최선을 다하고 있는 것이다.

- The New York Times, 2001. 4. 22, 17면, Paul Krugman (폴 크루그먼) -
신고

XAML Markup Extension

프로그래밍 2008.11.13 19:53 Posted by 아일레프

Dependency Property에 이어서 Markup Extension에 대한 글을 써 보려 합니다. 아래 적은 내용은 모두 MSDN에서 제공하는 내용을 단지 쉽게(?) 풀어 쓴 것에 지나지 않습니다.

사실 XAML Markup Extension은 XAML에서 특정 Object의 Property의 값을 대입하는 하나의 방법에 지나지 않습니다. 이 방법에는 크게 3가지가 있는데 다음과 같습니다.

  1. Using Property Element

<Button>
  <Button.Background>
   
<SolidColorBrush Color="White" />
 
</Button.Background>
</Button>

       2.   Using XML Attribute

<Button Background="White" />

       3.    Using Markup Extension

<Button Background="{StaticResource MyBrush}" />

1번의 경우 Button의 Background Property에 SolidColorBrush Object를 생성해 대입하는 것을 알 수 있습니다. Button button = new Button { Background = new SolidColorBrush(Colors.White) }; 와 같이 처리가 되겠네요.

2번의 경우는 Background에 "White"라는 문자열이 대입되었습니다. Background는 Brush Type인데 String Type에 해당하는 문자열이 대입되었기에 XAML Processor는 다음과 같은 처리 방법에 따라 Property에 적당한 값을 대입하게 됩니다.

  1. Property의 Type이 primitive Type인가? - primitiveType.Parse(string)을 호출 해 대입
  2. Property의 Type이 Enumeration Type인가? – Enum.Parse(typeof(EnumerationType, string)을 호출해 대입
  3. Property의 Type에 대응되는 TypeConverter가 존재하는가? – TypeConverter.ConvertFrom(string)을 호출해 대입

Using Property Element 와 XML Attribute를 사용해 Property의 값을 대입하는 방식에는 커다란 공통점이 있습니다. 만약 Property의 Type이 primitive Type이 아니라면 "생성"과정, 즉 내부적으로 "new" 키워드를 부르는 연산을 수행한다는 것입니다. 그런데 만약 "생성"과정이 아닌, 이미 존재하는 Object에 대한 참조를 Property에 대입하고 싶다면? 아쉽게도 1번과 2번의 방식으로는 결코 Object에 대한 참조를 대입할 수 없고 이런 방식을 고안 할 수 도 없습니다.

  • 기존에 존재하는 Object의 참조자를 Property에 대입하고 싶을 때 기존의 XML문법으로는 이 방법을 고안하기가 쉽지 않다.(적어도 WPF Team은 찾지 못했다.) 이 용도로 사용되는 XAML MarkupExtension의 가장 큰 예는 StaticResource, DynamicResource, TemplateBinding 등이 있습니다.

또 Property에 대입할 Object가 여러 개의 Constructor Argument를 필요로 하거나 혹은 Property 값 설정을 필요로 한다면? 즉 이런 경우입니다.

Button button = new Button { Background = new MyBrush(parameter1, parameter2) }; 혹은

Button button = new Button {Background = new MyBrush { Color=Colors.White, Offset=1}; ( 물론 MyBrush는 Brush를 상속받은 객체이겠지요)

이 경우는 Property Element를 사용하면 해결할 수 있을 수도 있어 보입니다.

<Button>
 
<Button.Background>
    
<my:MyBrush Color="White" Offset="1"/>
 
</Button.Background>
</Button>

 

그러나 XML Attribute 만을 사용해서는 이런 동작을 기존의 XML 문법만을 이용해서 구현하기는 힘들 것 같군요.

  • Property에 대응되는 Object가 하나 이상의 생성 parameter를 필요로 하거나 Property 설정을 필요로 할 때 기존의 XML의 Attribute문법으로는 이 방법을 고안하기 쉽지 않다. 이 용도로 사용되는 XAML MarkupExtension의 가장 큰 예는 Binding이 있습니다.

결국 Microsoft는 기존의 XML 문법을 확장할 수 밖에 없게 되었는데요 그것이 바로 XAML Markup Extension입니다. Extension에는 XML문법에서 확장했다는 의미가 포함 되어 있고 '{'로 시작하고 '}'로 끝나게 됩니다. 이 Markup Extension은 MarkupExtension클래스를 상속받고 ProvideValues라는 abstract Method를 Override해주는 것으로 구현됩니다.

<Button Background="{StaticResource MyBrush}" />
위의 XAML 은 코드는 Button Button button = new Button{Background = new StaticResourceExtension("MyBrush").ProvideValues()}; 와 같은 느낌으로 해석될 수 있습니다.

자주 사용하는 "{x:Null}" Extension은 어떨까요?

<Button Background="{x:Null}">

위 XAML은 Button button= new Button { Background = new NullExtension().ProvideValues() };로 해석될 수 있겠네요. 쉽지요? ^^;;

{x:Null}은 단순히 null을 return하기에 다음과 같이 직접 NullExtension 을 구현할 수도 있습니다.

public class NullExtension : MarkupExtension
{
  
public override object ProvideValue(IServiceProvider serviceProvider)
  
{
      
return null
   
}
}

정말 쉽네요.

StaticResourceExtension은 내부적으로 FindResource를 부르기 때문에 아마도 다음과 같이 구현될 것 같습니다.

public class StaticResourceExtension : MarkupExtension{
   
private object resourceKey;
   
public StaticResourceExtension(object key){
        
resourceKey= key; 
    
}
    public override object ProvideValue(IServiceProvider serviceProvider){ 
       
FindResource(resourceKey); //이 코드는 동작하지 않습니다. 단지 이런 느낌일 것 같다는 추측;;
    }
    public object ResourceKey
   
{
       
get { return resourceKey;} 
        
set { resourceKey =value;}
    
}
}

위에 Constructor Parameter를 사용하고 있다는 것을 확인해 주시기 바랍니다. 만약 {StaticResource MyBrush}로 사용되었다면 MyBrush가 Constructor Parameter가 되는 것입니다. 또한 StaticResourceExtension은 ResourceKey라는 Property도 가지고 있습니다. 이 Property를 통해서도 StaticResoure를 사용할 수 있는데 {StaticResource MyBrush}는 {{StaticResource ResourceKey=MyBrush}와 동일한 의미를 가지는 XAML 코드입니다.

MarkupExtension은 Object이기 때문에 Property Element를 사용해서 XAML을 구성할 수도 있습니다. <Button Background="{StaticResource MyBrush}" />은 다음과 같이 사용할 수도 있다는 것이죠.

<Button >
   
<Button.Background>
         
<StaticResource ResourceKey="MyBrush" /> 
    
</Button.Background>
</Button>

단 보통 Propery Element를 사용한 것의 차이점은 Background Property에 StaticResourceExtension Object가 대입되는 것이 아니라 ProvideValues() 메소드가 불린 결과가 대입된다는 것입니다. , 즉 Background = new StaticResource{ResourceKey="MyBrush"};로 Processing되는 것이 아니라 Background = new StaticResource{ResourceKey="MyBrush"}.ProvideValues()로 Processing된다는 것입니다.

 

기존의 Microsoft가 제공하는 XAML의 기능에 한계를 느낀다면 직접 MarkupExtension을 구현해보는 것도 재미있는 일이될 것 같습니다.

 

사족 : ProvidesValue의 함수에는 IServiceProvider라는 parameter가 들어가는 데요, 이 것으로 여러가지 Service를 사용할 수 있습니다. (저도 자세히 모르겠어요 ㅜ.ㅜ) 가령 앞서 구현한 StaticResourceExtension클래스를 보면 ProvideValue내에서는 FindResource를 사용할 수 없습니다. (FindResource method를 멤버 method로 가지고 있지 않기때문이죠) 이럴 경우에 serviceProvider를 사용하면 여러 메소드를 사용할수 있습니다. 거듭 말씀드리지만 이부분은 저도 잘 몰라서 혹시 이 부분에 대한 지식이 있으신 분들은 공유해주셨으면 합니다.

 

감사합니다.

신고

WPF에서 가장 중요하고도 이해하기 힘든 부분이 이 Dependency Property인 것 같습니다. WPF로 프로젝트를 진행하면서 가장 이해안가는 부분이 이 Dependency Property의 선언이었습니다. MSDN과 기타 WPF책들을 보면 Dependency Property를 public static readonly로 선언하라고 "권장" 하고 있습니다. 어떤 책에는 "관습"이란 단어로 표현하기도 하더군요. 그 이유가 너무 궁금해 Dependency Property를 private 로 선언한 후 사용해 본 적도 있습니다. 그런데 잘 되더군요. ^^;;; 그렇다면 왜 Dependency Property를 public static readonly로 선언하라고 권장하는 것일까요?

어떻게static으로 선언해도 서로 다른 Object는 다른 값을 가질 수 있지?

일단 static으로 선언한다는 것이 가장 이해가 잘 안 갔습니다. Static 으로 선언된 멤버는 정적 메모리에 단 하나만 존재하게 됩니다. Button을 예로 들어 볼까요? Button등 Control에는 NameProperty라는 Dependency Property가 있습니다. 물론, static으로 선언 되어 있지요.

Button button1 = new Button { Name = "button1" };

Button button2 = new Button { Name = "button2" };

Button button3 = new Button { Name = "button3" };

Button button4 = new Button { Name = "button4" };

위와 같은 프로그램이 있다고 생각합시다. Name이란 Property는 NameProperty(Dependency Property)에 대응되는 Property입니다. 누군가 이렇게 질문했다고 합시다. "NameProperty는 static으로 선언 되어 있어. 따라서 위 코드를 실행시킨 후에는 button1, 2,3,4의 Name은 button4로 동일해야 하지 않을까?

여러분은 위 질문이 잘못된 것임을 알고 있습니다. 그런데 왜 그런걸까요? 분명히 NameProperty는 static으로 선언 되어 있는데 말입니다. 그 비밀은 DependencyObject의 SetValue, GetValue가 가지고 있을 것 같습니다. 보통 관습적으로 Dependency Property에 대응되는 Property는 다음과 같이 구현됩니다.

public string Name

{

set{ SetValue(NameProperty, value);}

get { return (string)GetValue(NameProperty);}

}

만약에 SetValue함수가 정적 공간에 value값을 저장한다면 위의 질문처럼 되야 할 것입니다. 그런데 button1,2,3,4의 Name 값이 서로 다르게 나타나는 것으로 보아 DependencyObject의 SetValue함수는 Dependency Property의 value를 정적 공간에 저장하지 않음을 알 수 있습니다. 그렇다면 어디에 저장하는 것일까요?

사실 DependencyObject는 Dependency PropertyValues라는 Dictionary를 가지고 있습니다.

private IDictionary<Dependency Property, object> Dependency PropertyValues;

DependencyObject는 Dependency Property를 키 값으로 해서 실제 value를 dictionary에 저장하는 것을 알 수 있습니다. 이로서 왜 static으로 저장해도 여러 Instance의 동일한 Dependency Property에 대응되는 Property의 value값이 다른지 알 수 있네요. 또한 여기서 다음과 같은 깨달음을 얻을 수도 있겠습니다.

Dependency Property는 실제 Instance의 Property Value를 얻고 저장하기 위한 Key로 사용된다.

왜 public static readonly로 선언하는가?

위에서 우리는 Dependency Property가 static으로 선언되어도 이에 대응되는 Property가 다른 값을 가질 수 있는지 알 수 있었습니다. 또한 Dependency Property는 자신 스스로의 Dependency Property객체에 value를 저장하지 않는다는 사실도 유추 할 수 있었습니다. 그렇다면 Dependency Property는 어떠한 역할을 하는 것일까요? 단지 value를 찾기 위한 Key로 활용되는 거라면 이 녀석이 필요하지 않을 지도 모릅니다. 또한 DependencyObject의 Setvalue함수가 단지 DependencyPropertyValues[DependencyProperty] = value;와 같이 단순하게 구현되었다면 Dependency Property는 필요 없었겠죠. (사실 DependencyObject의 SetValue함수는 실로 복잡해서 도저히 덤빌 욕구가 안나게 만듭니다.)

여러분도 아시다시피 Dependency Property는 실로 엄청난 일을 해냅니다. 부모 tree로 부터의 값 상속 이라던지, 자동 변경 통지 기능, Template 등등.. 이런 일을 해내기 위해 Dependency Property Object가 존재합니다. Dependency Object는 Dependency Property의 값을 결정하기 위해 Dependency Property 객체를 참고 합니다. Dependency Property에 metadata와 이름, owner Type이 필요한 것은 이 때문입니다. DependencyObject의 SetValue함수는 단순히 value를 dictionary에 저장하는 것이 아니라 현재 자신의 환경?에 맞게(자신의 VisualTree의 위치, Style값 등등) 저장하는데 이 때 Dependency Property의 특성 정보가 적극 활용되는 것입니다.

Dependency Object는 Dependency Property의 metaData와 다중 Provider를 이용해 Dependency Property의 최종 값을 결정한다.

Dependency Property는 자신 스스로가 value값을 가지고 있지 않으며 단지 value값을 결정하는데 필요한 정보를 가진다.

그렇다면 왜 Dependency Property가 public static readonly로 선언하라고 권장하는 것일까요? 제가 생각하는 이유는 다음과 같습니다. 먼저 Dependency Property의 정보를 다른 객체에 노출해야 할 필요가 있기 때문입니다. Binding을 예로 들겠습니다.

Binding bind = new Binding();

bind.Source = this;

bind.Path = new PropertyPath(Window.NameProperty);

canvas1.SetBinding(NameProperty, bind);

( 코드는 아무 짝에도 쓸모없는 코드입니다. 절대 쓰지 마세요^^;;;)

Window의 NameProperty를 canvas1의 NameProperty에 binding하고 있는 정말로 쓸데없는 코드군요. 다만 살펴봐야 할 것은 binding을 사용할 때 bind의 Source가 될 Instance와 Path로 사용될 Dependency Property가 필요하다는 것입니다. 아마 Binding을 수행하기 위해 Window의 NameProperty라는 DependencyProperty정보가 필요하기 때문일 것입니다. Binding 뿐만이 아니라 Style, Template, Animation등도 Dependency Property의 정보를 필요로 합니다. 이로서 public 으로 DependencyProperty를 노출하는 이유는 조금 알겠군요. 그런데 왜 굳이 public static으로 선언할까요? 그냥 선언하고 Property나 함수로 노출하면 안될까요? 물론 이렇게 해도 상관없습니다. 만약 여러분이 DependencyProperty를 Interface로 노출시키고 싶다면 private 로 선언하고 Property나 함수로 접근을 허용해도 상관 없습니다. 다만 이럴 경우가 거의 없다는 것이죠. 그리고 여러 Instance를 생성시켜도 Dependency Property객체가 변할 필요성이 없기 때문에 public static readonly로 선언하게 되는 것입니다.

 

Dependency Property, Visual Tree, Routed Event는 WPF를 이루는 가장 중요한 3대 요소 입니다. 특히 Dependency Property는 이해하기 어려우면서도 정말 신기한 놈입니다. WPF를 만든 사람에게 어떻게 이런 생각을 했냐고 물어보고 싶네요 ^^;;

신고

XAML에서 static 변수 사용법

프로그래밍 2008.08.16 16:39 Posted by 아일레프

블로그에 글을 게시한 지도 꽤 많은 시간이 흘렀습니다. 그 동안 많은 일이 있었습니다. 닷넷엑스퍼트란 좋은 회사에 입사했고, 1 주일 동안 교육을 받은 후 바로 프로젝트에 투입되어 매우 즐겁게 프로그래밍하고 있습니다. 즐거운 일을 할 수 있는 전 참 행운아인 것 같습니다. ^^ 서론은 여기까지 하고 XAML 에서 static변수 사용법에 대해서 알아보도록 합시다.


  • x:Static 키워드

    XAML에서는 static 키워드를 사용하기 위해서 x:Static 이라는 키워드를 사용합니다. WPF책들을 살펴보면 x:Static 키워드를 사용한 예를 찾아볼 수 있는데요, 대부분 SystemParameters 클래스 안의 static 변수를 사용하는 예제를 보여줍니다.

Height="{x:Static SystemParameters.PrimaryScreenHeight}"

위는 Height 속성에 SystemPArameters의 PrimaryScreenHeight Static 변수를 대입하는 XAML코드입니다.


  • 이번엔 직접만든 Class에 static 변수를 넣고 그 값을 한번 XAML에 대입해 봅시다.

namespace Test

{

public class CustomStaticClass

{

public static double WindowHeight = 300;

}

}

Height="{x:Static CustomStaticClass.WindowHeight}"

한번 빌드해볼까요? 당연히 에러가 날 것입니다. 에러 메시지를 보면 CustomStaticClass를 찾을 수 없다고 하는 군요. CustomStaticClass가 있는 namespace를 XAML코드 상에서 인식하지 못하기 때문입니다. XAML에서 네임스페이스를 다음과 같이 추가합니다.

xmlns:local="clr-namespace:Test"

그리고 다음과 같이 작성해 봅시다.

Height="{x:Static local:CustomStaticClass.WindowHeight}"

빌드가 성공하고 성공적으로 프로그램이 동작함을 확인 할 수 있을 것입니다.


  • XAML에서 *.resx파일 사용하기

    이제 좀 더 나아가 visualStudio2008에서 제공하는 *.resx파일을 사용해봅시다. *.resx파일은 Static 멤버를 가지는 클래스를 생성하기 때문에 x:Static 키워드로 사용할 수 있을 것 같습니다.

 

Resources.resx파일에 다음과 같이 Test라는 string을 추가해보도록 하겠습니다.

위와 같이 만든 string 리소스는 코드상에서 다음과 같이 접근이 가능합니다.

string test = Properties.Resources.Test;


XAML에서도 한번 해볼까요?

Title="{x:Static local:Properties.Resources.Test}"

안타깝게 빌드 에러가 납니다. GraphicTest.Properties.Resources가 Test라 는 static member를 가지고 있지 않다고 나오는 군요.

Resource 디자이너가 만든 코드를 열어보았습니다.

internal static string Test

{

get {

return ResourceManager.GetString("Test", resourceCulture);

}

}

위와 같이 존재하고 있음을 알 수 있습니다. 왜 안될까요? 아무래도 internal 키워드가 좀 걸리네요. 위를 보니 class 의 키워드도 public 이 아닌 internal로 되어 있습니다. 모두 public으로 바꾸고 실행 해보았습니다. 오, 성공적이네요. Internal 키워드가 역시 문제였던 겁니다. 그런데, XAML에서 resx파일을 사용하려면 매번 위와 같이 internal로 된 keyword를 public으로 바꾸는 작업을 해야한다면.. 그것은 곧 resx파일을 사용할 수 없다는 것과 동일 하겠군요. ResxXFileCodeGenerator가 public 키워드로 된 클래스와 멤버를 제공하면 좋을 텐데요.


  • 정말 다행히도 VisualStudio2008에서는 PublicResXFileCodeGenerator라는 녀석이 있습니다.

    위와 같이 ResXFileCodeGenerator를 PublicResXFileCodeGenerator 로 바꾸고 저장 후 실행하니 잘 되는 군요. WPF에서는 앞으로 이 녀석을 사용해야 할 것 같습니다. ^^


신고
TAG resx, static, WPF

BackGroud of COM / ATL - 1

프로그래밍 2008.05.31 21:16 Posted by 아일레프

최근에 ATL과 COM을 공부하고 있습니다. msdn이나 블로그의 글을 찾아보아도 단순히 "따라하기"식의 방법만 말해주고 있기에 불만이 있었습니다. 그래서 갖은 방법을 써서 ATL에 관련된 책을 구했으니, 그것이 ATL internals와 inside ATL입니다. 특히 inside ATL책의 Chapter2에는 COM기술의 전반적인 배경을 말해주고 있는데 굉장히 많은 도움이 되어서 여러분들에게 나누어주려고 합니다. 아래의 내용은 inside ATL의 Chapter2의 내용을 번역, 정리한 것입니다.

The Component Object Model

여러분들은 대부분 ATL을 배우려고 하는 사람들이고 아마 ATL을 사용한 프로젝트를 하고 있거나 계획중에 있는 개발자일 것입니다. 어떠한 이유든지 이 책 혹은 다른 책의 ATL framework을 배우기 전에 반드시 이해해야 하는 것이 바로 Microsoft Component Object Model(COM)입니다.

대부분의 개발자들이 ATL의 목적을 잘못 이해하고 있습니다. 그리고 ATL이 COM에 대해 배워야 할 시간을 절약해준다고 생각합니다. 이처럼 잘못된 생각은 없습니다. 물론, 당신이 COM의 기본을 잘 이해하고 있다면 ATL은 많은 시간을 절약해 줄 수 있습니다. 하지만 COM의 가장 기본적인 것 조차 모른다면 ATL은 당신을 더욱 더 혼돈의 세계로 데리고 갈 것입니다. 여러분이 이해해야 하는 것은 implementation을 interface와 구분하는 방법과 그 목적에 있습니다. 만약 이것을 이해하지 못한다면 여러분은 결코 효율적인 COM model을 만들 수 없을 것입니다.

여기에서는 Microsoft's binary object model(COM , DCOM)이 어떻게 분산환경에서 잘 동작할 수 있는지 기본적인 개념에 대해 말하고자 합니다. 앞으로 COM interfaces, COM classes, class objects에 대해 다룰 것입니다.

 

The Atoms of COM

Interfaces

interfaces에 대해 이해하기 위해 간단한 시나리오를 생각해 봅시다. 여러분이 어떠한 필요때문에 간단한 스펠링 체킹 모듈을 C++로 작성해야 한다고 합시다. 다음과 같이 SpellChecker 클래스를 구현할 수 있을 것입니다.

// checker.h
struct tagTEXTBLOB {
    unsigned long nSizeIs;
    char* pBuffer;
};

class CSpellChecker {
    static int m_nRefCount; // How many others are using this?
    LPTEXTBLOB m_lpText;    // LPTEXTBLOB is defined elsewhere.
public:
    CSpellChecker(LPTEXTBLOB lpText);
    virtual ~CSpellChecker();
    void CheckIt();
};

// checker.cpp
CSpellChecker:: m_nRefCount = 0;

CSpellChecker::CSpellChecker(LPTEXTBLOB lpText) {
    m_lpText = lpText;
}
CSpellChecker::~CSpellChecker() {
    m_lpText = NULL;
}
void CSpellChecker::CheckIt() {
    // Parse the text blob, looking up each word,
    //  making corrections when necessary.
}

작성해 놓고 보니 만족스럽습니다. 괜찮은 클래스 같다는 생각이 드는 군요. 위 클래스를 활용하기 위해서는 다음과 같이 프로그램을 작성하면 되겠습니다.

// EditView.h
#include "checker.h"
void CEditorView::OnCheckSpelling() {
    LPTEXTBLOB lpTextBlob = GetRawText();
    if (lpTextBlob) {
        CSpellChecker spellChecker(lpTextBlob);
        spellChecker.CheckIt();
    }
}

괜찮군요. 하지만 우리의 목표는 이 것을 모든 사람이 사용하게 하는 것입니다. 나아가 이 클래스를 이용해 다른 사람들이 또 다른 프로그램을 작성하게 하는 것입니다. 어떻게 할 수 있을까요?

 

Try Static Linking

만약 여러분이 스펠링 체크 컴포넌트를 라이브러리로 배포한다면 소프트웨어 업체는 그 라이브러리를 static linking을 통해 그들의 프로그램에 넣을 수 있을 것입니다. 그러나 이 방법은 두 가지 단점이 있습니다.

첫번째는 static-linking 접근이 중복의 문제를 가지고 있다는 것입니다. 만약 여러분이 만든 라이브러리가 너무나 훌륭해서 많은 벤더들이 이 라이브러리를 이용해 많은 소프트웨어를 만들었다고 칩시다. 사용자들은 이 라이브러리를 사용한 3가지의 소프트웨어를 프로그램에 설치했구요. 이 경우에 사용자는 3가지의 스펠링 체커를 그의 디스크에 존재시켜야 합니다. 왜 낭비인지 알겠죠?

두번째 static-linking 접근의 문제점은 지나친 결합도에 있습니다. Static linking은 여러분이 스펠링 체크 기능을 변경하기 전까지만 괜찮게 동작합니다. 만약 여러분이 기막힌 스펠링 체크 알고리즘을 만들었다고 생각합시다. 또는 여러분이 만든 스펠링 체크 알고리즘에 안타깝게도 버그가 있어 수정해야 한다고 생각합시다. static-linking을 사용했기에 사용자는 전체의 프로그램을 다시 빌드해야 합니다. 혹은 전체 프로그램을 다시 다운 해야 할 것입니다. 사용자가 싫어할 것은 두말할 필요가 없습니다. static-linking이 아닌 다른 접근을 생각해 봐야 겠군요.

 

Dynamic Linking to the Rescue

Static linking의 단점을 해결할 수 있는 방법 중 하나는 DLL을 사용하는 것입니다. DLL은 하드디스크에서 호출되기를 기다리는 실행가능한 코드라고 할 수 있습니다. 클라이언트의 코드는 런타임에 DLL의 코드를 load할 수 있고 link할 수 있습니다. DLL은 여러프로그램이 공유하기에 중복의 문제를 해결할 수 있고 프로그램을 업데이트할 경우가 있다고 했을 때 DLL만 변경해주면 되기에 Static Linking 접근의 단점을 모두 해소할 수 있습니다. 위의 C++코드를 DLL로 export하려면 다음과 같이 코드를 작성하면 됩니다.

class _ _declspec(dllexport) CSpellChecker {
    static int m_nRefCount;
    LPTEXTBLOB m_lpText; // LPTEXTBLOB is defined elsewhere.

public:
    CSpellChecker(LPTEXTBLOB lpText);
    virtual ~CSpellChecker();

    void CheckIt();
};

차이점은 class를 선언할 때 __declspec(dllexport)를 사용한다는 것 밖에 없습니다. 여러분이 class를 이 방식으로 export할 때 모든 function과 static data member는 DLL's export list에 추가되게 됩니다. CSpellChecker 클래스를 사용하기 원하는 사용자는 단지 위 헤더파일을 그의 source 코드에 포함시키고 스펠링체크 dll이 사용가능한 path에 있는지 확인만 하면 됩니다. 정말 쉽군요. 하지만 다른 문제는 없을 까요?

The Downside of Exporting C++ clases

여러분이 위 스펠링 체크 클래스를 MS Visual C++을 사용해 만들었다고 합시다. 그리고 DLL로 배포했구요. 이 때 MS Visual C++를 사용하는 사용자는 여러분의 DLL을 활용할 수 있습니다. 그러나 만약 사용자가 Inprise 버전의 C++을 사용한다면 아쉽게도 그 사용자는 여러분의 DLL을 사용할 수 없습니다. 왜 그런걸까요?

C++의 강점은 type-safe linking이라는 것에 있는데요, C++ 클래스가 object code로 될때 클래스 멤버 function의 이름들은 mangled됩니다. mangled된다는 것은 이름이 function의 return type과 signature등의 정보로 장식(decorate)된다는 것입니다. 이렇게 하는 이유는 사용자의 코드와 object 코드가 안전하게 linking 될 수 있게 하기 위함입니다. 이 것을 type safe linking이라고 부릅니다. 그런데 문제는 Compiler 벤더들이 모두 통일된 방식으로 mangle을 하고 있지 않다는데 있습니다. 다음은 그 예입니다.

Symantec's Mangling

??0CSpellChecker@@QAE@PAX@Z
??1CSpellChecker@@UAE@XZ
??4CSpellChecker@@QAEAAV0@ABV0@@Z
??_GCSpellChecker@@UAEPAXI@Z
??_RCSpellChecker@@QAEAAV0@ABV0@@Z
?CheckIt@CSpellChecker@@QAEXXZ
?m_nRefCount@CSpellChecker@@0HA

Microsoft's Mangling

??0CSpellChecker@@QAE@ABV0@@Z
??0CSpellChecker@@QAE@PAX@Z
??1CSpellChecker@@UAE@XZ
??4CSpellChecker@@QAEAAV0@ABV0@@Z
??_7CSpellChecker@@6B@
??_ECSpellChecker@@UAEPAXI@Z
??_GCSpellChecker@@UAEPAXI@Z
?CheckIt@CSpellChecker@@QAEXXZ
?m_nRefCount@CSpellChecker@@0HA

Inprise's Mangling

@CSpellChecker@$bctr$qpv
@CSpellChecker@$bdtr$qv
@CSpellChecker@CheckIt$qv
@CSpellChecker@m_nRefCount

각 벤더마다 mangle하는 방식이 다르다는 것을 위에서 알 수 있습니다. Inprise로 빌드된 DLL과 Microsoft로 작성된 사용자의 코드와 적절히 링크되지 않는 이유는 이 이유 때문입니다.

Solving the Problem with Ordinals

위 문제를 해결하는 하나의 방법은 Ordinals를 사용하는 것입니다. 여러분은 DEF 파일을 사용해 각 exported member에 ordinal number를 배정할 수 있습니다. 그것에 의해 컴파일러를 위한 import library를 만들 수 있습니다. 이렇게 한다면 각 사용자들은 ordinal을 통해 각 멤버에 접근할 수 있습니다. 이 접근은 name mangling에 대한 문제를 해결했지만 큰 유지보수 문제를 야기합니다. 왜냐하면 여러분은 각각의  컴파일러에 대해 import library를 만들어 주어야 하고 DLL파일이 수정되었을 때에도 import library를 수정해주어야 되기 때문입니다.

 

Oops - Some More Problems

Type-safe linking 문제만이 전부가 아닙니다. 또다른 문제는 여러분이 class를 업데이트 할 때 발생합니다. 예를 들어서 여러분이 스펠링 체커를 변경해야할 필요가 생겼다고 합시다.  여러분은 사용자를 위해 10개의 빈번히 나오는 단어에 대한 캐시 기능을 추가하려고 하는데 이것을 수행하려면 여러분은 몇가지 데이터를 여러분의 class에 추가시켜야 합니다. 새로운 클래스의 선언은 다음과 같을 수 있습니다.

class _ _declspec(dllexport) CSpellChecker {
    static int m_nRefCount;
    LPTEXTBLOB m_lpText; // LPTEXTBLOB is defined elsewhere.
    LPSTR lpszFrequentWords[10];

public:
    CSpellChecker(LPTEXTBLOB lpText) {
        // Initialize.
    }
    virtual ~CSpellChecker() {
        // Tear down.
    }

    void CheckIt() {
        // Do the checking.
    }
    void AddToFrequentWordList(LPSTR lpszWord) {
        // Cache frequent words.
    }
};

새로운 데이터를 class에 추가했기에 class의 크기가 변했습니다. 현재의 클래스는 40bytes 이상이 될 것입니다. 게다가, 여러분은 class의 layout을 변경시켰습니다. 중요한 점은 사용자가 C++ 를 사용해 코딩을 할 때 클래스의 layout을 알아야 한다는 점에 있습니다. 여러분이 새로 변경된 DLL을 사용자에게 제공했는데 그 클래스의 layout이 변경되었다면 사용자는 자신의 코드를 다시 컴파일 해야할 것입니다. 또한 어떤 사용자는 새로 변경된 DLL을 사용하기 싫어한다면 어떤일 이 생길까요? 여러분은 어쩔 수 없이 각 사용자를 위해 다른 DLL을 제공해 주어야 할 것입니다. 여러분의 DLL에 문제가 생겼을 때 각 버전의 DLL을 점검해야 한다는 것은 당연하므로 또 다시 유지보수 문제가 고개를 드는 군요. 여러분의 컴퓨터에 다양한 버전의 MFC DLL이 존재한다면 그 이유는 바로 앞에서 설명한 그것 때문입니다. 결국 DLL을 사용한 배포가 효율 적으로 이루어지려면 다음의 2가지 사항이 만족되어야 합니다. 첫번째는 모두가 하나의 C++ 컴파일러를 사용해야 합니다. 두번째는 클래스의 크기와 layout이 절대 변하지 않아야 합니다. 이것이 만족될 수 있는 일은 결코 생기지 않을 것입니다.

이것 참 어렵습니다. 어떻게 해야 이 문제를 해결할 수 있을까요? class based programming으로는 결코 이 문제를 해결할 수 없습니다. 그 대신 interface-based programming 방법을 사용해야 합니다.  Interface-based programming은 클래스의 interface를 implementation과 분리시키는 것인데, 그것으로 인해 client code와 DLL의 coupling 오버헤드를 줄일 수 있습니다.

- interface-based programming이 왜 필요한지 설명하기 위해 먼길을 돌아왔습니다. 오늘은 여기까지 하도록 하죠. ㅋ

 

신고

태엽장치 돌고래 - 패닉

노래이야기 2008.05.23 14:13 Posted by 아일레프


이 노래를 이렇게 잘 표현하다니..
호민이의 삼류만화  에서 퍼왔습니다.   

신고

Anne murray의 Tennessee Waltz

노래이야기 2008.05.23 14:10 Posted by 아일레프

난 팝송듣는 것을 즐겨하는데 그 이유는 멜로디가 좋아서도 있겠지만,

더 큰 이유는 팝송 가사를 해석하는 것이 참 좋아서이다.

다른나라의 말을 우리나라말로 번역한다는 것을 난

다른사람의 이야기를 내 것으로 만드는 것과 조금 비슷하게 느끼는 것 같다.

어려운 소설을 골라 감동을 느끼려하는 감정과도 비슷한 것 같다.

물론 아예 손도 못델 것 같이 어려운 노래들은 포기한다.

각설하고 요즘은 우연히 Anne Murray의 I just fall in love again을 듣고 나서부터

Anne Murray의 노래에 푹 빠져 있다.

지금은 Tennessee Waltz라는 노래를 듣고 있다.

너무 좋은 멜로디, 그리고 목소리. 하지만 분명 아름다운 가사가 있을 것 같은 노래에 너무 서글픈 내용이 담겨 있는 것에 너무 많이 놀랐다.


Tennessee Waltz

I was dancing with my darlin'
To the Tennessee Waltz
When an old friend I happened to see
I introduced her to my loved one
And while they were dancing
My friend stole my sweetheart from me

I remember the night
And the Tennessee Waltz
Now I know just how much I have lost
Yes, I lost my little darlin'
The night they were playing
The beautiful Tennessee Waltz

I remember the night
And the Tennessee Waltz
Now I know just how much I have lost
Yes, I lost my little darlin'
The night they were playing
The beautiful Tennessee Waltz

부족한 영어실력으로 해석을 하자면




난 내 사랑하는 사람과 춤을 추곤 했습니다. Tennessee Waltz를.

언젠가 나의 오래된 친구를 우연히 보았을 때 난 그녀를

내 사랑하는 사람에게 소개 해주었습니다.

그리고 그들이 춤을 추고 있을 동안

내 친구는 그이를 내게서 가져갔습니다.



난 그 밤과 Tennessee Waltz를 기억합니다.

그리고 난 알고 있습니다. 내가 내게 얼마다 커다란 것을 잃었는지.

그래요 난 나의 그를 잃었습니다.

그 밤에 그들은 그렇게도 아름다운 Tennessee Waltz를 추었습니다.

그렇게 아름다운 Tennessee Waltz를.




아마도 내 생각에 작사가는 자신의 애인을 빼앗아간 옛친구를 미워하지 않았을 것 같다.

그 친구가 자신의 애인을 빼앗아간 그 밤에 그토록 아름다운

Tennessee Waltz를 추었기 때문에.

정확한 문제는 옛친구와 자신의 애인에 있었던 것이 아니라

그 때 하필 Tennessee Waltz를 추었기 때문이다.

분통터지고 슬픈 이 현실에 하필이면 Tennessee Waltz가 흘러나오고

자신의 친구는 저렇게 아름다운 춤을 함께 추고 있다.

하필이면 Tennessee Waltz가 그 때 나오다니.



이런! 정말 설명하기 어렵다. 이 노래를 계속 들으면서 느꼈던

복잡한 감정을 설명하기가 이렇게 어렵다니!!! 제길!!

글 쓰는 것이 이렇게 힘들다니.

어쩔 수 없다. 지금 소리바다로 가길. Tennessee Waltz를 검색해 들어보길.

그리고 그 슬픈 현실에 이 슬픈 노래를 왜 이리도 감미롭게 부르는 지 느껴 보길.

Anne murray 노래는 너무 좋다...
신고

이것은 삭제되지 않은 임시 게시물입니다. 이 게시물은 직접 삭제하세요. (ea468f57-e095-4b9e-8c5a-6db75dc2d3a8 - 3bfe001a-32de-4114-a6b4-4005b770f6d7)

신고

블로그를 만들었다~

노래이야기 2008.05.18 03:28 Posted by 아일레프

블로그 메인 이름은 "내일을 기다려" 박강성의 노래제목~ 흠 좋은데 ㅋ

 
 
내일을기다려
잊어야한다고 눈을 감으면
가까운 빛으로 다가오는 것을
낙엽이 지기전에 돌아서려나
벌써 눈이 내리네
하지만 어쩌다 그리울때면
지나간 날들을 사랑이라 여기고
흐르는 시간속에 나를 달래며
잊을수는 없을까
 
아는지 모르는지 웃음만 보이던 그대가
커피 한잔의 추억은 아닌거야
이렇게 흘러가는 세월속에서
슬픈 사랑의 비밀을 간직한채
또다시 내일을 기다려
내일을 기다려
신고
TAG 박강성


 

티스토리 툴바