InkPresenter 방명록 만들기 - 5. Color Palette User Control 만들기


1. InkPresenter 객체 겉핥기 (InkPresenter XAML 구조)
2. InkPresenter를 사용한 간단한 샘플 만들어보기 (작동완구)
3. Undo & Redo 구현하기
4. InkPresenter User Control 만들기
5. Color Palette User Control 만들기
6. Ink Thickness Palette User Control 만들기
7. HTTP Request와 Response를 이용하여 정보 교환하기 (Get, Post 전송)
8. Page Navigation User Control 만들기

사용자 삽입 이미지



0. Sample Project

체험장 :  http://shiverlight.net/InkPresenterSample/InkPresentationSample_Palette


1. Palette User Control 디자인 (UCColorPalette.xaml)

Expression Blend에서 원하는 모습으로 User Control을 디자인 합니다.

사용자 삽입 이미지

사용자 삽입 이미지

여러분 이제 오브젝트 트리만 봐도 감이 팍팍 오시죠?

왼쪽 부분을 통해 현재 색상을 표현해주고,
오른쪽 부분은 사용자가 색상을 고를 수 있게 합니다.

사용자 삽입 이미지

팔레트를 보였다 숨겼다하는 토글 방식 때 사용하기 위한 스토리보드를 두 개 정도 만들어 주었습니다.

사용자가 보게 될 각 색상 UI는 _cvColorSet에 직접 해당 색상이 있는 Rectangle 등을 통해서 직관적으로 작업할 수도 있습니다만,
색상이 많아 질수록 동적으로 생성해 주는 것이 더 편하다고 생각되어, 이 강좌에서는
코드로 생성하도록 하겠습니다.


2. Color Palette 비하인드 코드 작성 (UCColorPalette.xaml.cs)

UI 엘리먼트를 컨트롤 하기 위한 변수를 추가합니다.

//////////////////////////////////////////////////////////////////////////////////////
// UI Elements

// 기본
Canvas _cvMyself;

// 현재 색상
Canvas _cvCurColor;
Rectangle _btnCurColor;

// 컬러 셋
Canvas _cvColorSet;
Rectangle _rcPalette;

// 스토리보드 : 토글
Storyboard _sbToggleShow;
Storyboard _sbToggleHide;

생성자에서 FindName 함수를 이용하여 UI를 찾아서 해당변수에 할당합니다.

public UCColorPalette()
{
    System.IO.Stream s =
        this.GetType().Assembly.GetManifestResourceStream("InkPresentationSample_Palette.UCColorPalette.xaml");
    _cvMyself = this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd()) as Canvas;

    _btnCurColor = _cvMyself.FindName("_btnCurColor") as Rectangle;
    _rcPalette = _cvMyself.FindName("_rcPalette") as Rectangle;

    _cvCurColor = _cvMyself.FindName("_cvCurColor") as Canvas;
    _cvColorSet = _cvMyself.FindName("_cvColorSet") as Canvas;

    _sbToggleShow = _cvMyself.FindName("ToggleShow") as Storyboard;
    _sbToggleHide = _cvMyself.FindName("ToggleHide") as Storyboard;
}

사용자 삽입 이미지

일단, 위 그림과 같은 것을 목표로 하여, 팔레트에 색상을 추가하기 위한 변수를 추가합니다.

// 팔레트 색상 수
int _nColor = 0;
public int Count { get { return _nColor; } }

// 색상 배치 관련
Size _szMargin = new Size(10,10); // 컬러셋 Rectangle과의 마진
Size _szColor = new Size(20, 20); // 한 개의 색상의 사이즈
double _dSpace = 5; // 한 색상과 다른 색상과의 거리

// 색상 리스트
List<Color> _oColorList = new List<Color>();

// 선택된 색상
Color _color = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
public Color Color { get { return _color; } }

외부로 노출되는 AddColor 메서드를 작성합니다.
색상 값 하나를 받아 _szColor를 사이즈로 갖는 Ellipse를 하나 만든 뒤에,
_cvColorSet 캔버스에 추가시켜 줍니다.
이 때 알맞게, 위치도 결정해 주고, 색상 수 _nCount도 증가시킵니다.

public void AddColor(Color color)
{
    // 색상 리스트에 추가
    _oColorList.Add(color);

    // 원형 색상 UI 추가
    Ellipse el = new Ellipse();
    el.Width = _szColor.Width;
    el.Height = _szColor.Height;
    el.Fill = new SolidColorBrush(color);
    el.Cursor = Cursors.Hand;

    el.SetValue(Canvas.LeftProperty, _szMargin.Width + _nColor * (_szColor.Width + _dSpace));
    el.SetValue(Canvas.TopProperty, _szMargin.Height);

    _cvColorSet.Children.Add(el);

    // 색상 수 증가
    _nColor++;

    UpdateUI();
}

이때 색상 수가 늘어남에 따라 UI가 변경되어야 하기 때문에 UpdateUI를 작성하여 호출합니다.

void UpdateUI()
{
    _rcPalette.Width = _szMargin.Width * 2 + (_szColor.Width + _dSpace) * _nColor - _dSpace;
    _cvColorSet.Width = _rcPalette.Width;
}

자, 이제 어떤 색상이 선택되었을 때 발생시킬 이벤트를 준비합니다.

클래스 정의 외부에 적당한 delegate를 하나 준비합니다.

namespace InkPresentationSample_Palette
{
    public delegate void PaletteEventHandler(Color color);

    public class UCColorPalette : Control
    {

아래와 같은 프로퍼티를 추가해 줍니다.

// 이벤트
public event PaletteEventHandler ColorChanged;

이벤트 하나 작성합니다.

void OnColorChanged()
{
    if (ColorChanged != null)
    {
        ColorChanged(_color);
    }
}

자 이제 이 함수를 적당한 곳에서 호출해 주기만 하면 이벤트 준비가 끝나겠죠?
어디가 적당할까요? 사용자가 ColorSet 중의 색상을 클릭할 때가 되겠죠!

AddColor 함수로 돌아가 아래와 같이 이벤트에 대한 함수를 작성합니다.

public void AddColor(Color color)
{
    // 색상 리스트에 추가
    _oColorList.Add(color);

    // 원형 색상 UI 추가
    Ellipse el = new Ellipse();
    el.Width = _szColor.Width;
    el.Height = _szColor.Height;
    el.Fill = new SolidColorBrush(color);
    el.Cursor = Cursors.Hand;
    el.MouseLeftButtonUp += new MouseEventHandler(el_MouseLeftButtonUp);

    el.SetValue(Canvas.LeftProperty, _szMargin.Width + _nColor * (_szColor.Width + _dSpace));
    el.SetValue(Canvas.TopProperty, _szMargin.Height);

    _cvColorSet.Children.Add(el);

    // 색상 수 증가
    _nColor++;

    UpdateUI();
}

void el_MouseLeftButtonUp(object sender, MouseEventArgs e)
{
    if (sender == null)
        return;

    Ellipse el = sender as Ellipse;
    SolidColorBrush brush = el.Fill as SolidColorBrush;

    SetColor(brush.Color);
}

void SetColor(Color color)
{
    _color = color;
    _btnCurColor.Fill = new SolidColorBrush(_color);

    // 이벤트 발생
    OnColorChanged();
}
   
el_MouseLeftButtonUp -> SetColor -> OnColorChanged
결국 이벤트는 위와 같은 경로로 발생하게 되었구요.
User Control 내부에서든, 외부에서든, 컬러가 변경되게 되면 궁극적으로는
SetColor 함수를 호출하도록 신경을 써주시면 됩니다.

이제 아래 그림과 같이 왼쪽 부분을 누르면 ColorSet이 생겼다 없어졌다하는 토글 기능을
구현합니다.

사용자 삽입 이미지

클래스 정의 외부에 enum 타입으로 Style의 Domain을 정의합니다.
항상 보이기와, 토글 두 가지의 스타일을 지원합니다.

namespace InkPresentationSample_Palette
{
    public enum Style { AlwaysShow, Toggle }
    public delegate void PaletteEventHandler(Color color);

    public class UCColorPalette : Control
    {

프로퍼티를 추가합니다.

// 토글
Style _Style = Style.Toggle;
bool _bToggleShow = false;

여기서는 동적으로 Style을 변경할 수 있게 지원 안하고 ^^;
생성할 당시에 운명이 정해지도록 하겠습니다.
생성자에 아래와 같이 작업을 합니다.

public UCColorPalette(Style style)
{
    System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream("InkPresentationSample_Palette.UCColorPalette.xaml");
    _cvMyself = this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd()) as Canvas;

    _btnCurColor = _cvMyself.FindName("_btnCurColor") as Rectangle;
    _rcPalette = _cvMyself.FindName("_rcPalette") as Rectangle;

    _cvCurColor = _cvMyself.FindName("_cvCurColor") as Canvas;
    _cvColorSet = _cvMyself.FindName("_cvColorSet") as Canvas;

    _sbToggleShow = _cvMyself.FindName("ToggleShow") as Storyboard;
    _sbToggleHide = _cvMyself.FindName("ToggleHide") as Storyboard;

    _Style = style;

    // 접혀있는 상태
    if (_Style == Style.Toggle)
    {
        _bToggleShow = false;
        _cvColorSet.Opacity = 0;
        _btnCurColor.MouseLeftButtonUp += new MouseEventHandler(_btnCurColor_MouseLeftButtonUp);
    }
}

void _btnCurColor_MouseLeftButtonUp(object sender, MouseEventArgs e)
{
    // Palette 펼쳤다 접었다.
    _bToggleShow = !_bToggleShow;

    if (_bToggleShow == true)
    {
        ShowPalette();
    }
    else
    {
        HidePalette();
    }
}

public void ShowPalette()
{
    _sbToggleShow.Begin();
    _cvColorSet.IsHitTestVisible = true;
    _bToggleShow = true;
}

public void HidePalette()
{
    _sbToggleHide.Begin();
    _cvColorSet.IsHitTestVisible = false;
    _bToggleShow = false;
}

사용자가 ColorSet에서 색상을 골랐을 때도 Hide가 되게 합니다.

void el_MouseLeftButtonUp(object sender, MouseEventArgs e)
{
    if (sender == null)
    return;

    Ellipse el = sender as Ellipse;
    SolidColorBrush brush = el.Fill as SolidColorBrush;

    SetColor(brush.Color);
    HidePalette();

    // 이벤트 발생
    OnColorChanged();
}

자 이제 마지막으로 이 컨트롤을 가지고 작업할 프로그래머 편의를 위한 함수를
2개 정도만 추가하고 마무리합니다.

이 Palette가 현재 어떤 어떤 색상을 가지고 있는지 알 수 없기 때문에,
Index를 통해서 색상을 지정해 줄 수 있는 SelectColor(int nIndex) 함수와,
User Control의 위치를 손쉽게 변경할 수 있게 Position(Point pt) 함수를
서비스로 작성해 줍니다.

public void SelectColor(int nIndex)
{
    if (nIndex < 0 || nIndex >= _oColorList.Count)
        return;

    SetColor(_oColorList[nIndex]);
}

public void Position(Point pt)
{
    SetValue(Canvas.LeftProperty, pt.X);
    SetValue(Canvas.TopProperty, pt.Y);
}


3. 샘플 프로젝트에 적용

Page에서 Palette를 사용하기 위해 프로퍼티를 추가합니다.

Page.xaml.cs에서

UCColorPalette _oPalette = null;

Page_Loaded 함수 안에서 Palette를 생성합니다.
CreateInkEditor보다 먼저 생성해야 합니다.
이유는 곧 아시게 됩니다.

public void Page_Loaded(object o, EventArgs e)
{
    ....

    // 배경이미지 준비
    LoadBackImages();

    // Palette 생성
    CreatePalette();

    // Editor 생성
    CreateInkEditor();

    ...
}

void CreatePalette()
{
    _oPalette = new UCColorPalette(Style.Toggle);
    _oPalette.Position(new Point(405, 50));
    Children.Add(_oPalette);

    // 색상 추가 : 닥치는 대로
    _oPalette.AddColor(Color.FromRgb(0xC1, 0x31, 0x31));
    _oPalette.AddColor(Color.FromRgb(0xE3, 0x62, 0x1D));
    _oPalette.AddColor(Color.FromRgb(0xE3, 0xCF, 0x1D));
    _oPalette.AddColor(Color.FromRgb(0xB7, 0xE3, 0x1D));
    _oPalette.AddColor(Color.FromRgb(0x1D, 0xE3, 0x68));
    _oPalette.AddColor(Color.FromRgb(0x1D, 0xE3, 0xA7));
    _oPalette.AddColor(Color.FromRgb(0x1D, 0xD5, 0xE3));
    _oPalette.AddColor(Color.FromRgb(0x1D, 0x7A, 0xE3));
    _oPalette.AddColor(Color.FromRgb(0x80, 0x1D, 0xBD));
    _oPalette.AddColor(Color.FromRgb(0xFF, 0xFF, 0xFF));
    _oPalette.AddColor(Color.FromRgb(0xCC, 0xCC, 0xCC));
    _oPalette.AddColor(Color.FromRgb(0x99, 0x99, 0x99));
    _oPalette.AddColor(Color.FromRgb(0x7F, 0x7F, 0x7F));
    _oPalette.AddColor(Color.FromRgb(0x66, 0x66, 0x66));
    _oPalette.AddColor(Color.FromRgb(0x4D, 0x4D, 0x4D));
    _oPalette.AddColor(Color.FromRgb(0x33, 0x33, 0x33));
    _oPalette.AddColor(Color.FromRgb(0x1C, 0x1C, 0x1C));
    _oPalette.AddColor(Color.FromRgb(0x00, 0x00, 0x00));
    _oPalette.SelectColor(9); // White
    _oPalette.ColorChanged += new PaletteEventHandler(_oPalette_ColorChanged);
}

여기서 드디어 ColorChanged 이벤트 핸들러를 사용합니다.
_oPalette_ColorChanged 함수를 구현하기에 앞서 UCInk.xaml.cs를 방문합니다.

UCInk.xaml.cs에서 외부에서 색상을 입력 받을 프로퍼티를 추가합니다.

public Color StrokeColor { get; set; }

그리고 새 Stroke를 생성할 때 마다 그 색상을 가져다 쓰도록 합니다.

Stroke AllocStroke()
{
    // 스트로크 준비 흰색
    Stroke stroke = new Stroke();
    stroke.DrawingAttributes.Color = StrokeColor;
    stroke.DrawingAttributes.OutlineColor = Colors.Black;
    stroke.DrawingAttributes.Width = 2;
    stroke.DrawingAttributes.Height = 2;

    return stroke;
}

다시 Page.xaml.cs로 돌아와 못다한 일을 마칩니다.

void _oPalette_ColorChanged(Color color)
{
    _oInkEditor.StrokeColor = color;
}

마지막으로 새 작품을 만들 때, UCInk의 Stroke가 팔레트에 현재 색상을
가져다 쓸 수 있도록 CreateInkEditor 함수를 손봐줍니다.

void CreateInkEditor()
{
    // 새 InkPresenter Editor 생성
    _oInkEditor = new UCInk(_szInk, true);
    _oInkEditor.BackImage = GetImageRandom();

    if (_oPalette != null)
        _oInkEditor.StrokeColor = _oPalette.Color;

    // Canvas에 추가
    cvEditor.Children.Add(_oInkEditor);
}

여기서 _oPalette를 사용하기 때문에 CreateInkEditor보다 CreatePalette를 먼저 호출해 주셔야 한다고
그랬던 겁니다.

자, 끝났습니다.


별거 아닌데 말이 너무 길었습니다. ^^;;;
감사합니다.


설정

트랙백

댓글

  • taeyo 2007.11.08 16:12 신고 ADDR 수정/삭제 답글

    강좌가 상당히 깔끔하네요^^
    인상적입니다

  • BlogIcon 길버트 2007.11.08 23:07 신고 ADDR 수정/삭제 답글

    감사합니다. 혹시 taeyo.net의 taeyo님 이신가요?
    영광입니다!