2 Results for 'DefaultStyle'

  1. 2009.01.16 커스텀 컨트롤의 DefaultStyle을 여러 xaml로 나누기
  2. 2008.08.20 Custom Control Helper (2)

실버라이트에서 커스텀 컨트롤을 만들 때 각 커스텀 컨트롤들의 기본 스타일은 반드시 /themes/generic.xaml 파일에서  ResourceDictionary로 등록해야 했죠. 때문에 라이브러리가 커지다보면 수많은 기본 스타일들이 하나의 파일에 등록이 되어 있어서 찾아보기가 굉장히 힘들었죠.

과연 마이크로소프트의 개발자들은 이런 문제를 어떻게 해결하고 있는지 궁금했었는데요, Jeff Wilcox의 블로그에서 그에 대한 답을 내놓았네요.

아니나다를까 마이크로소프트의 내부 개발자들은 저런 문제를 쉽게 해결하기 위한 툴들을 만들어쓰고 있네요. 바로 기본 스타일들을 정의하는 Xaml들을 자유롭게 배치하고 대신 해당 xaml의 Build Action을 DefaultStyle이라는 키워드로 선택하기만 하면 빌드시 /themes/generic.xaml에 자동으로 통합을 해주는 MSBuild 툴을 만들어 쓰고 있다고 하는군요.

간단하게 말하자면 MSBuild 프레임워크에 포함된 Task라는 항목이 있는데, 이게 어떤 액션이 일어날 때 처리할 일을 정의하는 넘이죠. 그래서 빌드할 때 프로젝트에 포함된 아이템 중에서 DefaultStyle이라는 키워드의 Build Action이 붙은 모든 아이템들을 가져와서 처리한 후 /themes/generic.xaml에 덮어쓰는거죠. 여기에서 Build Action에 원하는 키워드를 추가하는 방법이 가장 중요한 요소인데요, VSProject를 구성하는 엘리먼트 중에 숨겨진 요소가 있다는군요. 참 Win32부터 그런 것들이 많았었죠 –_-;;

자세한 내용은 Jeff Wilcox의 포스팅에 잘 소개되어 있으니 소스 설명은 하지 않겠고요, 단순히 해당 내용을 구현한 결과물을 소개할께요.

다운 받은 파일의 압축을 풀면 세 개의 파일이 나오는데요, 각각 정해진 위치로 폴더를 만들고 복사하기만 하면 돼요.

  • Engineering.Build.dll
    -> C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Libraries\Extra
  • SilverlightAppWithMultipleResourceDictionary.zip
    -> %내 문서%\Visual Studio 2008\Templates\ProjectTemplates\Visual C#\Silverlight
  • SilverlightDefaultStyleItem.zip
    -> %내 문서%\Visual Studio 2008\Templates\ItemTemplates\Visual C#\Silverlight

zip 파일 들은 내 문서에 있는 Visual Studio 2008 폴더를 찾아서 넣으면 돼요.

자 이제 비주얼 스튜디오를 열고 프로젝트를 하나 만들어 볼까요?

image

Visual C#/Silverlight 타입에 보면 My Templates 그룹 아래에 Silverlight Application with Multiple…이 보이네요. 이 템플릿으로 새 프로젝트를 만들면,

image

보안 경고가 나오는데요, 이건 사용자 정의 템플릿을 사용할 때 항상 뜨는 걸로 아래에 있는 Load project normally를 선택해야 제대로 진행돼요.

image

프로젝트를 만들면 기본 실버라이트 프로젝트와 크게 다르지 않고 다만 /themes/generic.xaml이 기본으로 포함되어 있는 것을 볼 수 있죠.

다음으로 새 DefaultStyle 정의를 위한 xaml 파일을 만들어보죠.

image

프로젝트에 새 아이템을 추가하면…

image

위와 같이 카테고리에서 Visual C#/Silverlight를 선택하면 My Templates 그룹Silverlight Default Style Item이 있죠? 클릭하고 이름을 적당히 넣은 후 추가 버튼을 클릭하면 다음과 같이 보안 경고 메시지가 나와요. 저 믿죠? ㅋㅋ Trust를 클릭하여 커스텀 아이템을 추가하도록 허용합니다.

image

xaml 파일이 프로젝트에 추가되는데 여기에서 Build Action은 수동으로 DefaultStyle을 선택해야 하고 Custom Tool 설정은 삭제하세요.

image

네, 그리고 xaml의 내용에는 기본 스타일을 바로 작성할 수 있도록 ResourceDictionary를 추가해뒀어요. Enjoy it! :)

마지막으로 테스트를 해봐야죠? 새로 생성한 MyControlStyle.xaml에 다음과 같이 간략하게 코드를 작성해보죠.

image

이제 빌드를 하면?

image

/themes/generic.xaml이 외부에서 변경되었다는 메시지가 나오는데요 이건 어쩔 수가 없어요. 아쉽지만 매번 DefaultStyle로 설정한 xaml이 빌드될 때마다 Yes를 눌러줘야죠.

최종 결과물은 위와 같이 별도로 작성한 Style이 generic.xaml에 자동으로 복사된 모습. 자 이제 한 곳에 보기 싫게 몰아넣지 말고 각 커스텀 컨트롤 별로 연관성 있게 파일을 관리할 수 있어요.

단점이라면 하나의 xaml이라도 DefaultStyle을 사용한다면 generic.xaml이 자동으로 생성되기 때문에 모든 DefaultStyle을 별도의 파일로 관리해야 한다는 점이죠.

그렇지만 저는 각 커스텀 컨트롤의 스타일을 별도의 파일로 관리하고 공통으로 사용할 스타일이나 리소스 또한 common.xaml 등으로 따로 관리하는 것이 좋다고 봐요.

여튼 라이브러리나 커스텀 컨트롤을 많이 사용하는 프로젝트에서는 굉장히 유연성있는 스타일 관리를 할 수 있는 좋은 방법이죠. 피드백 바랍니다. :D

저작자 표시 동일 조건 변경 허락
신고
Posted by gongdo

실버라이트 2 베타 2에서 가장 중요한 업데이트는 바로 Visual State Manager(이하 VSM)의 지원이었죠. VSM은 실버라이트가 강조하는 개발자와 디자이너의 분리된 협업을 더욱 잘 지원하게 해주고 개발 생산성을 정말 엄청나게 향상시켰죠. 저 역시 베타 1때의 커스텀 컨트롤 만들었던 것과 지금을 비교하면 그저 눈물이 나올 뿐이에요.

그렇지만 아직 커스텀 컨트롤도 해결해야 할 문제가 몇 가지 있는데요, 그 중에서 실제 개발 할 때 가장 귀찮고 시간이 많이 걸리는 부분은 바로 코드에서 정의된 템플릿 파트나 Visual State Group 및 Visual State의 이름을 가지고 generic.xaml에 기본 템플릿을 정의해야 한다는 점이에요. 개발자와 디자이너가 '이름'이라는 수단으로 일종의 계약을 맺는거죠. 일단 개념 상으로는 굉장히 좋아요. 먼저 컨트롤이라는 부품의 역할과 필요한 요소를 먼저 정의한 후 그 정의에 부합하는 한에서 디자인을 마음대로 할 수 있으니까요.

그러나, 개발자의 입장에서 '이름' 즉, 문자열에 기반한 계약 관계란 귀찮고 불안하기 짝이 없는 물건이에요. 만약 프로젝트를 진행하다가 모종의 이유로 인해 이 이름을 바꿔야할 필요가 있다면? 특정 이름이 더 이상 사용되지 않는다거나 새로 추가 되었다면? 게다가 이런 일은 상당히 자주 발생하죠. 이런 상황에서는 대체로 개발자 해당 '문자열'을 수작업으로 검색해서 바꿔주는게 일반적이에요. 문제는 이것이 실수를 만들기 매우 쉬운 프로세스라는 것이에요. 코드만으로 구성된 프로그램에서는 이런 일이 발생하지 않죠. 왜냐면 만약 코드에서 어떤 변수의 이름을 바꾸거나 삭제했다면 컴파일러가 곧바로 그 사실을 알려주고 에러 메시지를 뿜어내니까요. 하지만 단순한 문자열은 그 내용을 바꿨는지 어쨌는지 아니 심지어 그 문자열이 무슨 뜻인지 조차 컴파일러는 알지 못하니까요.

이 문제는 과거 실버라이트 1.1의 사용자 컨트롤에서도 있었던 문제였어요. 요컨대 XAML에서 x:Name 어트리뷰트로 선언한 이름을 코드 비하인드에서 일일이 수작업으로 그 엘리먼트의 참조를 만들어야만 했었죠.

이 문제는 실버라이트 2 베타 1이 나오면서 해결되었어요. 바로 사용자 컨트롤을 생성하면 XAML 마크업과 코드 비하인드가 쌍으로 만들어지고 XAML에서 x:Name 어트리뷰트로 선언된 이름은 Xaml precompiler에 의해 자동으로 동일한 이름을 갖는 멤버 변수로 선언이 되죠.

[UserControl이란 사용자 컨트롤을 만들면 위와 같이 XAML 마크업과 코드 비하인드 외에 컴파일러가 자동으로 만들어주는 g.cs파일이 생성됩니다.]

이렇게 되어 사용자 컨트롤을 만들 때의 귀찮은 단순 노가다가 사라졌을 뿐만 아니라 디자이너가 자유롭게 XAML상의 x:Name어트리뷰트를 바꿔도 개발자는 그 사실을 빠르게 캐치 할 수 있게 되었죠.

불행하게도, 아직 커스텀 컨트롤에는 이러한 매커니즘이 적용되어 있지 않아요. 디자이너는 단지 generic.xaml에 컨트롤의 스타일을 XAML로 정의할 뿐이고 개발자는 단지 코드 비하인드에서 필요한 요소들을 수작업으로 참조하게 되죠. 마치 1.1에서 했던 그것처럼요!

휴... 사설이 길었죠? 네 바로 이런 이유로 Custom Control Helper를 만들어봤어요.

이 툴을 만들면서 굉장히 삽질을 많이 했는데요, generic.xaml과 커스텀 컨트롤의 DefaultStyle 적용 매커니즘은 굉장히 복잡하기 때문이죠. 그 옛날 사용자 컨트롤처럼 단순히 XAML 코드에서 x:Name으로 선언된 어트리뷰트를 가진 모든 엘리먼트를 멤버 변수로 1:1 매칭하기만 하는 코드와는 차원이 달랐어요.

먼저 일반적인 Custom Control을 만들 때 가장 필수적인 요소들을 뽑아 보죠.

  • Custom Control은 DefaultStyle로 자기 자신의 Type을 사용합니다. 즉, generic.xaml에 선언된 Style중 TargetType이 자기 자신의 Type과 일치하는 스타일을 선택합니다.
  • Custom Control에 포함될 VisualStateGroup과 VisualState의 이름을 internal 멤버로 선언합니다. 이 때 Custom Control이 이미 부모 Custom Control로부터 상속받았을 가능성도 있으므로 부모 Custom Control이 해당 멤버를 이미 가지고 있을 경우는 제외합니다.
  • Custom Control에 포함될 Template Part를 리스트업 합니다. 일반적으로 Template Part의 대상으로는 Custom Control의 RootElement의 Resources에 포함된 Storyboard, 그리고 RootElement를 포함한 모든 하위 엘리먼트 중에서 x:Name 어트리뷰트를 선언한 엘리먼트를 의미하며 그 하위 엘리먼트들은 자기 자신의 Style 정의를 따로 갖을 수도 있으므로 이런 부분은 제외합니다. 마찬가지로 Template Part는 부모 Custom Control로부터 상속이 가능하므로 이미 선언 되었다면 제외합니다.
  • Custom Control을 위한 partial class 문자열을 생성합니다. 이 때 해당 클래스에 포함된 모든 Template Part들의 Type을 사용할 수 있도록 적절히 using 문도 넣어줘야 합니다.
  • Custom Control의 partial class는 InintializeTemplate이라는 멤버 메서드를 정의하고 그 메서드 내에서 스타일이 정의하고 있는 Tempalte Part들을 멤버 변수로 참조합니다. 이 때 해당 Template Part가 null이 아닌데 Type이 일치하지 않는 경우는 Assertion을 발생시키는 코드도 넣습니다.
  • Template Part 참조시의 Assertion 코드는 단순히 똑같은 코드를 반복할 수도 있고 클래스의 멤버 메서드로 묶어서 쓸 수도 있고 혹은 해당 클래스의 부모 클래스에서 이미 상속했을 수도 있습니다.
  • 컨트롤의 템플릿을 정의하는 XAML은 단순히 이름만 정의했을 뿐 실제로 코드에서 굳이 참조하여 사용할 필요가 없는 경우가 더 많습니다. 따라서 이름을 가지고 있는 엘리먼트 중에서 Template Part로 등록할 엘리먼트를 선택할 수 있어야 합니다.

...만들다 보니 진짜 복잡하네요...
여튼 이런 조건을 만족시키기 위해서는 XAML 만 분석해서는 답이 안나와요. 그래서 차선으로 선택한 것은 아예 컴파일 되어 있는 XAP 패키지를 직접 다운로드하고 그 안에 들어있는 어셈블리들을 동적으로 로드한 후 정확한 Type 분석 및 상속 관계를 파악하여 작업해야 했어요.

완벽한 형태가 되려면 Visual Studio 플러그 인으로 개발하면 더 좋겠지만 더 이상은 귀찮아서 손대기가 싫어요 -_-;

사용법은 간단하게 [Browse]를 클릭하여 XAP파일을 선택하세요. 주소창에 http://...처럼 웹에서 직접 선택하는 것도 가능해요.

선택된 XAP 파일내에 어셈블리들을 로드하고 각 어셈블리에 generic.xaml이 있을 경우만 리스트업하죠.

분석할 어셈블리를 선택하면 generic.xaml에서 스타일이 정의되어 있는 커스텀 컨트롤의 목록이 나오고 Options에서 Template Part를 참조하는 방법을 선택할 수 있으며 Required template parts에서 Template Part로 등록할 엘리먼트의 이름을 체크하면 돼요.

왼쪽 아래의 TextBox는 원본 generic.xaml 코드이고 오른쪽 TextBox는 현재 선택된 커스텀 컨트롤에 대한 partial class 코드에요. 이 코드를 복사해서 프로젝트에 새 파일로 추가하고 해당 클래스의 선언을 partial 로 변경해준 후 OnApplyTemplate 오버라이드 구현에서 InitializeTemplate()을 호출하면 돼요.

사실 이 샘플은 그냥 제가 쓸려고 만든거라 아마 별 도움은 안되겠지만, 혹시 XAP 파일에서 어셈블리를 얻어와서 분석하는데 관심이 있다면 소스코드가 도움이 될 거에요.


없을 걸 알지만 피드백 기대합니다. :)

신고
Posted by gongdo


티스토리 툴바