개 요
여러분이 회사에서 프로젝트를 진행하다 보면 기존의 윈도우 컨트롤을 그대로 사용하기 보다는 프로젝트 성격에 맞게 컨트롤을 다시 작성해야 하는 경우가 생깁니다. 예를 들면 버튼만 하더라도 컴팩트 프레임워크에선 이미지 속성을 지원하지 않기 때문에 이 이미지 속성을 지원하도록 커스텀한 컨트롤 클래스를 먼저 만들어 줘야만 할 것입니다.
이제 여러분은 커스텀한 이미지 버튼 클래스를 만들었습니다. 하지만 그대로 그 컨트롤을 사용한다면 여러분이 기존에 eVC에서 해오던 방식과 다를게 없습니다. 여러분은 여러분이 만든 컨트롤을 도구 상자에 넣고 드래그&드랍으로 쉽게 컨트롤을 사용하길 원합니다. 여러분이 하고 싶어하는 그걸 우리는 디자인 타임 지원 컨트롤이라고 부릅니다. 여러분이 디자인 타임 지원 컨트롤을 만들게 되면 그 컨트롤을 가져다 쓰는 다른 동료들이 훨씬 더 편하게 컨트롤을 사용할 수 있게 될 것입니다. 아마 컴팩트 프레임워크를 이용해서 이렇게 쉽게 프로그래밍이 가능하다는걸 알게 되면 기존의 eVC를 그대로 사용하자고 고집하던 동료들도 마음을 되돌릴지 모릅니다.
자 이제부터는 여러분이 만든 커스텀 컨트롤이 디자인 타임을 지원할 수 있도록 하는 방법을 알아보도록 하겠습니다. 컴팩트가 아닌 닷넷 프레임워크에서는 단지 IComponent 인터페이스를 사용하는 System.ComponentModel.Control 또는 System.ComponentModel.Component 를 상속받게 하는 것만으로 쉽게 처리할 수도 있습니다. 하지만 컴팩트 프레임워크에서는 프레임워크의 사이즈와 속도를 향상시키기 위해 많은 부분이 감량되었고 이런 이유로 IComponent 인터페이스가 컴팩트 프레임워크의 런타임에서 빠져버렸습니다. 하지만 그렇다고 해서 우리가 컴팩트 프레임워크에서 디자인 타임을 지원하는 것이 불가능한 것은 아닙니다.
이번 아티클에선 그 방법에 대해 알려드리도록 하겠습니다.
사용자 컨트롤 작성
이번 아티클의 주제는 사용자 컨트롤 작성이 아니기 때문에 컴팩트 프레임워크에서 사용자 컨트롤을 작성할 때의 순서, 특성, 주의점 등에 대해서는 언급하지 않고 추후 아티클에서 다루도록 하겠습니다. 그러니 이후의 강의 내용에 대해서는 첨부된 프로젝트 파일을 참고하시기 바랍니다. 이 프로젝트 파일은 디자인 타임 지원에 필요한 모든 내용을 포함하게 될 것입니다.
DesignTime 구현
1. 디자인 타임 지원 어셈블리 가져오기
닷넷 컴팩트 프레임워크 런타임 어셈블리에서 IComponent 인터페이스가 빠지긴 했지만 MS에서는 디자인 타임 지원을 위해 다음 세 어셈블리에 디자인 타임 아키텍쳐를 포함시켰습니다.
System.CF.Windows.dll
System.CF.Design.dll
System.CF.Drawing.dll
위 말의 의미는 여러분이 디자인 타임을 구현하기 위해서는 여러분이 작성한 컨트롤을 위 어셈블리들을 참조하여 재 컴파일하여야 한다는 뜻이 될 수 있습니다.
자 그럼 다음 위치에서 위 어셈블리들을 여러분이 작성한 컨트롤 프로젝트 폴더로 복사해 옵시다.
C:Program FilesMicrosoft Visual Studio .NET 2003CompactFrameworkSDKv1.0.5000Windows CEDesigner
2. 디자인 타임 Attribute 추가
이제 코드를 손 볼 차례입니다. 여러분이 작성한 소스에서 네이스페이스 윗 부분에다가 다음처럼 어트리뷰트를 추가합니다.
#if NETCFDESIGNTIME
[assembly: System.CF.Design.RuntimeAssemblyAttribute("CustomControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
#endif
namespace CustomControl
{
/// <summary>
/// Image Button Class
/// </summary>
public class ImageButton : ImageButtonBase
{
}
}
우리가 방금 추가한 어트리뷰트는 디자인타임 모드일 때 비쥬얼 스튜디오로 하여금 어떤 디자인 타임 어셈블리가 어떤 런타임 어셈블리와 연관 되는지 알수 있게끔 해주는 역할입니다. 첫번째 파라미터가 바로 런타임 어셈블리의 이름입니다. 주목해야 할 것은 두 번째 Version인데 위처럼 어트리뷰트를 지정하면 생성되는 디자인 타임 어셈블리에서는 매번 위 버전의 런타임 어셈블리를 참조합니다. 그러나 여러분의 런타임 어셈블리는 여러분이 컨트롤을 매번 빌드 할 때마다 그 버전이 변하게끔 되어 있습니다. 그렇게 되면 런타임 어셈블리가 바뀔 때마다 디자인 타임 어셈블리도 재 컴파일 해주어야 할 필요가 있습니다. 이걸 막기 위해서 여러분은 런타임 어셈블리의 버전이 매번 바뀌는 것을 막을 필요가 있습니다. 그런 이유로 우리는 AssemblyInfo.cs 파일에 기본값으로 설정된 버전 어트리뷰트를 수정하도록 하겠습니다.
[assembly: AssemblyVersion("1.0.*")]
를 다음과 같이 고정시킵니다.
[assembly: AssemblyVersion("1.0.0.0")]
3. 플랫폼 의존적인 코드 작성
이제 할 일은 플랫폼 의존적인 코드를 작성하는 일입니다. 만약 코드상에 오직 데스크탑에서만 돌아가는 코드가 있다면 당연히 런타임에서 그 코드는 돌아가지 않을 것이고 그 반대의 경우도 마찮가지입니다. 그리고 이런 경우에 여러분은 NETCFDESIGNTIME을 써서 플랫폼 의존적인 영역을 보호해줄 수 있습니다. 다음은 그 예를 보여주고 있습니다.
#if NETCFDESIGNTIME
[DllImport("winmm.dll", EntryPoint="sndPlaySoundW")]
private static extern int PlaySound(String szName, uint uiSound);
#else
[DllImport("coredll.dll", EntryPoint="sndPlaySoundW")]
private static extern int PlaySound(String szName, uint uiSound);
#endif
또 여러분은 의도적으로 해당 코드에 대해서 디자인 타임에서만 혹은 런타임에서만 동작하도록 할 수 있습니다.
public Image DefaultImage
{
get
{
return this.defaultImage;
}
set
{
this.defaultImage = value;
if(this.defaultImage != null)
this.Size = this.defaultImage.Size;
#if NETCFDESIGNTIME
this.Invalidate();
#endif
}
참고로 #ifndef를 쓰고 싶은 경우는 #if !NETCFDESIGNTIME와 같이 사용합니다.
4. Custom Designer Attribute 추가하기
위와 같이만 해주면 이제 여러분이 작성한 컨트롤은 툴박스에 들어가 드래그&드랍으로 쉽게 폼 위에 추가될 수 있고 또 속성창을 통해 해당 컨트롤의 Property 도 지정해줄 수 있습니다. 이건 여러분이 커스텀 컨트롤을 만들기 위해 상속받은 Control 클래스로부터 디자인 타임 속성을 상속받기 때문인데 만약 여러분이 작성한 컨트롤에 여러분이 작성한 속성이 있다거나 기존 Control 클래스의 디자인 타임 속성을 오버라이드 하고 싶다면 이런 경우 여러분은 여러분의 코드에 사용자 디자이너 속성을 추가합니다.
여러분이 사용할 수 있는 디자인 타임 Attribute들의 종류는 다음 링크를 참조하시기 바랍니다.
Design-time Attributes for Components
다음은 사용 예입니다.
#if NETCFDESIGNTIME
[
System.ComponentModel.Browsable(true),
System.ComponentModel.RecommendedAsConfigurable(true),
System.ComponentModel.Category("Button Images"),
System.ComponentModel.Description("Default Button Image")
]
#endif
public Image DefaultImage
{
get
{
return this.defaultImage;
}
set
{
this.defaultImage = value;
if(this.defaultImage != null)
this.Size = this.defaultImage.Size;
#if NETCFDESIGNTIME
this.Invalidate();
#endif
}
}
위와 같이 어트리뷰트를 지정했을 때 디자인 타임에서 속성창에는 다음과 같이 보여지게 됩니다.
부연 설명하자면 Browsable(true)은 속성창에 해당 Property를 보여주겠다는 뜻입니다. 이 값을 false로 하게 되면 속성창에 해당 Property 즉 DefaultImage Property는 보여지지 않을 것입니다. 다음 RecommendedAsConfigurable(true)는 그림에서와 같이 해당 Property의 에디트 창의 문자를 굵게 표시하라는 뜻입니다. 이렇게 함으로써 사용자는 이 Property가 내가 에디트 해줘야 하는 값이구나 하는 것을 알 수가 있습니다. 만약 이 값을 false로 주면 동작->Enabled 속성과 마찬가지로 에디트 창의 문자가 가늘게 표시될 것입니다. 사용자는 이것을 보고 이런 속성들은 디폴트로 놔두어도 무방할 것이라고 짐작할 수 있습니다. 커스텀 컨트롤을 작성하다보면 때론 Control 클래스로부터 상속받은 기존 Property가 속성창에 나오지 말아야 할 경우도 있습니다. 이런 경우는 해당 속성을 override하고 위의 Browsable 어트리뷰트를 false로 주면 해결 할 수 있습니다.
5. 컨트롤 아이콘 추가하기
이제 여러분은 거의 모든 작업을 끝마쳤습니다. 회사내부에서나 개인적으로 컨트롤을 만들어서 사용할 경우에는 이번 장이 필요없습니다. 그러나 자신이 만든 컨트롤을 배포하려고 할 때는 조금더 노력해서 여러분의 컨트롤이 도구 상자에 올라갔을때 다른 윈도우 컨트롤처럼 자신만의 아이콘 이미지를 가질 수 있도록 하는게 좋겠죠.
아이콘 이미지를 만들기 위해서 여러분은 먼저 솔루션 탐색기를 열고 추가->새항목 추가->비트맵 파일을 선택하고 파일 이름을 적어주는데 이 때 이 이름은 반드시 아이콘으로 만들 대상이 되는 컨트롤 클래스명과 동일해야 합니다. 예를 들어 위의 경우는 ImageButton 클래스이므로 비트맵 파일의 이름은 ImageButton.bmp가 될 것입니다. 원하는대로 아이콘을 디자인 합니다. 그리고 반드시 이 이미지를 포함 리소스 속성으로 지정한 후 빌드합니다. 이렇게 하면 우리가 추가한 아이콘은 우리의 런타임 어셈블리에 들어가게 될 것입니다.
다음은 Reflector라는 프로그램으로 런타임 어셈블리의 리소스를 확인하는 화면입니다. 그런데 이상한게 있군요. 우리는 분명히 bmp 이름을 ImageButton.bmp라고 주었는데 런타임 어셈블리의 리소스에는 CustomControl.ImageButton.bmp라고 되어 있습니다. 이것으로 우리는 런타임 어셈블리를 빌드할 때 리소스명 앞에 네임스페이스명이 붙는다는 것을 알 수 있습니다. 왜 이 사실을 확인했는가 하는 것은 뒤에 살펴 보기로 하겠습니다.
6. 디자인 타임 어셈블리 빌드하기
자 이제 빌드하는 것만 남았습니다. 실수하지 않고 한 번에 성공하시길 바랍니다. 첨부된 파일을 보시면 builddesign.bat 라는 파일이 있을 겁니다. 이 파일의 내부는 다음과 같습니다.
REM ECHO USAGE sdectrlbld <library name> <file 1> <file 2> ... <file n> <flag 1> <flag 2> ... <flag n> /res:???.bmp
csc /noconfig /define:NETCFDESIGNTIME /target:library /out:design.%1 %2 %3 %4 %5 %6 %7 %8 %9 /r:.System.CF.Design.dll /r:.System.CF.Windows.Forms.dll /r:.System.CF.Drawing.dll /r:System.Windows.Forms.dll
/r:System.Drawing.dll /r:System.dll /r:System.XML.dll /r:System.Web.Services.dll /r:System.Data.dll /nowarn:1595
이제 builddesign.bat, System.CF.Windows.dll, System.CF.Design.dll, System.CF.Drawing.dll, ImageButton.bmp 파일 모두를 프로젝트 안 소스 파일이 있는 곳으로 가져옵니다.
시작->모든 프로그램->Microsoft Visual Studio .NET 2003->Visual Studio .NET 도구->Visual Studio .NET 2003 명령 프롬프트
를 실행합니다. 참고로 이 프롬프트를 시작 밑에 두고 기본 프롬프트로 사용하시는게 편합니다.
이제 우리 프로젝트가 있는 폴더로 이동합니다.
그 다음 사용법은 다음 그림과 같습니다.
자 아무런 에러 없이 빌드가 성공했습니다. 그럼 이제 다시 리플렉터로 생성된 디자인 타임 어셈블리의 리소스를 확인해보겠습니다.
이런 디자인 타임에 들어간 아이콘 이미지 이름과 런타임에 들어간 아이콘 이미지 이름이 서로 맞질 않는군요. 이로써 우리는 디자인 타임을 빌드할 때는 런타임에서처럼 네이스페이스명이 자동으로 붙지 않는다는 것을 알 수 있습니다. 런타임 어셈블리와 디자인 타임 어셈블리의 리소스가 일치하지 않으면 여러분이 컨트롤을 툴박스에 추가한 후에도 그레이 상태가 되어 사용할 수가 없습니다. 따라서 우리는 디자인 타임에 들어가는 리소스명을 런타임의 그것과 동일하게 맞춰주기 위해 다시 빌드할 필요가 있습니다.
그럼 비쥬얼 스튜디오에서 다음과 같이 리소스명을 바꿔줍니다.
ImageButton.bmp -> CustomControl.ImageButton.bmp
그런 다음 런타임은 다시 빌드하지 말고 디자인 타임만 다시 빌드해 줍니다.
다시 성공적으로 디자인 타임 어셈블리가 생성되었습니다. 디자인 타임 어셈블리는 런타임 어셈블리명앞에 design. 이 붙은 이름으로 생성됩니다. 위에 /out:design.%1 부분입니다.
7. 우리가 만든 컨트롤 추가하기
이제 런타임과 디자인 타임 어셈블리 모두 성공적으로 빌드가 되었으므로 이것을 실제로 추가할 차례입니다. 그러기 위해서 우리는 런타임 어셈블리는
C:Program FilesMicrosoft Visual Studio .NET 2003CompactFrameworkSDKv1.0.5000Windows CE
의 위치에, 디자인 타임 어셈블리는
C:Program FilesMicrosoft Visual Studio .NET 2003CompactFrameworkSDKv1.0.5000Windows CEDesigner
의 위치에 복사해 넣으시기 바랍니다. 반드시 이렇게 해야 하는 것은 아니지만 이렇게 함으로써 관리를 쉽게 할 수 있습니다.
이제 우리가 만든 컨트롤을 사용할 데모 프로젝트를 하나 만들고 폼을 연 후 도구 상자에서 추가/삭제를 선택합니다. 그리고 위의 위치로 가서 디자인 타임 어셈블리를 선택합니다.
그럼 위와 같이 해당 디자인 타임 어셈블리 안에 있는 컨트롤명이 나옵니다. 여기서 체크하는 항목들이 도구 상자에 나타나게 될 것입니다. 이미 체크가 되어 있으므로 그대로 확인을 누르고 진행합니다. (이미 체크되어 있는 항목들의 체크를 해제하면 도구 상자에서 사라집니다. )
우리가 작성한 컨트롤이 도구 상자에 성공적으로 추가 되었군요. 아이콘도 잘 보입니다. 여기까지 성공적으로 끝마치셨다면 디자인 타임 구현에 성공한 것입니다. :)
정리
이미 알고 있는 내용을 정리해서 올리는 것인데도.. 문서 작성이 서툴러서 한참을 붙잡고 씨름했습니다. 그동안 계속 다른 분들이 작성한 아티클만 읽어오다가 compact framework 쪽의 자료가 부족한 것 같아 부족한 지식이나마 공유하고자 아티클 작성을 시도해 봤습니다. 부디 어느 한 분에게라도 도움이 되었으면 하는 바람입니다. 앞으로도 Compact Framework 에 대한 issue들을 가지고 찾아뵙도록 하겠습니다. 읽어주셔서 감사합니다.