검색결과 리스트
글
TypeConverter
(xaml 에 string 을 값으로 넣기)
Color 는 문자열 "Red" 도 인식한다?
xaml 을 작성하다보면, 색과 관련된 Property 을 수시로 하게 됩니다. Rectangle 이나 Ellipse 의 Fill 을 하는 경우나, Window 배경색을 바꾼다거나 하는 것들 말이죠. 이 경우 xaml 에서 보통 hex 값을 넣거나 정의된 색값(Red, Yellow 등)을 넣습니다. 물론 그라데이션 블러쉬나 Solid Color Brush 로 감싸주는 경우도 있고요.
앞선 포스팅에서도 있었습니다. 바로 아래 코드입니다.
<Ellipse Width="50" Height="50" Fill="Red"></Ellipse>
주의해서 볼 곳은 Fill="Red" 이 부분입니다. Ellipse 클래스를 보면, Fill 프로퍼티의 타입은 Brush 입니다. 그런데 위 코드에서는 문자열(Red)을 넣었습니다. 이게 어떻게 가능한 것일까요?
Test Code
먼저 일반적인 테스트를 위해서 코드를 작성해 보려고 합니다. 약간 억지스러운(?) 코드입니다. 적절한 예를 찾지 못하겠네요 :)
SenseOfTouch 라는 클래스가 있습니다. 생성자는 enum 값인 Sense 를 받습니다. Hot 과 Cold 두가지가 있지요.
그리고 Property(Dependency Property) 로 이 SenseOfTouch 을 갖는 Control 을 하나 만들었습니다. 코드는 아래와 같습니다.
public class SenseOfTouch
{
public enum Sense { Hot, Cold }
public Sense Feel { get; set; }
public SenseOfTouch()
{
}
public SenseOfTouch(Sense sense)
{
this.Feel = sense;
}
public override string ToString()
{
return this.Feel.ToString();
}
}
<UserControl x:Class="Test.SenseOfTouchControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:test="clr-namespace:Test"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding Feel, RelativeSource={RelativeSource AncestorType=test:SenseOfTouchControl}}"/>
</Grid>
</UserControl>
public partial class SenseOfTouchControl : UserControl
{
public SenseOfTouch Feel {
get { return (SenseOfTouch)GetValue(FeelProperty); }
set { SetValue(FeelProperty, value); }
}
// Using a DependencyProperty as the backing store for Feel. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FeelProperty =
DependencyProperty.Register("Feel", typeof(SenseOfTouch), typeof(SenseOfTouchControl), new PropertyMetadata(new SenseOfTouch(SenseOfTouch.Sense.Hot)));
public SenseOfTouchControl() {
InitializeComponent();
}
}
복잡해 보이지만, 간단합니다. SenseOfTouch 라는 Control 이 있는데, 프로퍼티로 SenseOfTouch 가 있고, 이 값을 화면에 출력하는게 전부입니다.
<TextBlock Text="{Binding Feel, RelativeSource={RelativeSource AncestorType=test:SenseOfTouchControl}}"/>
이 부분은 프로퍼티의 내용을 TextBlock 에 뿌리겠다라는 건데, Binding 이라고 합니다. 다음에 설명토록 하겠습니다. 지금은 그냥 넘어갑니다.
이제 이 SenseOfTouchControl 을 MainWindow 에 붙여넣고 실행해보도록 하겠습니다.
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:Test"
Title="MainWindow" Height="100" Width="200">
<Grid>
<test:SenseOfTouchControl></test:SenseOfTouchControl>
</Grid>
</Window>
Hot 이라고 화면에 출력되네요. 이유는 SenseOfTouchControl 에서 프로퍼티 초기값(Default)를 Hot 으로 주었기 때문입니다.
public static readonly DependencyProperty FeelProperty =
DependencyProperty.Register("Feel", typeof(SenseOfTouch), typeof(SenseOfTouchControl), new PropertyMetadata(new SenseOfTouch(SenseOfTouch.Sense.Hot)));
이제 MainWindow 에서 SenseOfTouchControl 의 Feel 값을 Cold 로 변경해 봅시다. static 으로 값을 넣으려면, 미리 값이 있어야 합니다.
public class SensesOfTouch
{
private static SenseOfTouch _Hot = new SenseOfTouch(SenseOfTouch.Sense.Hot);
private static SenseOfTouch _Cold = new SenseOfTouch(SenseOfTouch.Sense.Cold);
public static SenseOfTouch Hot
{
get { return _Hot; }
}
public static SenseOfTouch Cold {
get { return _Cold; }
}
}
이렇게 만들어 두고,
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:Test"
Title="MainWindow" Height="100" Width="200">
<Grid>
<test:SenseOfTouchControl Feel="{x:Static test:SensesOfTouch.Cold}"></test:SenseOfTouchControl>
</Grid>
</Window>
이렇게 프로퍼티 값을 넣어주면 됩니다. Color 를 예로 들자면 이런 셈이지요.
<Ellipse Fill="{x:Static Colors.Red}"></Ellipse>
만일 우리도 Fill 이 "Red" 와 같이 문자열도 값으로 받도록 하려면 어떻게 해야 될까요? 무작정 해 보죠.
<test:SenseOfTouchControl Feel="Cold"></test:SenseOfTouchControl>
잘 되나요? 안타깝게도 빌드가 되지 않습니다.
TypeConverter
지금 시점에 우리에게 필요한것은 바로 TypeConverter 입니다. Color 나 Width / Height 의 Length 정보등이 xaml 에서 문자열을 값으로 받을 수 있는 이유가 바로 이것에 있습니다. xaml 에서 어떤 타입(대게 문자열)을 받았을 때, 이를 원하는 값으로 바꿀수 있게 해 주는 것이죠.
SenseOfTouch 클래스에 TypeConverter 라는 Attribute 를 추가해 줍니다. 짐작하시겠지만, 해당 클래스의 값을 변환할 Converter 를 지정해 주는 것입니다. 더불어 편의를 위해서 Parse 메소드도 추가해 주었습니다.
[TypeConverter(typeof(SenseOfTouchTypeConverter))]
public class SenseOfTouch
{
public enum Sense { Hot, Cold }
public Sense Feel { get; set; }
public SenseOfTouch()
{
}
public SenseOfTouch(Sense sense)
{
this.Feel = sense;
}
public static SenseOfTouch Parse(string feel)
{
if (string.IsNullOrEmpty(feel) == false)
{
return new SenseOfTouch((Sense)Enum.Parse(typeof(Sense), feel));
}
throw new FormatException("Cannot parse.");
}
public override string ToString()
{
return this.Feel.ToString();
}
}
이제 TypeConverter 를 작성해 보도록 하겠습니다. override 해야할 메소드를 보면 ConvertTo 와 ConvertFrom 이 있습니다. 그리고 각각이 가능한지 Can 이 붙은 CanConverTo / CanConvertFrom 이 있습니다. From 은 xaml 에 넣은 값으로 해당 Object 를 만드는 것이고, To 는 이 반대입니다.
public class SenseOfTouchTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
if (sourceType == typeof(string)) { return true; }
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
if (destinationType == typeof(string)) { return true; }
return base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) {
string text = value as string;
if(text != null)
{
return SenseOfTouch.Parse(text);
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null) { throw new ArgumentNullException("destination Type is null"); }
SenseOfTouch sense = value as SenseOfTouch;
if(sense != null && this.CanConvertTo(context, destinationType))
{
return sense.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
이제 모든 준비가 끝났습니다. 이제 Feel 의 값으로 문자열(Cold 또는 Hot)을 넣을 수 있게 되었습니다.
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:Test"
Title="MainWindow" Height="100" Width="200">
<Grid>
<test:SenseOfTouchControl Feel="Cold"></test:SenseOfTouchControl>
</Grid>
</Window>
확인
한번 Ellipse 의 Fill 을 살펴보죠. 정말 TypeConverter 가 있는지, 그리고 어떻게 생겼는지.
public Brush Fill
{
get { return (Brush) base.GetValue(FillProperty); }
set { base.SetValue(FillProperty, value); }
}
Fill 의 타입은 Brush 군요. 그럼 Brush 를 살펴보죠. TypeConverter 가 선언되어 있을 겁니다.
[Localizability(LocalizationCategory.None, Readability=Readability.Unreadable), TypeConverter(typeof(BrushConverter)), ValueSerializer(typeof(BrushValueSerializer))]
public abstract class Brush : Animatable, IFormattable, DUCE.IResource
{
// 생략
Brush 의 TypeConverter 로 BrushConverter 가 정의되어 있네요. 살펴보죠
public sealed class BrushConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return ((sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (!(destinationType == typeof(string)))
{
return base.CanConvertTo(context, destinationType);
}
if ((context == null) || (context.Instance == null))
{
return true;
}
if (!(context.Instance is Brush))
{
throw new ArgumentException(MS.Internal.PresentationCore.SR.Get("General_Expected_Type", new object[] { "Brush" }), "context");
}
Brush instance = (Brush) context.Instance;
return instance.CanSerializeToString();
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
{
throw base.GetConvertFromException(value);
}
string str = value as string;
if (str != null)
{
return Brush.Parse(str, context);
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if ((destinationType != null) && (value is Brush))
{
Brush brush = (Brush) value;
if (destinationType == typeof(string))
{
if (((context != null) && (context.Instance != null)) && !brush.CanSerializeToString())
{
throw new NotSupportedException(MS.Internal.PresentationCore.SR.Get("Converter_ConvertToNotSupported"));
}
return brush.ConvertToString(null, culture);
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
Brush 이외에도 수 많은 클래스들이 Converter 를 가지고 있는 것을 한번 직접 확인해 보시는것도 좋을 것 같습니다.
'Microsoft > WPF' 카테고리의 다른 글
MEF(Managed Extensibility Framework) (2) | 2014.05.19 |
---|---|
화면에 노출되는 프로퍼티는? (1) | 2014.05.13 |
XAML 의 문법구조 (0) | 2014.05.12 |
xaml 과 cs 파일의 관계 (1) | 2014.05.08 |
WPF 기본 동작구조 (0) | 2014.05.08 |
RECENT COMMENT