프로그래밍을 하다보면 쓰레드를 사용할 일이 있습니다. 이상한 것은 쓰레드를 써야한다고 생각하면 꼭 MSDN을 다시 보게 된다는 것입니다. 막상 코딩을 하려 하면 막 헷갈려요. 저만 그런가요? 

한빛 미디어 사이트에 C# 쓰레드에 관한 좋은 글이 있어서 소개합니다. 저자는 한동훈님이고 아마 C# 에센스라는 책에 있는 내용이 아닌가 생각됩니다. 작성일이 2001년 11월 인데도 아직도 내용은 살아 있습니다. 초보 개발자나 저처럼 머리가 굳어가서 기억이 가물거리는 개발자들에게 도움이 될 것 같습니다. 


WRITTEN BY
ONESTONE

트랙백  1 , 댓글  0개가 달렸습니다.
secret

본 강좌의 원본은 훈스닷넷에 있습니다.

연재 리스트 

  1. Windows 7 멀티터치 시작하기
  2. 미리 정의된 9가지 제스쳐 지원하기 (WM_GESTURE)
  3. 멀티터치 Raw 데이터를 사용 (WM_TOUCH)
  4. Manipulation and Inertia 활용
  5. WPF4 멀티터치 프로그래밍
  6. 실버라이트 4의 멀티터치 프로그래밍

Windows 7이 실버라이트와 결합되면 웹에서도 멀티터치가 가능하도록 만들 수 있다. 물론 Windows 7 운영체제에 종속적이지만 브라우저를 통해서 웹 응용프로그램에서 멀티터치를 경험할 수 있는 길이 열렸습니다. 아래 그림을 살펴보면 멀티터치 입력이 어떤 과정을 통해서 실버라이트 응용프로그램까지 전달 되는지 알 수 있습니다. 윈도우 7의 멀티터치 데이터는 웹 브라우저에 전달이 되고 웹 브라우저는 그 데이터를 다시 실버라이트로 전달하는 구조입니다. 그러므로 웹 브라우저는 인터넷 익스플로러 외에 구글 크롬 등 다른 브라우저에서도 정상적으로 작동 됩니다.


출처 : MSDN

이전 강좌에서는 멀티터치를 윈도우 응용프로그램에 적용하기 위한 여러가지 방법을 소개했습니다. 하지만 실버라이트에서는 한가지 만 지원하는데 바로 Raw Data를 프로그래머에게 던져주는 것이죠. 결국 멀티터치 Raw Data를 가지고 개발자가 여러가지 제스쳐를 만들어야 합니다. 또한 UIElement 개체에 핸들러를 등록하여 처리하는 방식이 아닌 실버라이트 전체 응용프로그램에서 멀티터치 이벤트 핸들러를 등록한다는 것이 마우스와 같은 다른 입력방식과 다른 점입니다.

실버라이트3부터 Touch 라는 클래스가 지원 됩니다. 이 클래스는 FrameReported 라는 이벤트를 제공하는데 이 이벤트가 멀티터치를 처리합니다. 아래 코드와 같이 이벤트 핸들러를 등록하고 Action 값에 따라서 멀티터치의 Down, Move, Up을 처리 할 수 있습니다.

Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);

void Touch_FrameReported(object sender, TouchFrameEventArgs e)

{

foreach (TouchPoint tp in e.GetTouchPoints(_canvas))

{

if (tp.Action == TouchAction.Down) { }

else if (tp.Action == TouchAction.Up) {}

else if (tp.Action == TouchAction.Move) {}

}

}


실버라이트 멀티터치 소스코드 다운로드 : SilverlightMultitouchApplication.zip (Visual Studio 2008, Silverlight 3)


실버라이트의 멀티터치 입력은 기본적으로 '마우스 이벤트 승격'이 적용되는데 이것은 멀티터치 입력의 기본 동작이 마우스클릭으로 사용된다는 의미 입니다. 즉 버튼을 올려놓고 Click 이벤트 핸들러를 등록한 후 멀티터치로 그 버튼을 누르면 Click 이벤트 핸들러가 호출 됩니다. 

"마우스 이벤트 승격"이 특정 시나리오에서는 오히려 방해가 될 수도 있는데 그걸 막을 수 있는 방법을 제공합니다. 바로 TouchFrameEventArgs.SuspendMousePromotionUntilTouchUp() 메서드를 사용하면 됩니다. FrameReported 이벤트 핸들러에서 터치가 Down일때 호출해 주면 Up이 될 때까지 해당 터치에 대해서 "마우스 이벤트 승격"을 하지 않습니다.

이 테스트는 위의 소스코드에서 TouchFrameEventArgs.SuspendMousePromotionUntilTouchUp(); 호출부분을 찾아서 지우고 실행했을 때의 버튼 반응을 비교해 보면 알 수 있습니다. 아래 영상에서도 그 내용을 보실 수 있습니다.



이렇게 실버라이트는 멀티터치 Raw Data를 활용해야 합니다. 결국 프로그래밍을 통해서 제스쳐를 인식하거나 물리적인 시뮬레이션을 구현해야 하는 것이죠. 하지만 잘 찾아보면 참고할 자료들이 있습니다.

http://multitouch.codeplex.com/ 에서는 실버라이트에서 사용할 수있는 Manipulations and Inertia 를 구현한 샘플을 다운로드 받을 수 있습니다. Behavior 로 구현되어 있으니 쉽게 사용할 수있습니다. 라이브 데모를 제공하니 확인을 해보시고 소스코드도 살펴보세요.

WRITTEN BY
ONESTONE

트랙백  0 , 댓글  0개가 달렸습니다.
secret

본 강좌의 원본은 훈스닷넷에 있습니다.

연재 리스트 

  1. Windows 7 멀티터치 시작하기
  2. 미리 정의된 9가지 제스쳐 지원하기 (WM_GESTURE)
  3. 멀티터치 Raw 데이터를 사용 (WM_TOUCH)
  4. Manipulation and Inertia 활용
  5. WPF4 멀티터치 프로그래밍
  6. 실버라이트 4의 멀티터치 프로그래밍


WPF 4가 나오면서 Windows 7의 멀티터치를 적극적으로 지원을 하고 있습니다. 멀티터치를 구현하는 가장 좋은 방법이 바로 WPF 4를 사용하는 것이 아닐까 합니다.

WPF 컨트롤의 상속 구조에서 상위 클래스인 UIElement, UIElement3D, ContentElement 가 멀티터치를 지원 합니다. 즉 WPF에서 사용하는 모든 컨트롤이 멀티터치를 지원 하는 것이죠. 두가지 방식으로 지원을 하는데 첫번째는 멀티터치 원시 데이터(Raw Data)를 지원하고 두번 째는 Manipulation and Inertia를 지원 한다는 것입니다. 그래서 컨트롤의 이벤트를 살펴보면 이런 이벤트가 보입니다.

TouchDown, TouchUp, TouchMove, TouchEnter, TouchLeave


멀티터치 원시 데이터 이용
여러분이 예상하시는 그대로 터치를 하면 TouchDown, 움직이면 TouchMove, 터치를 제거하면 TouchUp 이벤트가 발생합니다. TouchEnter와 TouchLeave가 있는데 터치가 올라가고 내려가는데 반응하는게 아니라 터치를 한 상태에서 터치 영역의 안쪽에서 바깥쪽으로 터치 나가면 TouchLeave 바깥쪽에서 안쪽으로 들어오면 TouchEnter 이벤트가 발생합니다.

마우스와 조금 다른데 생각해보면 터치는 커서가 없기 때문에 공중에 떠서 버튼위에 올라가도 감지할 방법이 없기 때문이겠죠. (이런것들이 모두 터치 인터페이스의 특징입니다.)

그럼 Canvas 하나를 올리고 XAML 에서 위의 이벤트 핸들러를 등록하고 터치가 발생한 지점에 위치와 ID 정보를 표시해 봅시다. XAML 코드에 이렇게 사용되었습니다. 물론 코드 비하인드에서도 이벤트 핸들러를 등록할 수 있습니다.

<Canvas x:Name="_canvas" Canvas.Top="300" Canvas.Left="300"

                TouchDown="Canvas_TouchDown" TouchMove="Canvas_TouchMove" TouchUp="Canvas_TouchUp"

                TouchEnter="_canvas_TouchEnter" TouchLeave="_canvas_TouchLeave"

                Width="500" Height="500" Background="#FF0033FF">


이전 WM_TOUCH, WM_GESTURE 로 프로그래밍 할때는 터치 이벤트가 전체 프로그램에 하나 들어 오는데 WPF 4 에서는 컨트롤 마다 따로 멀티터치 이벤트를 처리 할 수 있습니다.

이벤트 핸들러를 살펴보면 핸들러마다 매개변수로 TouchEventArgs 를 사용합니다. 이 매개변수를 열어보면 TouchDevice, TouchPoint 등 멀티터치 관련된 원시 데이터를 얻을 수 있습니다. 이 데이터를 이용해서 하고 싶은 작업을 하면 되죠.

코드 비하인드에 추가적인 코드를 넣고 실행을 하면 아래 영상처럼 작동을 합니다. WM_TOUCH 샘플과 방식은 약간 다르지만 코드는 매우 비슷하네요.


영상이 보이지 않으면 : http://tvpot.daum.net/my/ClipView.do?ownerid=ADowB8WbINw0&clipid=25744494&q=

WPF 4 멀티터치 Raw data 소스코드 다운로드 : Wpf4MultitouchVisualization.zip (Visual Studio 2010, WPF 4)


WPF 4 멀티터치 Manipulation
WPF 4는 Manipulation 도 내장을 하고 있고 (이전 강좌의 Manipulation은 COM으로 구현된 라이브러리를 Interop 해서 쓰는 방식) 역시 컨트롤 마다 따로 프로그래밍을 할 수 있습니다. 매번 하는 샘플이지만 사진을 가지고 노는 샘플을 만들어 봅시다.

Image 컨트롤에 IsManipulationEnabled 라는 속성이 있고 Manipulation과 Inertia 관련 이벤트들이 존재하는 걸 볼 수 있습니다.

ManipulationStarting, ManipulationStarted, ManipulationDelta, ManipulationBoundaryFeedback, ManipulationCompleted, ManipulationInertiaStarting

이런 이벤트들이 놀랍게도 Image 컨트롤에 추가가 되었습니다. Manipulation의 기본 개념은 이전 강좌를 통해서 살펴봤으니 여기서는 코드만 보고 가겠습니다.

Image 컨트롤의 XAML 코드 입니다.

<Image Source="C:\Users\Public\Pictures\Sample Pictures\Chrysanthemum.jpg" Width="100" Height="80"

               IsManipulationEnabled="True"

               ManipulationStarting="Image_ManipulationStarting" ManipulationDelta="Image_ManipulationDelta" ManipulationStarted=""

               ManipulationInertiaStarting="Image_ManipulationInertiaStarting"

               >

            <Image.RenderTransform>

                <MatrixTransform  />

            </Image.RenderTransform>

        </Image>


IsManipulationEnabled="True" 와 함께 이벤트 핸들러를 등록했고 변환을 편하게 적용하기 위해 MatrixTransform을 사용한 다는 걸 알 수 있습니다.

private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)

{

    e.ManipulationContainer = RootCanvas;

 

     e.Mode = ManipulationModes.All;

     //e.Mode = ManipulationModes.Rotate | ManipulationModes.Scale;

}


우선 Manipulation이 시작되는 시점(ManipulationStarting)에 적용될 범위를 정하고 사용할 조작의 종류들을 지정할 수 있습니다.

private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)

{

  FrameworkElement element = e.Source as FrameworkElement;

 

if (element != null)

{

Matrix matrix = ((MatrixTransform)element.RenderTransform).Matrix;

ManipulationDelta deltaManipulation = e.DeltaManipulation;

 

Point center = new Point(element.ActualWidth / 2, element.ActualHeight / 2);

center = matrix.Transform(center);

 

matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, center.X, center.Y);

matrix.RotateAt(e.DeltaManipulation.Rotation, center.X, center.Y);

matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y);

 

((MatrixTransform)element.RenderTransform).Matrix = matrix;

 

 

e.Handled = true;

 

}


이벤트를 발생시킨 소스를 찾아서 변화된 값이 모두 들어 있는 DeltaManipulation 속성에서 값을 찾아 MatrixTransform을 적용 했습니다.

WPF4 멀티터치 Inertia
관성은 ManipulationInertiaStarting 이벤트 핸들러에서 초기 값을 잘 정해주기 만 하면 알아서 잘 적용이 됩니다. 아래 코드를 보세요. 제가 물리/수학을 잘 못해서 가속도 값 계산 다른 샘플에서 가져다 썼습니다.

private void Image_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)

{

     // (10 inches * 96 DIPS per inch / 1000ms^2)

     e.TranslationBehavior = new InertiaTranslationBehavior()

     {

         InitialVelocity = e.InitialVelocities.LinearVelocity,

         DesiredDeceleration = 1.0 * 96.0 / (1000.0 * 1000.0)

     };

 

     // Decrease the velocity of the Rectangle's resizing by

     // 0.1 inches per second every second.

     // (0.1 inches * 96 DIPS per inch / (1000ms^2)

     e.ExpansionBehavior = new InertiaExpansionBehavior()

     {

         InitialVelocity = e.InitialVelocities.ExpansionVelocity,

         DesiredDeceleration = 0.1 * 96 / 1000.0 * 1000.0

     };

 

     // Decrease the velocity of the Rectangle's rotation rate by

     // 2 rotations per second every second.

     // (2 * 360 degrees / (1000ms^2)

     e.RotationBehavior = new InertiaRotationBehavior()

     {

         InitialVelocity = e.InitialVelocities.AngularVelocity,

         DesiredDeceleration = 720 / (1000.0 * 1000.0)

     };

     e.Handled = true;    

 }


결국 TranslationBehavior , ExpansionBehavior , RotationBehavior 속성을 설정해 주는데 초기 속도와 원하는 가속도를 정해주고 있습니다.

WPF 4 멀티터치 Manipulation  샘플 : Wpf4ImageManipulation.zip (Visual Studio 2010, WPF 4)



실제 실행 결과는 아래 영상을 보세요.


영상이 보이지 않으면 : http://tvpot.daum.net/my/ClipView.do?ownerid=ADowB8WbINw0&clipid=25745220&q=


WPF 4 에서 멀티터치와 잘 통합되어 프로그래밍이 많이 편해진걸 느낄 수 있습니다. 하지만 아직도 멀티터치만이 할 수 있는 인터페이스는 연구해야  할 과제죠.


WRITTEN BY
ONESTONE

트랙백  0 , 댓글  1개가 달렸습니다.
  1. En attendant l'annonce d'un second épisode, http://www.moncleroutletespain.com/ moncler españa, Electronic Arts nous propose, http://www.moncleroutletespain.com/ moncler?? de redecouvrir la version iOS de Mirror's Edge en la proposant gratuitement pendant quelques heures. Cela concerne aussi bien la version iPhone/iPod (100 Mo) que la version iPad (123 Mo), http://www.moncleroutletespain.com/ moncler online. News Robert Downey Jr, http://www.moncleroutletespain.com/ moncler chaquetas. et Gwyneth Paltrow : amitié, http://www.moncleroutletespain.com/ http://www.moncleroutletespain.com/, drogue et alcool News De nouvelles images pour L, http://www.moncleroutletespain.com/ moncler outlet.A. NoireRelated articles:


    http://think1more.kr/222 http://think1more.kr/222

    http://hapina.tistory.com/category/?page=3 http://hapina.tistory.com/category/?page=3
secret


본 강좌의 원본은 훈스닷넷에 있습니다.

연재 리스트 

  1. Windows 7 멀티터치 시작하기
  2. 미리 정의된 9가지 제스쳐 지원하기 (WM_GESTURE)
  3. 멀티터치 Raw 데이터를 사용 (WM_TOUCH)
  4. Manipulation and Inertia 활용
  5. WPF4 멀티터치 프로그래밍
  6. 실버라이트 4의 멀티터치 프로그래밍


조작과 관성이라는 뜻의 Manipulation and Inertia API는 멀티터치 API 중에서 가장 강력한 기능을 가지고 있습니다. 원래는 마이크로소프트 서피스 SDK에 들어 있던 API인데 윈도우 7에 적용이 되었습니다. 사실 이동, 회전, 크기 조절은 모두 수학계산입니다. 관성도 물리 시간에 배우는 몇가지 공식이 적용되는 결국 수학으로 표현됩니다. 간단하지만은 않겠죠. 올바르게 구현하려면 조금  시간이 걸릴 것입니다. 하지만 Manipulation and Inertia API는 내부적으로 이 계산을 알아서 해주고 프로그래머에게는 결과 값만 이벤트로 알려주는 착한일을 합니다. 고맙죠.


Manipulation API
먼저 Manipulation API는 2D 환경에서의 변환 즉, 이동(Translation), 회전(Rotation), 크기조절(Rotation)를 담당합니다. WM_GESTURE 메시지와 다른 것은 모든 변환이 동시에 적용 될 수 있다는 것이다. 이동을 하면서 회전과 크기조절이 동시에 가능하죠. 또한 한 개 또는 두 개의 터치입력에 반응하는 WM_GESTURE와는 달리 여러 개의 멀티터치 입력에도 이동, 회전, 크기변환이 되어 조금 더 자연스러운 느낌을 줄 수 있습니다. .

아래 그림을 살펴보면 Manipulation API가 어떻게 작동되는지 잘 알 수 있습니다. COM으로 구현되어 있는 Manipulation API는 먼저 윈도우 7이 제공하는 WM_TOUCH 메시지의 원시(Raw) 데이터를 입력 받는 Manipulation Processor를 생성합니다. Manipulation Processor는 3가지 이벤트를 발생시키는데 ManipulationStarted 이벤트는 오브젝트를 조작하기 시작할 때 발생하고 ManipulationDelta 이벤트는 이동, 회전, 크기변환이 일어나는 중간에 발생하며 ManipulationCompleted 이벤트는 오브젝트에서 손가락을 떼어 조작을 마무리 하면 발생합니다.

결국 WM_TOUCH Raw data를  활용할 뿐 Application 단에서 모두 처리되는 구조 입니다.



조작이 일어나는 중간에 발생하는 ManipulationDelta 이벤트에서 전달되는 매개변수를 아래 표에서 살펴보세요. 여기서 변화량은 이전 이벤트와 현재 이벤트 사이의 변화량을 말하며 누적량은 ManipulationStarted 이벤트가 발생한 시점부터 현재 이벤트까지 누적된 변화량을 말한다.

종류

속성

설명

변화량

translationDeltaX

X축으로 이동한 변화량

translationDeltaY

Y축으로 이동한 변화량

scaleDelta

크기 변화량(1이 원래크기 1보다 작으면 작아짐)

rotationDelt

회전 변화량 (양수는 시계방향, 음수는 반시계방향)

누적량

cumulativeTranslationX

누적된 X축 이동량

cumulativeTranslationY

누적된 Y축 이동량

cumulativeScale

누적된 크기 변화량

cumulativeRotation

누적된 회전 변화량


즉, 이벤트 핸들러의 매개변수를 통해서 변화된 크기에 대한 정보를 프로그래머에게 알려줍니다. 프로그래머는 그 변화량 또는 누적량을 사용하면 됩니다.

Inertia API
Inertia API를 이용하면 간단한 물리 시뮬레이션을 구현할 수 있습니다. 물리 시뮬레이션은 우리가 실제 환경에서 사물을 이동할 때의 법칙을 멀티터치 응용프로그램에도 적용을 해준다는 것입니다. 예를 들어 책상 위의 물체를 잡고 이리저리 움직이다가 저쪽 끝으로 던지면 그 물체는 관성에 의해서 조금 더 미끄러지며 움직이다가 마찰력에 의해서 멈추게 됩니다. 회전이나 크기 변경도 마찬가지로 관성이 적용될 수 있습니다. 이런 물리적인 움직임을 우리의 멀티터치 응용프로그램에 적용을 하면 매우 자연스러운 느낌을 만들 수 있게 되는데 실제 손가락을 이용하는 터치 인터페이스와 연결되면 NUI(Nature User Interface)라는 말을 실감할 수 있게 만들수 있습니다.

Inertia API는 항상 Manipulation API와 함께 사용하게 된다. 그 이유는 Manipulation API가 끝나는 시점의 속도 등의 값들이 Inertia API의 초기값이 되어 물리적인 시뮬레이션을 시작하기 때문입니다. Inertia Processor가 시작 될 때 입력되는 초기값들의 종류를 아래 표에서 확인 할 수 있습니다. 이 값들을 조작 API의 BeforeInertia 이벤트 핸들어에서 설정을 하게 되면 추가적인 코드 없이도 관성이 적용되어 자연스러운 움직임을 구현할 수 있습니다.

속성

설명

InertiaTimerInterval

관성처리 이벤트가 발생하는 간격

MaxInertiaSteps

발생되는 이벤트의 최대 개수

InitialVelocity

초기 속도 값

DesiredDisplacement

원하는 이동거리

InitialAngularVelocity

초기 회전하는 속도 값

DesiredRotation

원하는 회전 각도 (라디안 값으로 표현)



구현 예
닷넷 환경에서 구현된 코드 조각을 살펴보겠습니다.

using Windows7.Multitouch.Manipulation;

private readonly ManipulationInertiaProcessor processor;

processor = new ManipulationInertiaProcessor

(ProcessorManipulations.ALL, Factory.CreateTimer());

 

processor.ManipulationStarted += processor_ManipulationStarted;

processor.ManipulationDelta += processor_ManipulationDelta;

processor.ManipulationCompleted += processor_ManipulationCompleted;

// WM_TOUCH 데이터(멀티터치 Raw data)를 사용합니다.
_image.StylusDown += new StylusDownEventHandler(Window1_StylusDown);

_image.StylusMove += new StylusEventHandler(Window1_StylusMove);

_image.StylusUp += new StylusEventHandler(Window1_StylusUp);

// WM_TOUCH 데이터를 연결시킵니다.
void Window1_StylusUp(object sender, StylusEventArgs e)

{

   processor.ProcessUp((uint)e.StylusDevice.Id,

e.GetPosition(_canvas).ToDrawingPointF());

}

 

void Window1_StylusMove(object sender, StylusEventArgs e)

{

  processor.ProcessMove((uint)e.StylusDevice.Id,

e.GetPosition(_canvas).ToDrawingPointF());

}

 

void Window1_StylusDown(object sender, StylusDownEventArgs e)

{

   processor.ProcessDown((uint)e.StylusDevice.Id,

e.GetPosition(_canvas).ToDrawingPointF());

}

 

processor.BeforeInertia += processor_BeforeInertia;

void processor_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)

{

_translate.X += e.TranslationDelta.Width;

_translate.Y += e.TranslationDelta.Height;

_rotate.Angle += e.RotationDelta * 180 / Math.PI;

_scale.ScaleX *= e.ScaleDelta;

_scale.ScaleY *= e.ScaleDelta;

}

void processor_BeforeInertia(object sender, BeforeInertiaEventArgs e)

{

processor.InertiaProcessor.InertiaTimerInterval = 15;

processor.InertiaProcessor.MaxInertiaSteps = 500;

//processor.InertiaProcessor.InitialVelocity = … //processor.InertiaProcessor.DesiredDisplacement = …

//processor.InertiaProcessor.InitialAngularVelocity = …

//processor.InertiaProcessor.DesiredRotation = …

//processor.InertiaProcessor.InitialExpansionVelocity = …

//processor.InertiaProcessor.DesiredExpansion = …

}


ManipulationInertiaProcessor라는 클래스를 초기화 하고 ManipulationStarted, ManipulationDelta, ManipulationCompleted 이벤트 핸들러를 등록했습니다. 조작이 끝날때 Inertia를 초기화하면 관성도 적용을 할 수 있습니다.

한가지 더 있습니다. 탄성을 적용할 수 있습니다. 오브젝트를 벽에 던지면 튕겨 나오듯이 모니터의 경계면에 우리가 던지 오브젝트가 튕겨져 나오는 걸 시뮬레이션 할 수 있습니다. 아래처럼 간단한 코드를 넣으면 말이죠.

processor.InertiaProcessor.Boundary = new System.Drawing.RectangleF(1.0f, 1.0f, (float)_canvas.ActualWidth, (float)_canvas.ActualHeight);

processor.InertiaProcessor.ElasticMargin = new System.Drawing.RectangleF(200, 50, 800, 700);


이 코드의 이미는 아래 그림과 같이 관성이 적용될 영역을 지정하고 (Boundary) 관성이 적용될 영역(ElasticMargin)을 지정해 주는 방법 입니다.



Manipulation and Inertia 사용 소스코드 다운로드 : WpfManuplationAndInertia.zip (Visual Studio 2008, .NET Framework 3.5)


실행 결과를 아래 영상으로 확인할 수 있습니다.


WRITTEN BY
ONESTONE

트랙백  0 , 댓글  0개가 달렸습니다.
secret

본 강좌의 원본은 훈스닷넷에 있습니다.

연재 리스트 

  1. Windows 7 멀티터치 시작하기
  2. 미리 정의된 9가지 제스쳐 지원하기 (WM_GESTURE)
  3. 멀티터치 Raw 데이터를 사용 (WM_TOUCH)
  4. Manipulation and Inertia 활용
  5. WPF4 멀티터치 프로그래밍
  6. 실버라이트 4의 멀티터치 프로그래밍

이전 강좌에서 소개된 9개의 미리 정의된 제스쳐는 쉽고 빠른 개발 방법이지만 9개 이외의 다른 제스쳐를 만들어 낼 수는 없습니다. 그래서 새로운 인터페이스를 만들고 싶다면 멀티터치 데이터의 원본(Raw Data)을 이용할 수 있어야 합니다.

마우스를 프로그래밍할 때 생각해보면 마우스의 위치 값, 왼쪽 버튼 클릭, 오른쪽 버튼 클릭 등의 데이터가 있고 MouseDown, MouseMove, MouseUp 이벤트가 있는 것을 기억할 것입니다. 멀티터치도 다르지 않습니다. WP_TOUCH라는 Windows 7 Native 메시지가 비슷한 정보를 우리의 응용프로그램에 전달해 줍니다.

Win32 API에서 WM_TOUCH를 받으려면 먼저 우리가 다루고 있는 윈도우를 WM_TOUCH 메시지 전달이 가능하도록 RegisterTouchWindow() 함수를 이용하여 등록을 하는 과정이 필요합니다. 그러면 터치 입력이 있을 때마다 TOUCHINFO라는 구조체가 매개변수로 전달 되는데 GetTouchInputInfo() 함수를 호출하여 전달된 멀티터치 메시지 데이터를 분석 할 수 있습니다. 하지만 우리는 닷넷 환경에서 작업을 할 것이기 때문에 이런 등록과정은 숨겨져 있습니다.

다만 WP_TOUCH가 전달하는 메세지의 내용을 살펴볼 필요가 있습니다. WM_TOUCH 메시지의 자세한 내용은 MSDN을 참조하세요.

입력 값

설명

ID

여러 개의 터치입력을 구분하는 ID

위치

화면 좌표 (x,y)

Contact의 크기

터치입력의 너비 (cxContact, cyContact)

Primary Contact 인지?

첫 번째 터치입력이면 true

Palm 인지?

손가락 입력이 아니면 true

Time

터치입력 발생 시각



마우스 보다 다양한 내용을 전달하는 걸 볼 수 있고 멀티터치를 구분하기 위해 ID 값이 존재하는 것을 알 수 있습니다. 제가 테스트 한 멀티터치 디바이스에서 ID 값은 처음 터치입력은 ID 10, 두 번째 터치는 11, 세 번째 터치는 12처럼 1씩 증가하면서 부여되고 모든 터치가 제거된 후에 다시 터치 입력을 하면 다시 ID 10부터 시작을 하는 식으로 부여됩니다. 이런식의 ID 변화는 손가락이 현재 몇개가 터치되어 있는지 확인 할 수 있겠죠.

당연히 화면에서의 위치 정보(이것은 마우스의 위치를 정하는 방법과 같습니다)가 있고 터치 입력의 넓이가 있습니다. 또한 첫번째 손가락인지, 손가락이 아니고 손바닥인지를 알 수 도 있습니다.

윈폼(WinForm) 환경에서 터치 데이터 활용
아래 코드처럼 TouchHandler 인스턴스를 생성하고 TouchDown, TouchMove, TouchUp 이벤트 핸들러를 등록합니다. 이벤트 핸들러에서 전달된 매개변수를 열어보면 위의 표에서 설명된 터치 입력에 대한 원시 데이터가 전달된 것을 확인 할 수 있죠. 이 코드는 마우스를 다루는 코드와 같군요. 다만 여러개가 입력될 수도 있다는 걸 제외하면요.

윈폼에서는 Windows7.Multitouch.WinForms.dll 을 사용합니다. 이 어셈블리를 구하는 방법은 이전 강좌에서 확인하세요.

using Windows7.Multitouch;

private TouchHandler _touchHandler;

_touchHandler = Factory.CreateHandler<TouchHandler>(this);

_touchHandler.TouchDown += OnTouchDownHandler;

_touchHandler.TouchMove += OnTouchMoveHandler;

_touchHandler.TouchUp += OnTouchUpHandler;

 

private void OnTouchDownHandler(object sender,TouchEventArgs e)

{

//e.Location

//e.Id

//e.AbsoluteTime

//e.ContactSize

//e.IsPrimaryContact

//e.IsTouchPalm

}

윈폼 샘플 소스코드 : TouchDataVisualization.zip (Visual Studio 2008, WinForm, .NET Framework 3.5)


소스코드는 손가락을 올렸을 때 손가락의 정보를 보여줍니다. 여기에서는 아이디가 10, 100 이런식으로 부여되네요. 아이디 부여 방식은 디바이스 드라이버 마다 다른지 확인을 해봐야겠습니다.


실행 결과를 영상으로 보세요.



동영상이 안보이면 http://tvpot.daum.net/my/ClipView.do?ownerid=ADowB8WbINw0&clipid=25737296&q=

한가지 기억할 것은 WP_TOUCH를 이용하면 WP_GESTURE를 이용할 수 없다는 것입니다. 즉, 터치의 포인트에 대한 정보를 받으면서 동시에 9가지 제스쳐를 다룰 수 없다는 것이죠.

WPF에서 멀티터치 원시 데이터를 이용하는  방법 (.NET Framework 3.5)
WPF에서도 같은 방법으로 프로그래밍을 합니다. WPF에서는 Windows7.Multitouch.WPF.dll을 참조해서 해야죠. 내부적인 방식이 약간 다른데 스타일러스 펜의 입력을 약간 우회하는 방법을 사용하나 봅니다. 소스를 따라 들어가다 보니 아래와 같은 코드가 있던데요. 이것은 아마도 WPF4가 나오기전에 멀티터치 프로그래밍을 위해서 스타일러스 입력을 활용할 수 있도록 열여주는 코드 같습니다.

HwndSource hs = (HwndSource)PresentationSource.FromVisual(this);
SetProp(new HandleRef(this, hs.Handle),     

"MicrosoftTabletPenServiceProperty", new HandleRef(null,    

new IntPtr(0x01000000)));


어째든 우리는 윈폼의 코드와 같은 방식으로 프로그래밍이 가능합니다.

Loaded += (s, e) => { Factory.EnableStylusEvents(this); };

 

StylusDown += OnTouchDownHandler;

StylusMove += OnTouchMoveHandler;

StylusUp += OnTouchUpHandler;


WPF 소스코드 다운로드 : TouchDataVisualization.zip (Visual Studio 2008, .NET 3.5)


실행한 결과는 아래 영상에서 확인 할 수 있습니다. 윈폼의 실행결과와 같습니다.



동영상이 보이지 않으면 : http://tvpot.daum.net/my/ClipView.do?ownerid=ADowB8WbINw0&clipid=25737794&q=

우리는 멀티터치의 원시 데이터(Raw Data)를 얻었습니다. 이 데이터를 가지고 프로그래밍을 하여 다양한 제스쳐를 만들거나 손가락을 그림을 그리는 등의 여러 아이디어 구현에 활용 할 수 있습니다.

이 강좌의 소스는 .NET Framework 3.5 를 사용했지만 WPF 4가 나오면서 조금더 세련되고 쉬운 방법으로 멀티터치를 프로그래밍을 할 수 있습니다. 다음 강좌에서 그 방법을 확인해 보세요.





WRITTEN BY
ONESTONE

트랙백  0 , 댓글  1개가 달렸습니다.
  1. keugbang 2015.09.22 18:59
    마이크로소프트의 링크가 이젠 사라지고 없군요. 하지만 첨부하신 예제 소스파일 덕분에 많은 도움이 되었습니다.
secret

본 강좌의 원본은 훈스닷넷에 있습니다.

연재 리스트 

  1. Windows 7 멀티터치 시작하기
  2. 미리 정의된 9가지 제스쳐 지원하기 (WM_GESTURE)
  3. 멀티터치 Raw 데이터를 사용 (WM_TOUCH)
  4. Manipulation and Inertia 활용
  5. WPF4 멀티터치 프로그래밍
  6. 실버라이트 4의 멀티터치 프로그래밍


관련 어셈블리(DLL) 다운로드 하기
시작하기전에 관련 DLL을 다운로드 해서 사용해야 합니다. 윈도우 7의 멀티터치 지원 API들은 모두 Win32 네이티브 API들입니다. Win32 환경에서는 직접 호출하여 사용을 할 수 있지만 .NET 프레임워크 환경에서는 직접 호출하여 사용할 수 없습니다.

따라서 .NET 프레임워크 환경에서 Win32 환경의 멀티터치 API를 호출할 수 있도록 해줘야 하는데 이 라이브러리가 공개되어 있습니다. Windows 7 Multitouch .NET Interop Sample Library 링크를 통해서 소스코드까지 다운로드 받을 수 있으며 비주얼 스튜디오를 이용하여 빌드하면 결과로 나오는 어셈블리 3가지 Windows7.Multitouch.dll, Windows7.Multitouch.WPF.dll, Windows7.Multitouch.WinForms.dll를 .NET 응용프로그램 소스코드에 참조로 등록하여 사용해야 합니다. 준비를 해 주세요.

MICROSOFT PUBLIC LICENSE 를 따르고 있으므로 꼭 다운로드 하셔야 합니다.

9가지 정해져 있는 멀티터치 제스쳐 (WM_GESTURE 메세지)

WM_GESTURE 메시지를 이용한 멀티터치 개발은 가장 쉽고 단순하게 윈도우 7에서 멀티터치를 구현하는 방법으로 정해진 9가지의 제스처를 이용하는 방법입니다. 먼저 9가지의 제스처를 아래 그림으로 살펴봅시다.


(출처 : MSDN, http://msdn.microsoft.com/ko-kr/library/dd940543(v=VS.85).aspx)

그림에서 보면 9가지 제스처와 그에 상응하는 마우스와 키보드 사용법을 매치 시켜놨습니다. 모든 제스처가 하나 또는 두 개의 터치입력으로 이루어진 제스처들인데 이는 두 개의 터치입력만 가능한 디바이스에서도 문제가 없도록 디자인을 했나봅니다.

어찌되었건 Windows 7은 멀티터치 디바이스에서 전달한 데이터를 가지고 저런 제스쳐를 만들어 프로그래머에게 던져 줍니다. 프로그래머는 그 이벤트를 받아서 마치 마우스나 키보드 컨트롤 하듯이 프로그래밍을 하면 쉽게 우리의 응용프로그램에 멀티터치 기능을 추가할 수 있습니다. 프로그래밍을 해봅시다.

다시 말하지만 Windows 7의 멀티터치 지원은 Windows의 Native API이므로 우리는 닷넷에서 사용가능하도록 랩핑된 어셈블리를 사용하겠습니다. 그리고 WPF 환경에서 샘플을 만들 예정이라서 주로 Windows7.Multitouch.WPF.dll 에 있는 내용을 사용하겠습니다.

소스코드 다운로드 하기 : Win7MultitouchCapabilities.zip (Visual Studion 2008, WPF)



멀티터치를 지원하는지 확인하기
먼저 해야 할 작업은 무엇보다 응용프로그램이 실행된 환경이 멀티터치가 가능한 환경인지 확인을 하는 하는 것입니다. 이 작업은 Win32 네이티브 API 중에서 GetSystemMetrics() 함수의 호출을 통해서 확인 할 수 합니다. 반환되는 값을 분석하면 추가적으로 몇 개의 멀티터치 포인트를 지원하는지, 터치 디바이스가 내장인지 외장인지에 대해서도 알 수 있다. .NET 환경에서는 아래 코드로 확인 할 수 있다. 우리는 이런 검사를 통해서 멀티터치가 가능한 상태에서의 반응과 가능하지 않은 상태에서의 반응을 다르게 프로그래밍 할 수도 있을 것입니다. 여기에서는 프로그램을 종료시켜 버렸는데 사실 좋은 프로그래밍이 아니죠.

using Windows7.Multitouch;

 

if (!GestureHandler.DigitizerCapabilities.IsMultiTouchReady)

{

MessageBox.Show("Multitouch is not availible");

Environment.Exit(1);

}

else

{

int numberoftouches =GestureHandler.DigitizerCapabilities.MaxumumTouches;

MessageBox.Show(numberoftouches + "개의 터치입력 가능");

}


저는 멀티터치 디바이스가 없는 PC에서 작업을 하고 있어서 Multitouch Vista라는 오픈소소의 도움을 받고 있는데요. 이 상태에서 실행시켜보니 아래처럼 255개의 포인트를 지원하네요. 멀티마우스를 손가락으로 시뮬레이션하기 때문에 최대값을 저렇게 지정을 했나봅니다. 실제로 마우스 255개를 붙이면 될까요? ^^



9개의 제스쳐 활용하기
Windows 7의 Native API가 전달해주는 구조체는 MSDN에서 확인 할 수 있는데 조금 복잡해 보일 수 도 있습니다. 하지만 닷넷, WPF에서는 우리가 기존에 마우스 이벤트를 다뤘던 경험만 있으면 어려워 보이는 9가지 제스쳐를 쉽게 구현할 수 있습니다. 먼저 코드를 살펴봅시다.

using Windows7.Multitouch;

GestureHandler _gestureHandler = Factory.CreateGestureHandler(this);

 

_gestureHandler.Pan += ProcessPan;

_gestureHandler.Rotate += ProcessRotate;

_gestureHandler.PressAndTap += ProcessPressAndTap;

_gestureHandler.TwoFingerTap += ProcessTwoFingerTap;

_gestureHandler.Zoom += ProcessZoom;

private void ProcessPan(object sender, GestureEventArgs args)

{

trans.X += args.PanTranslation.Width;

trans.Y += args.PanTranslation.Height;

}

private void ProcessRotate(object sender, GestureEventArgs args)

{

rotate.Angle -= args.RotateAngle * 180 / Math.PI;

}

private void ProcessPressAndTap(object sender, GestureEventArgs args)

{

rectangle.Fill = new SolidColorBrush(Colors.Blue);

}

 

private void ProcessTwoFingerTap(object sender, GestureEventArgs args)

{

rectangle.Fill = new SolidColorBrush(Colors.Red);

}

private void ProcessZoom(object sender, GestureEventArgs args)

{

scale.ScaleX *= args.ZoomFactor;

scale.ScaleY *= args.ZoomFactor;

}


원하는 제스쳐 선택하고 이벤트 핸들러를 등록한 다음 이벤트 핸들러에서 전달되는 매개변수 값을 이용하여 프로그래밍을 하면 됩니다. 참 쉽죠 잉~~. 아래 코드를 다운로드 하여 실행 시켜 봅시다. 

소스코드 다운로드 : WpfTouchGesture.zip (Visual Studion 2008, WPF)


실행한 영상을 아래 첨부 합니다. 마이크가 없어서 목소리 없이 그냥 ... 중간에 손가락을 빨간색 점으로 표시하고 박스도 빨간색이니 포인트가 안보여서 다시 소스를 보는 장면이 있는데 소스를 보니 Two Finger Tap이 박스의 색을 파란색으로 바꿔줍니다.



영상이 안보이면 http://tvpot.daum.net/my/ClipView.do?ownerid=ADowB8WbINw0&clipid=25736362

9개의 제스쳐를 활용한 프로그래밍은 단순해서 좋지만 한가지 단점이 있습니다. 한번에 하나의 제스쳐만 가능하기 때문에 사각형 박스를 돌리면서 동시에 이동을 할 수가 없습니다. 그래서 조금 부자연스럽다고 느끼게 됩니다.

하지만 응용프로그램의 설계에 따라서 필요한 제스쳐를 선택하고 적절히 사용한다면 문제가 없을 것입니다.


WRITTEN BY
ONESTONE

트랙백  0 , 댓글  0개가 달렸습니다.
secret
이 글의 원본은 훈스닷넷에 있습니다.


Windows 7이 출시된지 9개월이 지난 시점에서 총 판매량이 1억 7천5백만 카피라는 통계도 들려오고 시장 점유율이 13.7%를 넘었다는 뉴스기사가 올라옵니다. 실제로 사용자들의 반응이 좋다는 것은 모두들 느끼고 있을 것 같습니다. 그리고 Visual Studio 2010의 출시와 함께 .NET Framework 4가 출시되었습니다. 보다 향상된 개발 환경에서 응용프로그램들을 만들 수 있는 환경이 되었습니다.

Windows 7의 출시와 함께 주목 받았던 멀티터치 관련 지원이 요즘에는 조금 잠잠해진 것 같습니다. 많은 이유가 있겠지만 무엇보다도 멀티터치를 지원하는 장치가 없다는 것이죠. 모니터를 생산하는 많은 제조업체들이 싼 가격에 PC 사용자를 위한 멀티터치 모니터와 노트북을 출시해야 관련 프로그램들이 많아 질텐데 현실은 그렇지가 않네요. 애써서 멀티터치 지원을 해도 적용을 할 수 있는 장치가 없으니 소프트웨어 개발 업체나 개인들이 만들이유가 없습니다.

하지만 여전히 키오스크를 만들거나 어떤 특정한 공공장소에 설치되는 터치를 지원하는 스크린을 만든다면 Windows 7이 좋은 선택이라고 생각합니다. 그래서 이번 강좌에서는 아주 끈질기게 Windows 7의 멀티터치 프로그래밍에 대해서 물고 늘어져 보겠습니다. Windows 7 멀티터치 프로그래밍의 끝은 어디인지 가보죠. (너무 거창한가요?)

Windows 7 멀티터치 지원의 의미
멀티터치(Multi-touch)입력은 2개 이상의 터치 입력이 가능한 입력 시스템을 말하죠. 사실 멀티터치는 완전히 새롭고 신기한 기능은 아닙니다. 벌써 아이폰이 전세계를 강타하면서 멀티터치는 더 이상 생소한 기술이 아닙니다. 몇몇 유명한 아이폰 게임은 두 개의 터치 입력이기 때문에 가능한 새로운 인터페이스와 새로운 아이디어가 얼마나 많이 나올 수 있는지 증명을 해주고 있습니다. 예를 들어 두 개의 터치로 얼굴에 여드름을 짜는 게임, 두 손가락으로 스케이트보드를 컨트롤하고 장애물을 넘는 게임 등 재미있는 인터페이스가 많습니다. 

 

국내 출시는 안되었지만 영상으로 접해볼 수 있는 마이크로소프트 서피스(Microsoft Surface)는 50개 정도의 터치 입력이 가능한 디바이스죠. 우리는 이미 멀티터치를 여러 경로를 통해서 접해 왔다.


하지만 Windows 7의 멀티터치 지원은 PC라는 환경에서 멀티터치가 가능하다는 의미에서 새롭습니다. 마우스와 키보드를 이용해서 PC를 사용하지만 이제는 손가락으로 모니터를 만지듯이 PC를 다루면서 업무를 하는 시대가 올 수도 있다는 얘기입니다.



Windows 7의 멀티터치는 운영체제가 멀티터치를 지원한다는 의미입니다. 다시 말해 하드웨어 업체들은 멀티터치 디바이스를 만들고 윈도우 7용 디바이스 드라이버를 제공하면 그 임무를 다 한 것이고 응용프로그램 개발자는 윈도우 7이 제공하는 멀티터치 관련 API를 응용프로그램 개발에 활용하면 된다는 얘기죠. 운영체제의 역할이 바로 그런 것 아니겠습니까?

Windows Touch를 언제 쓰면 좋을까?
MSDN에서 추천하는 Windows Touch를 사용할 만한 시나리오는 이렇습니다.

  • 기존의 윈도우 응용프로그램의 기능을 멀티터치를 이용해서 사용 가능하도록 할때
  • 오브젝트를 회전하고, 옮기고, 이동하고, 확대/축소하는 등의 기능이 필요할 때
  • Windows Touch Geusture를 지원하고 싶을 때
  • 스타일러스 펜을 사용하는 응용 프로그램에 터치 입력을 추가할 때

MSDN에서는 이 정도의 시나리오를 제시하는데 제 개인적인 의견으로는 아직 터치가 지원되는 노트북과 모니터가 많이 없으므로 멀티터치에 특화된, 즉 멀티터치만 입력으로 사용하거나 멀티터치에 최적화된 응용프로그램의 제작에 적합할 것을 보입니다. 예를들어 매장 앞에 설치할 멀티터치를 지원하는 키오스크 같은 디바이스나 프로그램 말입니다. 

손 안대고 코풀기
Windows 7 OS를 사용하고 멀티터치 디바이스가 설치되어 있다면 (Multi-touch Enabled) 개발자가 특별히 신경쓰지 않아도 작동하는 몇가지 멀티터치 기능이 있습니다. 기본 제스쳐가 지원 됩니다. single finger panning, two fingers panning, two fingers zoom, fick ...

스크롤바가 생기면 Panning이 된다.
우리가 만들어놓은 기존 응용프로그램에 스크롤 바가 생긴다면 멀티터치 가능한 PC에서는 한손가락으로 panning, 두 손가락으로 panning 을 지원 합니다.

Textbox, RichTextbox 등은 멀티터치로 텍스트 선택이 되고 특히 RichTextbox는 두손가락으로 확대/축소가 가능합니다.



영상에서 사용된 멀티터치 시뮬레이션은 Multitouch Vista라는 오픈소스 프로젝트의 도움을 받았습니다. 설정방법은 여기에 있습니다.

그 외 더 찾아보면 여러가지가 있을 것 같아요.
이 정도 지원으로도 만족하는 개발자들도 있겠지만 멀티터치 API를 사용하여 좀 더 다양한 제스쳐와 기능을 제공할 수 있습니다. 앞으로 연재되는 강좌를 통해서 하나하나 알아보도록 하죠.

연재 리스트 

  1. Windows 7 멀티터치 시작하기
  2. 미리 정의된 9가지 제스쳐 지원하기 (WM_GESTURE)
  3. 멀티터치 Raw 데이터를 사용 (WM_TOUCH)
  4. Manipulation and Inertia 활용
  5. WPF4 멀티터치 프로그래밍
  6. 실버라이트 4의 멀티터치 프로그래밍

 


WRITTEN BY
ONESTONE

트랙백  0 , 댓글  0개가 달렸습니다.
secret

이 글의 원본은 훈스닷넷에 있고 지난 강좌는 여기에 있습니다.

두번째 강좌의 소스코드 다운로드


Ribbon 탭과 그룹 적용
Ribbon 인터페이스는 탭 컨트롤이 적용되어 있습니다. 명령과 기능들을 잘 정의하고 정리해서 탭을 만들고 탭에서 그룹으로 잘 묶어줘야 좋은 UI가 나오겠죠.
“홈”과 “공유” 두 가지 RibbonTab을 만들겠습니다.

저는 메인 기능을 홈 탭으로 묶으려 하고 공유와 관련된 기능을 공유라는 탭으로 묶으려고 하고 있습니다. 아무래도 홈 탭에는 “도구, 모양, 색” 이렇게 3가지 그룹을 넣고 공유 탭에는 “SNS”라는 탭을 만들어서 묶어보겠습니다. 이런 구조가 나오겠네요.

도구

지우개, 연필 (서로 토글)

모양

연필의 브러시 모양 지정

연필에 적용될 색

공유

SNS

트위터, 페이스북

Command들이 많아지네요. 버튼마다 기능이 있고 그 기능들은 명령으로 구현할 수 있습니다.

우선 그룹에 쓰일 명령을 만들어봤습니다

<r:RibbonCommand x:Key="ToolsGroupCommand" LabelTitle="도구" LargeImageSource="Images\Transfer.png" SmallImageSource="Images\Transfer.png" />

        <r:RibbonCommand x:Key="BrushGroupCommand" LabelTitle="모양" LargeImageSource="Images\brush.png" SmallImageSource="Images\brush.png" />

        <r:RibbonCommand x:Key="ColorGroupCommand" LabelTitle="" LargeImageSource="Images\color.png" SmallImageSource="Images\color.png" />

        <r:RibbonCommand x:Key="SNSGroupCommand" LabelTitle="SNS" LargeImageSource="Images\twitter.png" SmallImageSource="Images\twitter.png" />


이 Command를  <r:RibbonGroup Command="{StaticResource ToolsGroupCommand}"> 형식으로 RibbonGroup에 붙여 줍니다.

각 그룹에는 해당하는 버튼들을 위치 시킬 수 있는데요. 그 버튼들 마다 하나의 Command를 만들어서 적용해 보겠습니다. Command가 많다 보니까 만드는게 힘드네요.
(Visual Studio 2010의 XAML을 그대로 Word에 붙여넣기 하면 왜 아래처럼 한글이 깨지는지 모르겠어요.)

<r:RibbonCommand x:Key="EraserCommand"

                         LabelTitle="지우개"

                         LabelDescription="o¯iÆ©ø¬| ù¡¾AI¯¨Ï ù¡¾¡í o¯iùo OöA¥I¥U."

                         SmallImageSource="Images\eraser.png"

                         LargeImageSource="Images\eraser.png"

                         ToolTipTitle="o¯iÆ©ø"

                         ToolTipDescription="o¯iÆ©ø¬| ù¡¾AI¯¨Ï ù¡¾¡í o¯iùo OöA¥I¥U."

                         CanExecute="EraserCommand_CanExecute"

                         Executed="EraserCommand_Executed"/>

 

        <r:RibbonCommand x:Key="BrushCommand"

                         LabelTitle="ù¡¾¾¡¿¬¢ç¾a"

                         LabelDescription="ù¡¾¾¡¿¬¢ç¾a¬| ù¡¾AI¯¨Ï ù¡¾¡í ¾¡¿¬¡¾ùo OöA¥I¥U."

                         SmallImageSource="Images\brush.png"

                         LargeImageSource="Images\brush.png"

                         ToolTipTitle="ù¡¾¾¡¿¬¢ç¾a"

                         ToolTipDescription="ù¡¾¾¡¿¬¢ç¾a¬| ù¡¾AI¯¨Ï ù¡¾¡í ¾¡¿¬¡¾ùo OöA¥I¥U."

                         CanExecute="BrushCommand_CanExecute"

                         Executed="BrushCommand_Executed"/>

 

<!-- 나머지는 소스 코드 참조-->


이 Command를 아래 코드처럼 RibbonButton에 적용할수 있습니다.

<r:RibbonTab Label="">

<r:RibbonGroup Command="{StaticResource ToolsGroupCommand}">

<r:RibbonButton Command="{StaticResource BrushCommand}" />

<r:RibbonButton Command="{StaticResource EraserCommand}" />

</r:RibbonGroup>

<r:RibbonGroup Command="{StaticResource BrushGroupCommand}">

<r:RibbonButton Command="{StaticResource CircleShapeCommand}" />

<r:RibbonButton Command="{StaticResource StarShapeCommand}" />

</r:RibbonGroup>

<r:RibbonGroup Command="{StaticResource ColorGroupCommand}">

<r:RibbonButton Command="{StaticResource RedColorCommand}" />

<r:RibbonButton Command="{StaticResource BlueColorCommand}" />

<r:RibbonButton Command="{StaticResource GreenColorCommand}" />

</r:RibbonGroup>

</r:RibbonTab>

 

<r:RibbonTab Label="공유">

<r:RibbonGroup  Command="{StaticResource SNSGroupCommand}">

<r:RibbonButton Command="{StaticResource TwitterCommand}" />

<r:RibbonButton Command="{StaticResource FacebookCommand}" />

</r:RibbonGroup>

</r:RibbonTab>


실행결과는 이렇습니다.
(아이콘들은 구글 이미지 검색에서 가져왔습니다.)

버튼들이 원하는 대로 그룹으로 묶여 있습니다. 탭도 정상적으로 작동을 하고 있죠.

Ribbon은 RibbonButton말고 다양한 컨트롤을 제공하고 있습니다.

• RibbonButton
• RibbonCheckBox
• RibbonToggleButton
• RibbonDropDownButton
• RibbonSplitButton
• RibbonComboBox
• RibbonTextBox
• RibbonLabel
• RibbonSeparator

RibbonDropDownButton을 써볼까요? 색 그룹을 아래 코드처럼 변경해보세요.,

<r:RibbonGroup Command="{StaticResource ColorGroupCommand}">

<r:RibbonDropDownButton Command="{StaticResource RedColorCommand}">

<MenuItem Header="Red" />

<MenuItem Header="Green" />

<MenuItem Header="Blue" />

</r:RibbonDropDownButton>

</r:RibbonGroup>



이런식으로 체크박스, 토글버튼, 텍스트 박스 등을 사용할 수 있을 것 같습니다.

한가지 더 재미있는 것이 남았습니다. 오피스를 실행하고 창 크기를 변경해보면 Ribbon의 그룹과 그 그룹에 속한 컨트롤들이 점차 줄어들어 작아진 윈도우에 최적화 되는걸 볼 수 있습니다.



이것을 구현하는 속성이 RibbonGroup의 GroupSizeDefinitions 속성입니다.
리소스 부분에 RibbonGroupSizeDefinitionCollection 을 아래와 같이 추가합니다. 이 정의는 창이 줄어들 때 어떤 방식으로 정리를 할 것인지에 대한 내용입니다.

<r:RibbonGroupSizeDefinitionCollection x:Key="RibbonLayout">

            <r:RibbonGroupSizeDefinition>

                <!-- Control sizes: L,L,L -->

                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True"/>

                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True"/>

                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True"/>

            </r:RibbonGroupSizeDefinition>

            <r:RibbonGroupSizeDefinition>

                <!-- Control sizes: L,M,M -->

                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True"/>

                <r:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="True"/>

                <r:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="True"/>

            </r:RibbonGroupSizeDefinition>

            <r:RibbonGroupSizeDefinition>

                <!-- Control sizes: L,S,S -->

                <r:RibbonControlSizeDefinition ImageSize="Large" IsLabelVisible="True"/>

                <r:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="False"/>

                <r:RibbonControlSizeDefinition ImageSize="Small" IsLabelVisible="False"/>

            </r:RibbonGroupSizeDefinition>

            <!-- Collapsed -->

            <r:RibbonGroupSizeDefinition IsCollapsed="True" />

        </r:RibbonGroupSizeDefinitionCollection>


이 정의를 적용합니다.

<r:RibbonTab Label="¡§">

                    <r:RibbonGroup Command="{StaticResource ToolsGroupCommand}" GroupSizeDefinitions="{StaticResource RibbonLayout}">

                        <r:RibbonButton Command="{StaticResource BrushCommand}" />

                        <r:RibbonButton Command="{StaticResource EraserCommand}" />

                    </r:RibbonGroup>

                    <r:RibbonGroup Command="{StaticResource BrushGroupCommand}" GroupSizeDefinitions="{StaticResource RibbonLayout}">

                        <r:RibbonButton Command="{StaticResource CircleShapeCommand}" />

                        <r:RibbonButton Command="{StaticResource StarShapeCommand}" />

                    </r:RibbonGroup>

                    <r:RibbonGroup Command="{StaticResource ColorGroupCommand}" GroupSizeDefinitions="{StaticResource RibbonLayout}">

                        <r:RibbonButton Command="{StaticResource RedColorCommand}" />

                        <r:RibbonButton Command="{StaticResource BlueColorCommand}" />

                        <r:RibbonButton Command="{StaticResource GreenColorCommand}" />

</r:RibbonGroup>

                </r:RibbonTab>

 


실행을 한 후에 창의 크기를 줄여나가면 아래 그림과 같이 그룹들이 정렬이 되고 버튼들이 재배치 되는걸 볼 수 있습니다.

 

 

그런데 창이 어느 한계에 가면 리본이 사라지는 현상이 있는데 왜일까요? CTP 버전의 버그 인 줄 알았는데 오피스도 그러네요. ‘창이 너무 작아지면 UI가 의미가 없으니 사라진다라고 생각하면 좋을 것 같습니다.

 

지금까지 Ribbon의 기본 사용법에 대해서 2회에 걸쳐서 강좌를 올렸습니다. WPF에서 XAML과 함께 사용하는 것은 이처럼 어렵지 않습니다. 남은 것은 탭과 그룹을 잘 설계해서 사용자들에게 실질적인 도움을 주는 것이겠죠.

 

감사합니다.

 


WRITTEN BY
ONESTONE

트랙백  0 , 댓글  0개가 달렸습니다.
secret


이 글의 원본은 훈스닷넷에 있습니다.

강좌에 사용한 소스코드 다운로드


(RibbonControlsLibrary.dll 은 아래 글을 읽고 다운로드 받아서 사용하세요. 라이센스가 있어 그렇습니다.)

Ribbon 개요
Ribbon 사용자 인터페이스는 Microsoft Office 2007부터 적용이 되어 사용되고 있습니다. Windows 7에서는 그림판에도 적용이 되었죠. 툴바와 메뉴에 익숙하던 윈도우 사용자들이 처음 Ribbon 인터페이스를 접하고 조금 당황해 하기도 했지만 사용하면 할수록 쉽고 편리하다는 걸 느낄 수 있습니다. 그 편리함이 단지 익숙해져서 그렇기도 하겠지만 UX에 대한 깊은 연구가 바탕이 되었습니다. Office UI 팀이 Ribbon을 만들고 적용하기까지의 과정을 살펴보려면 MIX09의 The Story of the Ribbon (http://videos.visitmix.com/MIX08/UX09) 세션을 한번 보세요. UX에 관심이 많으신 분들 보시면 좋을 것 같습니다.

WPF에서는 Ribbon 인터페이스를 사용할 수 있습니다. 아직은 CTP 버전이지만 Ribbon의 기본기능들은 모두 구현이 되어 있습니다. 이 강좌에서 Ribbon의 기초적인 사용법을 같이 살펴보겠습니다.

사실 Ribbon을 쉽게 보면 Office와 비슷한 UI를 만드는 것이지만 Office 만큼 사용하기 쉽게 만들기 위해서는 많은 고민이 필요합니다. 그런 고민에 도움을 줄 수 있는 많은 글들이 Office 사이트에 있습니다. Ribbon UI를 심각하게 사용하실 분은 꼭 참조를 해야겠지만 이 글에서는 우선 Ribbon을 사용하는 방법에 집중을 해보겠습니다. 

Ribbon 컨트롤 다운로드 (RibbonControlsLibrary.dll)
RibbonControlsLibrary.dll의 다운로드는 Office UI Licensing Site에서 다운로드 받을 수 있습니다. 아래의 절차를 따라서 해보세요.

  1. Office UI Licensing Site 에 접속하고 License the Office UI 링크를 눌러 들어갑니다. 
  2. Live ID로 로그인을 합니다. 
  3. 몇 가지 정보를 입력하고 라이선스 사용에 동의를 한 후 Finish 버튼을 눌러 전송합니다. 
  4. Microsoft Office Fluent UI Design Guidelines File Download 페이지에서 “WPF Ribbon Control: Binary & Link to Sample Application”을 눌러 다운로드 받습니다. 디자인 가이드라인도 제공을 하고 있습니다. 
  5. 압축된 파일을 풀면 RibbonControlsLibrary.dll을 확인 할 수 있습니다.


Ribbon 컨트롤 올리기
우선 리본 컨트롤을 윈도우에 올려봅시다.

1. 먼저 Visual Studio 2010을 열고 적당한 이름으로 WPF 프로젝트를 생성합니다. (물론 Visual Studio 2008에서도 사용 가능합니다.) 저는 Visual Studio 2010에서 RibbonInkCanvas라는 이름으로 프로젝트를 생성 했습니다.

2. 다운로드 받은 RibbonControlsLibrary.dll을 프로젝트에 참조로 추가합니다.

3. MainWidnows.xaml을 열어서 네임스페이스를 추가합니다. 저는 r 이라는 접두사를 사용했습니다.

xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"

4. 윈도우 상단에 리본을 붙여 봅시다. 리본은 상단에 붙어 있어야 하므로 StackPanel 을 사용하는 것이 좋겠네요. 아래 XAML 코드 처럼 StackPanel을 추가하고 방향을 Vertical 로 설정합니다.

5. 드디어 Ribbon 을 추가합니다.

<Window x:Class="RibbonInkCanvas.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"

        Title="MainWindow" Height="350" Width="525">

    <Grid>

        <StackPanel Orientation="Vertical">

            <r:Ribbon DockPanel.Dock="Top">

              

            </r:Ribbon>

        </StackPanel>

    </Grid>

</Window>


실행한 결과는 아래와 같습니다. 껍데기가 붙어있는 상황이네요.



Ribbon 인터페이스의 구조
리본 인터페이스의 구조를 알아야 적용을 할 수 있습니다. 아래 그림을 보면 그 구조가 보입니다. 그냥 쓸 때는 몰랐는데 만들려고 하니 뭐가 많군요.

<출처 : MSDN>

  • Office 메뉴를 보니까 Application Button은 파일 관련된 메뉴(저장, 새로 만들기 등)를 넣으면 되겠고 프로그램을 종료하는 메뉴가 있으면 되겠네요. 
  • Quick Access Toolbar 도 있었네요. 자주 사용하는 몇 가지가 들어가면 되겠어요. 
  • Tab 이 있네요. 리본은 기본적으로 탭구조를 가지고 있죠. 
  • Tab 중에 상황에 맞게 생기는 Contextual Tab도 있구요. 이게 상당히 유용하던데. 
  • Tab 안에 기능들을 Group으로 묶어주고 그 중에 In-Ribbon Gallery라고 그림으로 한눈에 알아볼 수 있는 명령들이 있죠. 
  • 잘 사용하지 않던 건데 Dialog Box Launcher를 누르니까 리본 이전에 많이 사용하던 대화상자가 뜨네요. 
  • Enhanced Tooltips은 자세한 설명이 들어있는 툴팁을 표시해 줍니다.

이 강좌에서 위의 모든 것을 구현하지는 못하지만 기본은 해보죠.

Application Menu 버튼과 Enhanced Tooltips, 그리고 탭, Quick Launch Toolbar을 만들어서 그 안에 명령들을 유사한 그룹으로 묶어서 메뉴를 만들어 보겠습니다.

그럴싸하게 만들려면 아이콘들이 좀 필요한데 Ribon Sample에서 조금 가져다 쓰도록 하죠. 소스를 다운로드 받아서 Image 폴더의 아이콘을 우리가 작업하는 프로젝트에 넣습니다.


Application Menu 추가
아래 그림은 윈도우7의 그림판의 Application Menu입니다.

메뉴들을 살펴보니 어떤 것들이 들어가면 좋을지 감이오네요. 여기에서는 새로 만들기/저장/닫기 등의 메뉴를 만들어 봅시다.

먼저 RibbonCommand를 이용해서 필요한 커멘드를 만들고 인터페이스에 적용을 하는 식으로 진행을 해봅시다. WPF에서는 초창기부터 Command 패턴을 적용한 구조를 가지고 있습니다. 사용자가 하려는 행동을 Command로 만들고 그것을 XAML을 통해서 인터페이스에 붙이고 실행되어야 하는 코드는 코드 비하인드에서 처리하는 방식인데요. 먼저 Command Pattern을 이해하시고 MSDN의 명령 개요를 읽어보시면 이해를 하실 수 있습니다.

Command를 사용하면 코드의 구현과 UI를 분리할 수 있고 Command를 재사용 할 수 있습니다. 예를 들어 ‘저장’이라는 버튼은 리본 인터페이스에서 Application Menu, Quick Access Toolbar, 어떤 Tab에 삽입된 버튼 등 여러 곳에서 사용 가능합니다. 이럴 때 Command 를 만들어서 여러 UI에 붙이고 실행되는 코드는 한 곳으로 만들 수 있습니다.

리본에서도 RibbonCommand라는 클래스를 제공합니다. 리본 커멘드 3가지를 만들어서 Windows 리소스에 추가를 해봅시다.

RibbonCommand의 주요 속성은 아래와 같습니다.

LabelTitle

 

LabelDescription

 

ToolTipTitle

 

ToolTipDescription

 

SmallImageSource

 

LargeImageSource

 

RibbonCommand의 주요 이벤트는 아래 표와 같습니다.

CanExcute

메뉴는 상황에 따라서 비활성화/활성화 될 필요가 있습니다. 이 이벤트의 핸들러에서 CanExecuteRoutedEventArgsCanExecute 속성을 False로 해주면 해당 메뉴가 비활성화 됩니다.

Excuted

명령이 실행될 때 이벤트 발생. 이벤트 핸들러에서 해당 명령에 해당하는 코드를 실행해 주면 됩니다.

아래 XAML 코드처럼 3가지 RibbonCommand를 만들고 Application Menu를 추가해 봅시다.

<Window x:Class="RibbonInkCanvas.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"

        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>

        <r:RibbonCommand x:Key="CloseCommand"

                         LabelTitle="닫기"

                         LabelDescription=" 프로그램을 종료합니다."

                         ToolTipTitle="프로그램 닫기"

                         ToolTipDescription=" 프로그램을 종료합니다. 종료하기 전에 변경된 데이터를 저장하시기 바랍니다."

                         SmallImageSource="Images\delete.png"

                         LargeImageSource="Images\delete.png"

                         CanExecute="CloseCommand_CanExecute" 

                         Executed="CloseCommand_Executed"/>

        <r:RibbonCommand x:Key="SaveCommand"

                         LabelTitle="저장"

                         LabelDescription=" 변경된 내용을 저장합니다."

                         ToolTipTitle=" 변경된 내용 저장"

                         ToolTipDescription=" 변경된 내용을 저장합니다."

                         SmallImageSource="Images\save.png"

                         LargeImageSource="Images\save.png" 

                         CanExecute="SaveCommand_CanExecute"

                         Executed="SaveCommand_Executed"/>

        <r:RibbonCommand x:Key="NewCommand"

                         LabelTitle="새로만들기 "

                         LabelDescription="새로운 그리기를 시작합니다."

                         ToolTipTitle="새로만들기"

                         ToolTipDescription="새로운 그리기를 시작합니다."

                         SmallImageSource="Images\files.png"

                         LargeImageSource="Images\files.png"

                         CanExecute="NewCommand_CanExecute"

                         Executed="NewCommand_Executed"/>

        <r:RibbonCommand x:Key="ApplicatioMenuCommand"

                         LabelTitle="Application Button"

                         LabelDescription="Application Button."

                         SmallImageSource="images/Pencil.png"

                         LargeImageSource="images/Pencil.png"

                         ToolTipTitle="Ribbon Ink Canvas"

                         ToolTipDescription="Click here to open Ribbon IncCanvas Menu." />

 

    </Window.Resources>

    <Grid>

        <StackPanel Orientation="Vertical">

            <r:Ribbon DockPanel.Dock="Top">

                <r:Ribbon.ApplicationMenu>

                    <r:RibbonApplicationMenu Command="{StaticResource ApplicatioMenuCommand}" >

                        <r:RibbonApplicationMenuItem Command="{StaticResource NewCommand}" />

                        <r:RibbonApplicationMenuItem Command="{StaticResource SaveCommand}" />

                        <r:RibbonSeparator />

                        <r:RibbonApplicationMenuItem Command="{StaticResource CloseCommand}" />

                    </r:RibbonApplicationMenu>

                </r:Ribbon.ApplicationMenu>

            </r:Ribbon>

        </StackPanel>

    </Grid>

</Window>

 


코드 비하인드(MainWindow.xaml.cs)에도 오류가 나지 않도록 아래와 같이 RibbonCommand의 이벤트 핸들러를 넣어줍니다.

private void CloseCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)

{

e.CanExecute = true;

}

private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)

{

   e.CanExecute = true;

}

private void NewCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)

{

   e.CanExecute = true;

 }

private void CloseCommand_Executed(object sender, ExecutedRoutedEventArgs e)

{

}

private void SaveCommand_Executed(object sender, ExecutedRoutedEventArgs e)

{

}

private void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)

{

}

코드를 실행하면 Application Menu가 생성된 것을 볼 수 있습니다. RibbonSeparator를 사용해서 메뉴에 가로선을 넣을 수도 있네요.



Quick Launch Toolbar 만들기.
아래 XAML 코드를 추가하면 Quick Launch Toolbar에 아이콘을 추가 할 수 있습니다. 

...
<
r:Ribbon.QuickAccessToolBar>

<r:RibbonQuickAccessToolBar>

<r:RibbonButton Command="{StaticResource NewCommand}"                                               

r:RibbonQuickAccessToolBar.Placement="InCustomizeMenu"/>

<r:RibbonButton Command="{StaticResource SaveCommand}"                                                

r:RibbonQuickAccessToolBar.Placement="InCustomizeMenuAndToolBar"/>

</r:RibbonQuickAccessToolBar>

</r:Ribbon.QuickAccessToolBar>

 ...



r:RibbonQuickAccessToolBar.Placement 속성에는 3가지 값(InCustomizeMenuAndToolBar, InCustomizeMenu, InToolBar)이 들어갈 수 있는데 값을 변경하면서 실행을 해보시면 용도를 알 수 있습니다. InCustomizeMenuAndToolBar로 지정하면 버튼도 보이고 버튼을 커스터마이징 할 수 있는 메뉴에도 들어갑니다. 

실행을 해보면 Quick Lanch Toolbar가 추가된 것을 확인할 수 있습니다. 새로 만들기 버튼은 "InCustomizeMenu"에만 추가되어 있는 걸 확인 할 수 있습니다.



Application Menu와 Quick Launch Menu까지 추가를 했습니다.

 (2편에서 계속)


WRITTEN BY
ONESTONE

트랙백  0 , 댓글  0개가 달렸습니다.
secret

개발자를 위한 Windows 7 세미나 시리즈 그 마지막 "멀티터치를 활용한 응용 프로그램 개발 가이드" ...
계속되는 외근으로 인해 여유가 없어서 발표자료를 이제야 올려 드립니다.

발표자료
세션 1 윈도우 7 멀티터치 개발의 세계로
세션1 PPT 발표자료 다운로드

세션 1 개발환경 : Windows 7, Visual Studio 2008, Windows 7 SDK, .NET Framework 3.5 SP1

WM_GESTURE 메시지
Win7MultitouchCapabilities 샘플 : 터치가 가능한 API인지 확인하는 방법에 대한 샘플
WpfTouchGesture : WPF 환경에서 WM_GESTURE 메시지를 이용한 멀티터치 샘플

WM_TOUCH 메시지
TouchDataVisualization : WinForm 환경에서 WM_TOUCH 메시지를 이용한 멀티터치 샘플
WPFTouchDataVisualization : WPF 환경에서 WM_TOUCH 메시지를 이용한 멀티터치 샘플

Manuplation and Inertia
WpfManuplationAndInertia : WPF 환경에서 Manuplation and Inertia API를 활용한 멀티터치 샘플

세션2 멀티터치 WPF 4를 주목하라
세션2 PPT 발표자료 다운로드

세션2 개발환경 : Windows 7, Visual Studio 2010 beta 1, .NET Framework 3 beta 1

WPF4MultiTouchSample : .NET 4에서 Image 컨트롤의 멀티터치 활용 샘플
WPF4ScatterViewSample : .NET 4에서 제공하는 멀티터치 전용 컨트롤 ScatterView 샘플

많은 분들이 함께 해 주셔서 감사했습니다.
모두들 Windows 7의 세로운 가능성을 알아보고 미리 준비를 하시는 분들이겠죠. 마이크로소프트에서는 그저 Windows Native API에 멀티터치 지원을 추가했지만 새로운 경험, 새로운 인터페이스, 새로운 인터렉션을 만들어 내는 것은 결국 개발자, 기획자, 디자이너의 협업이라고 생각합니다. 화이팅 ~


WRITTEN BY
ONESTONE

트랙백  1 , 댓글  0개가 달렸습니다.
secret