[Silverlight] Custom Control 작성법
2010. 5. 21. 11:22ㆍWEB/Silverlight
이 문서는 Silverlight 3.0 기준으로 Style, ControlTemplate, XML Namespance, UserControl에 기본적인 지식이 있는 사용자를 대상으로 작성되었습니다.
Silverlight에서는 기본적으로 UserControl을 지원합니다. 이 UserControl은 완전히 외부와 분리된, 캡슐화된 컨트롤을 생성할 수 있으며, 다양한 곳에서 재사용될 수 있습니다. 하지만 만약 기능은 동일하지만 UI만 다르게 보여주고 싶다면 어떻게 해야 할까요? 물론 다양한 방법이 있겠지만 UserControl의 로직과 디자인이 모두 수정되어야 할 가능성이 높거나, 혹은 디자인 별로 N개의 컨트롤을 생성해야 할 것입니다.
여기서 제시하는 CustomControl을 사용하면, Logic의 수정 없이 디자이너는 작성된 Control의 UI를 쉽게 변경할 수 있습니다. 기본적으로 제공되는 다른 Silverlight Control 처럼 Style을 지정하거나 ControlTemplate를 사용할 수 있게 됩니다.
작성법
여기에서는 예제로 WatermarkTextBox를 제작해보도록 하겠습니다.- 먼저 ‘Silverlight Class Library Project’를 생성합니다. 이 프로젝트를 생성하면 class파일인 .cs파일이 덩그러니 생성될 것입니다. 먼저 이 파일을 삭제합니다.
- 그리고 생성할 CustomControl명의 Class파일을 추가합니다. 여기서 저는 WatermarkTextBox 생성할 것이므로 WatermarkTextBox.cs파일을 추가하였습니다. 그리고 TextBox Class를 상속받습니다. 여기서 TextBox가 아니라 DatePicker 같은 다른 컨트롤을 상속 받을 수 있습니다. 무엇을 상속 받느냐에 따라 어떤 Property를 기본적으로 사용할 수 있는지가 결정됩니다. 앞으로 이 파일에 Control의 Logic 부분을 작성하게 될 것입니다.123456789101112131415161718
using
System;
using
System.Net;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Documents;
using
System.Windows.Ink;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Animation;
using
System.Windows.Shapes;
namespace
Nsi.Silverlight.Controls
{
public
class
WatermarkTextBox : TextBox
{
}
}
- Control의 외관을 지정하기 위해서는 ‘themes’ 폴더를 생성하고, 그 폴더 안에 ‘generic.xaml’파일을 생성해야 합니다. ‘themes’ 폴더를 생성하고 내부에 XML파일을 ‘generic.xaml’파일명으로 추가합니다.
- 추가된 ‘generic.xaml’파일은 기본적으로 ‘Build Action’이 ‘Page’로 설정되어 있을 것입니다. ‘Page’에서 ‘Resurce’로 변경합니다.
- 또 한가지 더 ‘Custom Tool’ 값으로 설정된 ‘MsBuild:MarkupCompilePass1’를 삭제합니다.
- generic.xaml은 다음과 같은 기본적은 코드를 포함해야 합니다. 물론 기존의 코드가 있다면 삭제하고 다음과 같은 코드를 입력합니다.
이로써 generic.xaml은 Resource Dictionary로서 Custom Control의 외형을 제공하는 Template 파일로 환골탈태하였습니다.1234
<
ResourceDictionary
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
>
</
ResourceDictionary
>
- Control의 Style과 Template를 작성해줍니다. Style의 TargetType을 설정해 주어야 하는데, TargetType는 여기서 작성하는 CustomControl을 지정해줍니다.
12345678
<
ResourceDictionary
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local
=
"clr-namespace:Nsi.Silverlight.Controls"
>
<
Style
TargetType
=
"local:WatermarkedTextBox"
>
</
Style
>
</
ResourceDictionary
>
- 이제 Control의 Template을 지정해줍니다. Style의 하위 엘리먼트인 Setter에 Property="Template"을 추가하고, ControlTemplate을 작성하여 Control의 외형을 잡아줍니다.
위 코드에서 TemplateBinding의 값들은 모두 Class파일에서 상속했던 TextBox에서 기본적으로 제공되는 것입니다. 만약 여기에 없는 Property를 TemplateBinding으로 사용하고 싶다면, 해당 Property를 Class파일에 DependencyProperty로 선언해주어야 합니다. 위에서는 WatermarkText라는 새로운 Property를 TemplateBinding으로 사용하고 있습니다.1234567891011121314151617181920212223242526272829303132333435363738
<
ResourceDictionary
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local
=
"clr-namespace:Nsi.Silverlight.Controls"
>
<
Style
TargetType
=
"local:Nsi.Silverlight.Controls"
>
<
Setter
Property
=
"Template"
>
<
Setter.Value
>
<
ControlTemplate
TargetType
=
"local:WatermarkTextBox"
>
<
Grid
x:Name
=
"Root"
Width
=
"{TemplateBinding Width}"
Cursor
=
"{TemplateBinding Cursor}"
Height
=
"{TemplateBinding Height}"
Background
=
"{TemplateBinding Background}"
VerticalAlignment
=
"{TemplateBinding VerticalAlignment}"
HorizontalAlignment
=
"{TemplateBinding HorizontalAlignment}"
>
<
VisualStateManager.VisualStateGroups
>
<
VisualStateGroup
x:Name
=
"WatermarkStates"
>
<
VisualStateGroup.Transitions
>
<
VisualTransition
From
=
"WatermarkHidden"
GeneratedDuration
=
"00:00:00.0000000"
To
=
"WatermarkVisible"
/>
</
VisualStateGroup.Transitions
>
<
VisualState
x:Name
=
"WatermarkVisible"
>
<
Storyboard
>
<
DoubleAnimationUsingKeyFrames
BeginTime
=
"00:00:00"
Duration
=
"00:00:00.0000000"
Storyboard.TargetName
=
"WaterMarkTextBlock"
Storyboard.TargetProperty
=
"(UIElement.Opacity)"
>
<
EasingDoubleKeyFrame
KeyTime
=
"00:00:00"
Value
=
"1"
/>
</
DoubleAnimationUsingKeyFrames
>
</
Storyboard
>
</
VisualState
>
<
VisualState
x:Name
=
"WatermarkHidden"
>
<
Storyboard
>
<
DoubleAnimationUsingKeyFrames
BeginTime
=
"00:00:00"
Duration
=
"00:00:00.0000000"
Storyboard.TargetName
=
"WaterMarkTextBlock"
Storyboard.TargetProperty
=
"(UIElement.Opacity)"
>
<
EasingDoubleKeyFrame
KeyTime
=
"00:00:00"
Value
=
"0"
/>
</
DoubleAnimationUsingKeyFrames
>
</
Storyboard
>
</
VisualState
>
</
VisualStateGroup
>
</
VisualStateManager.VisualStateGroups
>
<
TextBlock
x:Name
=
"WatermarkTextBlock"
Text
=
"{TemplateBinding WatermarkText}"
Margin
=
"3"
VerticalAlignment
=
"Center"
/>
<
TextBox
x:Name
=
"WatermarkedTextBox"
Background
=
"Transparent"
Foreground
=
"{TemplateBinding Foreground}"
Padding
=
"{TemplateBinding Padding}"
VerticalContentAlignment
=
"{TemplateBinding VerticalContentAlignment}"
/>
</
Grid
>
</
ControlTemplate
>
</
Setter.Value
>
</
Setter
>
</
Style
>
</
ResourceDictionary
>
- 이제 Class파일로 돌아옵니다. 생성자를 생성하고 DefaultStyleKey를 generic.xaml에서 지정한 Style을 사용하도록 다음과 같이 코드를 작성합니다.
위 코드를 작성함으로써 시스템은 generic.xaml의 WatermarkedTextBox타입의 Style를 기본 Style로 사용합니다.12345678910
namespace
Nsi.Silverlight.Controls
{
public
class
WatermarkTextBox : Control
{
public
WatermarkTextBox()
{
DefaultStyleKey =
typeof
(WatermarkTextBox);
}
}
}
- 이제 generic.xaml에 정의된 각 컨트롤과 상호작용을 해야 합니다. 그러기 위해서는 가장 먼저 generic.xaml에 정의된 컨트롤을 찾아오는 것입니다. 컨트롤을 찾기는 OnApplyTemplate()메서드를 오버라이딩하고, OnApplyTemplate()메서드 내에서 GetTemplateChild()메서드에 해당 컨트롤 명을 넘겨줌으로써 가능합니다.
1234567891011121314151617181920212223242526272829303132333435363738394041
using
System;
using
System.Net;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Documents;
using
System.Windows.Ink;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Animation;
using
System.Windows.Shapes;
namespace
Nsi.Silverlight.Controls
{
public
class
WatermarkTextBox : Control
{
//Control명을 지정
public
const
string
RootElementName =
"Root"
;
public
const
string
WatermarkTextBlockElementName =
"WatermarkTextBlock"
;
public
const
string
WatermarkedTextBoxElementName =
"WatermarkedTextBox"
;
//찾은 Control을 담고 있을 멤버변수
private
Grid RootElement;
private
TextBlock WatermarkTextBlock;
private
TextBox WatermarkedTextBox;
public
WatermarkTextBox()
{
DefaultStyleKey =
typeof
(WatermarkTextBox);
}
//generic.xaml에 정의된 컨트롤 찾기
public
override
void
OnApplyTemplate()
{
base
.OnApplyTemplate();
//Control 찾기
RootElement = GetTemplateChild(RootElementName)
as
Grid;
WatermarkTextBlock = GetTemplateChild(WatermarkTextBlockElementName)
as
TextBlock;
WatermarkedTextBox = GetTemplateChild(WatermarkedTextBoxElementName)
as
TextBox;
}
}
}
- 컨트롤을 찾았으면, 이벤트나 필요한 설정 등을 추가적으로 해줍니다.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
using
System;
using
System.Net;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Documents;
using
System.Windows.Ink;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Animation;
using
System.Windows.Shapes;
namespace
Nsi.Silverlight.Controls
{
public
class
WatermarkTextBox : Control
{
//Control Name 지정
public
const
string
RootElementName =
"Root"
;
public
const
string
WatermarkTextBlockElementName =
"WatermarkTextBlock"
;
public
const
string
WatermarkedTextBoxElementName =
"WatermarkedTextBox"
;
//찾은 Control을 담고 있을 멤버변수
private
Grid RootElement;
private
TextBlock WatermarkTextBlock;
private
TextBox WatermarkedTextBox;
public
WatermarkTextBox()
{
DefaultStyleKey =
typeof
(WatermarkTextBox);
}
//generic.xaml에 정의된 컨트롤 찾기
public
override
void
OnApplyTemplate()
{
base
.OnApplyTemplate();
RootElement = GetTemplateChild(RootElementName)
as
Grid;
WatermarkTextBlock = GetTemplateChild(WatermarkTextBlockElementName)
as
TextBlock;
WatermarkedTextBox = GetTemplateChild(WatermarkedTextBoxElementName)
as
TextBox;
//Attach Events
WatermarkedTextBox.GotFocus -=
new
RoutedEventHandler(WatermarkedTextBox_GotFocus);
WatermarkedTextBox.LostFocus -=
new
RoutedEventHandler(WatermarkedTextBox_LostFocus);
WatermarkedTextBox.TextChanged -=
new
TextChangedEventHandler(WatermarkedTextBox_TextChanged);
WatermarkedTextBox.GotFocus +=
new
RoutedEventHandler(WatermarkedTextBox_GotFocus);
WatermarkedTextBox.LostFocus +=
new
RoutedEventHandler(WatermarkedTextBox_LostFocus);
WatermarkedTextBox.TextChanged +=
new
TextChangedEventHandler(WatermarkedTextBox_TextChanged);
}
//Events
private
void
WatermarkedTextBox_GotFocus(
object
sender, RoutedEventArgs e)
{
VisualStateManager.GoToState(
this
,
"WatermarkHidden"
,
false
);
}
private
void
WatermarkedTextBox_LostFocus(
object
sender, RoutedEventArgs e)
{
if
(
string
.IsNullOrEmpty(
this
.WatermarkedTextBox.Text))
VisualStateManager.GoToState(
this
,
"WatermarkVisible"
,
true
);
}
private
void
WatermarkedTextBox_TextChanged(
object
sender, RoutedEventArgs e)
{
if
(
this
.WatermarkedTextBox.Text.Trim() ==
""
)
VisualStateManager.GoToState(
this
,
"WatermarkVisible"
,
true
);
else
VisualStateManager.GoToState(
this
,
"WatermarkHidden"
,
true
);
}
}
}
- 이제 generic.xaml에서 사용한 WatermarkText라는 Property를 DependencyProperty로 선언해야 합니다. DepdendencyProperty를 선언하기 위해서는 기존 Property선언과 비교할 때, 조금 더 코드를 입력해주면 됩니다. 먼저 외부에 공개될 Property에서 사용할 DependencyProperty를 먼저 등록해줍니다.
PropertyMetadata에는 두 가지 값을 설정할 수 있는데, 이 컨트롤이 로드 될 때 설정될 기본 값과, 이 값이 변경될 때 호출될 CollBack method(쉽게 말해 Changed 이벤트)를 지정할 수 있습니다. 참고하시기 바랍니다.12
//DependencyProperty
public
static
DependencyProperty WatermarkTextProperty = DependencyProperty.Register(
"WatermarkText"
,
typeof
(
string
),
typeof
(WatermarkedTextBox),
new
PropertyMetadata(
""
));
- 이제 외부에 공개될 Public Property를 다음과 같이 DependencyProperty를 사용해서 선언해줍니다.
12345
public
string
WatermarkText
{
get
{
return
(
string
)GetValue(WatermarkTextProperty); }
set
{ SetValue(WatermarkTextProperty, value); }
}
- 이제 마지막으로 TemplatePart를 추가해주어야 합니다. 지정하지 않아도 크게 동작에는 문제가 없습니다. 그런데도 TemplatePart를 지정하는 이유는 이 CustomControl의 UI를 디자이너가 변경하고자 할 때, 반드시 포함되어야 하는 Control 명을 디자이너에게 알려주기 위해서입니다. class의 상단에 다음과 같이 코드를 추가합니다.
1234567
[TemplatePart(Name = WatermarkTextBox.RootElementName, Type =
typeof
(Grid))]
[TemplatePart(Name = WatermarkTextBox.WatermarkTextBlockElementName, Type =
typeof
(TextBox))]
[TemplatePart(Name = WatermarkTextBox.WatermarkTextBoxElementName, Type =
typeof
(TextBox))]
public
class
WatermarkTextBox : Control
{
...
}
- 이제 테스트 해볼 차례입니다. 프로젝트를 Build하면 Bin/Debug폴더에 WatermarkTextbox.dll이 생성될 것입니다.
이 파일을 사용할 Silverlight Project에 Add Reference를 통해 추가하고 페이지에 다음과 같은 코드로 컨트롤을 사용해봅시다.123456789<
UserControl
x:Class
=
"NsiDatePicker.MainPage"
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:watermarkTextBox
=
"clr-namespace:Nsi.Silverlight.Controls;assembly=WatermarkTextBox"
Width
=
"400"
Height
=
"300"
>
<
StackPanel
x:Name
=
"LayoutRoot"
Background
=
"White"
>
<
watermarkTextBox:WatermarkTextBox
WatermarkText
=
"입력해주세요."
/>
</
StackPanel
>
</
UserControl
>
결과화면
'WEB > Silverlight' 카테고리의 다른 글
[Silverlight] SortDescriptions in Silverlight DataGrid (0) | 2011.02.23 |
---|---|
[Silverlight] 컨트롤에 Validation Tooltip 적용하기 (0) | 2010.05.22 |
[Silverlight] 'ServiceReferences.ClientConfig'을(를) 열 수 없습니다. (0) | 2010.04.15 |
[Silverlight] Element to Element Binding (0) | 2009.11.17 |
[Silverlight] Create CheckBoxList in Silverlight (0) | 2009.11.16 |