Silverlight 3 에 새롭게 추가 된 ChildWindow 를 쓰다보면, X 버튼을 없애고 싶을 때가 생깁니다. X 버튼이란 ChildWindow 최우측 상단에 있는 컨트롤로, X버튼을 클릭 시 ChildWindow 가 닫히게 되죠. 이런 경우엔 사용자의 입력 (OK or Cancel)에 대한 확인을 할 수 없게 됩니다.
그래서 X 버튼을 없애는 방법을 구현 해 보겠습니다.
X 버튼을 없애는 방법은 ChildWindow 가 컨트롤이라는 것을 이용하게 되는 것이죠. 컨트롤이기 때문에 ControlTemplate 를 재정의 해서 X 버튼의 Visible을 Collapsed 로 변경하거나, 아예 삭제하시면 됩니다.
(예전에 포스팅 했던 ControlTemplate 정의하기를 보시면 조금이나마 도움이 되실거라 생각합니다. ^^;)
우선, ChildWindow를 Blend 3 Preview에서 엽니다.
그 다음, Objects and Timeline 에서 Objects의 최 상위인 ChildWindow를 선택 하신 후, ControlTemplate 를 수정할 수 있도록 Edit Control Parts (Template)를 선택하여, Template Style을 하나 생성합니다.
이렇게 Template Style을 생성하게 되면, Objects and Timeline 에 ChildWindow 컨트롤의 Template 구조가 나오죠. 여기서 X 버튼 즉, CloseButton 을 찾아서 Visible 속성을 Collapsed로 변경하시거나, 삭제 해 주시면 됩니다.
이렇게 작업하시고 저장하시면, X 버튼이 없는 ChildWindow 가 만들어지게 됩니다. :D
System.Windows.Contols 네임스페이스가 추가 되 있네요. ChildWindow가 System.Windows.Controls 에 있나 봅니다^^;
그리고, 레이아웃도 일정한 폼으로 정해져 있는데요, 이는 Blend 3 Preview 에서 열어보시면 확인 하실 수 있습니다.
비하인드 코드도 살펴보죠.
publicpartialclassTestChildWindow : ChildWindow
{ public TestChildWindow()
{
InitializeComponent();
}
기본적으로 OK, Cancel 버튼의 이벤트가 구현 되 있네요.
여기서 유념해 보실 사항은, OK, Cancel 버튼 각각은 DialogResult Property의 값을 할당 할 뿐 아무 코드도 추가 되 있지 않다는 것입니다. 이렇게 되 있는데도 ChildWindow를 띄워 동작을 해 보시게 되면, OK, Cancel 버튼 클릭 시 ChildWindow 가 닫히게 됩니다. 이는 ChildWindow 의 DialogResult Property에 값을 할당하면 Closing -> Closed 순으로 ChildWindow 가 닫힌다는 것을 반증하는 것이죠.
그럼, 말씀 드렸던 ChildWindow를 어떻게 띄우는 지 살펴 보겠습니다.
TestChildWindow t = new TestChildWindow();
t.Show();
(너무 쉬운가요? ㅎ)
MessageBox에 비해 ChildWindow가 가지는 장점은, ChildWindow 의 XAML 내용을 보시면 짐작하시겠지만, 바로 개발자의 마음대로 Content 를 구성할 수 있다는 것입니다. 또한, DialogResult 값으로 사용자의 입력(OK, Cancel)을 ChildWindow를 띄운 MainPage에서 확인 할 수 있다는 것이죠.
ChildWindow에 DialogResult Property 가 사용자의 입력을 확인 할 수 있는 Property 입니다.(Nullable Boolean) 한 가지, 유의하실 점은 Property 값을 사용하는 시점을 명확하게 해야 한다는 것인데요, Silverlight 에서는 모든 동작이 비동기로 이뤄지기 때문에 DialogResult Property 의 값을 아무곳에서나 사용하게 되면 값이 없거나, 사용자의 입력과 상관 없는 값을 가지고 있을 수 있겠죠.
그래서, ChildWindow의 Closed 이벤트가 일어나는 시점의 DialogResult 값을 사용하시면 됩니다.
TestChildWindow t = newTestChildWindow();
t.Show();
t.Closed += (s2, e2) =>
{ bool? result = t.DialogResult;
if (result != null)
{ if((bool)result)
{ this.myTextBlock.Text = "Nice to meet you.";
} else { this.myTextBlock.Text = "Who are you?";
}
}
};
위 코드와 같이 구현하게 되면, 정확히 ChildWindow 가 사용자의 입력에 의해 닫히고, 그 때의 DialogResult 값을 확인 할 수 있게 되는 것이죠 :D (ChildWindow 는 여러모로 활용도가 높은 컨트롤임에도 불구하고, 사용하기 참 쉽네요^^)
WinForm에서는 TextBox.ScrollToCaret() 같은 메소드를 이용하여 TextBox에 AppendText("텍스트") 된 내용을 현재 캐럿 위치까지 스크롤이 가능하게 하고, WPF에서는 ScrollViewer.ScrollToTop, ScrollToBottom 등을 이용하여 Scroll의 위치를 컨텐츠의 상,하,좌,우로 이동 할 수 있습니다.
헌데, Silverlight에서는 ScrollToCaret도, ScrollToTop, ScrollToBottom 등도 없기 때문에 이와 같은 기능을 직접 구현 해 줘야 합니다. 그래서, 확장 메서드를 이용해서 ScrollViewer에 직접 ScrollToTop, ScrollToBottom, ScrollToLeftEnd, ScrollToRightEnd 메소드를 구현 해 보겠습니다.
실버라이트에서 ScrollViewer 컨트롤의 메소드를 찾아보시면, ScrollToVerticalOffset, ScrollToHorizontalOffset 이란 메소드가 있습니다. 두 메서드는 모두 double 값을 파라미터 인자로 받는데요, 여기에 double 값을 주게 되면, 각각 Vertical, Horizontal의 위치로 Scroll이 이동하게 됩니다.
그리고, ScrollViewer 컨트롤의 Property를 찾아보시면, ScrollableHeight, ScrollableWidth 가 있습니다. 각각의 Property를 이용하면 스크롤 할 수 있는 컨텐츠 요소의 세로, 가로의 값을 알 수 있습니다.
ScrollToVerticalOffset, ScrollToHorizontalOffset 메소드와 ScrollableHeight, ScrollableWidth Property를 이용해서 다음과 같이 구현하게 되면 Top, Bottom, LeftEnd, RightEnd 로 스크롤을 이동할 수 있습니다.
저는, 같은 프로젝트 내(어셈블리)에서 모두 사용 가능 하도록 확장 메서드를 사용했습니다. 확장 메서드를 사용하게 되면, 이미 존재하는 ScrollViewer 컨트롤에 메서드를 추가 해 주기 때문에, 동일 어셈블리 상에서 또 다른 ScrollViewer를 사용 하게 될 때에도 반복된 코드가 필요 없이 ScrollViewer 자체의 메소드를 쓰는 것처럼 사용하실 수 있습니다.
Orientation = "Vertical" 인 경우, Button 3의 Width를 변경하여 Button 1,2,4의 Horizontal 쪽으로 여백이 생겼고,
Orientation = "Horizontal"인 경우, Button 6의 Height를 변경하여 Button 5,7,8의 Vertical 쪽으로 여백이 생겼다.
이 여백 에 HorizontalAlignment, VerticalAlignment를 변경하여 다음과 같이 배치 할 수 있다.
DockPanel DockPanel 은 Docking 될 위치를 설정하여, 설정 된 위치에 고정시켜 배치 하는 Layout Panel이다.
Docking 될 위치를 지정하는 방법은, 콘텐트 엘리먼트에서 DockPanel의 Attached-Property인 Dock를 사용하면 되고, Dock은 Left, Right, Top, Bottom 네 가지가 있다. (배치 되는 콘텐트 엘리먼트의 Dock 위치를 설정하지 않으면 Default로 Left로 Docking 시킨다.)
DockPanel.LastChildFill property를 True로 하게 되면, 마지막으로 배치 되는 콘텐트 엘리먼트는 지정 된 Dock property와 상관없이 다른 콘텐트 엘리먼트가 고정되고 남은 부분을 꽉 채우게 된다.
Docking 은 먼저 설정 된 콘텐트가 우선한다.
예를 들어, 위 코드에서 처럼 Dock = "Top" 을 먼저 하고 Dock = "Left" 를 그 뒤에 지정했기 때문에, Top과 Left가 겹치는 부분에서는 Top이 그 공간을 차지하게 된다. 반대로, Dock = "Left"를 먼저 지정 한다면, Left가 그 공간을 차지하게 된다.
StackPanel : 콘텐트 엘리먼트들을 Stack처럼 순서대로 차곡차곡 쌓아 배치하는 Layout Panel.
(Stack처럼 배치 되긴 하나, Data structure의 Stack 구조처럼 LIFO(Last In First Out)로 콘텐트 엘리먼트를 삭제하는 것은 아니다. 다른 Layout Panel과 마찬가지로, Children property(UIElementCollection)에 보관하며, RemoveAt으로 임의의 콘텐트를 삭제할 수도 있다.)
StackPanel 에 콘텐트 엘리먼트를 추가하면, 순서대로 배치 되며, 배치 방향은 Default로 Vertical이 된다.
위 예제에서는 myStackPanel 안에 Button 이 순서대로 배치 된 것을 확인할 수 있다.
또, Button의 Width와 Height를 지정하지 않았기 때문에, Vertical일 경우엔 Width가 Stretch되고 Height는 Content의 픽셀만큼, Horizontal 일 경우에는 Width가 Content의 픽셀만큼, Height가 Stretch된다.
콘텐트 엘리먼트의 Width와 Height property를 지정 가능하며,
Vertical인 경우에는 HorizontalAlignment를
Grid : 테이블 형식으로 콘텐트를 구성하여 배치 할 수 있도록 하는 Layout Panel.
테이블 형식의 콘텐트를 구성하기 위한 Grid 설정하기 Grid에는 RowDefinitions와 ColumnDefinitions 두 가지 컬렉션 property가 있다. 두 property는 각각 행과 열을 관리하는 컬렉션이다. 원하는 행,열 만큼 각각의 컬렉션에 RowDefisnition과 ColumnDefinition을 추가하여 설정한다.
예를 들어, 3x3 (행,열)의 테이블 형식으로 Grid 를 구성하려 한다면,
3x3 (행,열)로 구성 된 Grid에 위와 같이 콘텐트 엘리먼트들을 추가 할 수 있다.
Grid.Row와 Grid.Column 값으로 위치 될 행과 열을 지정하고,
Grid.RowSpan 으로 행의 확장을, Grid.ColumnSpan으로 열의 확장을 지정할 수 있다.
위 코드를 실행하면 다음과 같이 콘텐트 엘리먼트들이 배치 된 것을 확인 할 수 있다.
셀 크기 변경
RowDefinition과 ColumnDefinition을 추가 할 때, 각각 행의 높이, 열의 너비를 지정 할 수 있다.
셀 크기 변경에는 고정 값을 지정하는 방법과, * 을 이용하는 방법, Auto 를 이용하여 자종 크기를 지정하는 3가지 방법이 있다.
<Grid.RowDefinitions>
<RowDefinition Height="50"/> 고정 값을 지정하는 방법
<RowDefinition Height="2*"/> *을 이용하는 방법 (50을 제외한 부분의 2/5)
<RowDefinition Height="3*"/> *을 이용하는 방법 (50을 제외한 부분의 3/5)
</Grid.RowDefinitions>
고정 값을 지정 하는 경우 Row의 Height나 Column의 Width가 지정 된 픽셀만큼으로 지정된다.
*을 이용하여 지정하는 경우에는 다른 지정방법에 의해서 사용되고 남은 공간이 얼마나 있는지를 계산 한 뒤, 그 결과에 따라서 결정된다. 위 경우에는 50을 지정하고 남은 공간을 5등분 하여, 두번째 Row는 2/5만큼, 세번째 Row는 3/5만큼의 크기로 결정된다.
Grid의 Height가 200이라면, 첫 번째 Row의 Height는 50, 두 번째 Row의 Height는 60, 세 번째는 90이 된다.
또 한가지, Auto를 사용하게 되면, 해당하는 Row나 Column에 배치되는 콘텐트 엘리먼트의 사이즈(Width나 Height)에 따라서 자동으로 Row나 Column의 사이즈가 지정 된다.
위 코드를 실행 한 결과, 첫 번째 Column은 콘텐츠 엘리먼트의 Width 값인 50만큼, 두 번째는 150, 세 번째는 200만큼의 사이즈가 지정 됐다.
GridSplitter를 사용하여, 동적으로 셀의 경계를 조절
System.Windows.Control.dll 어셈블리에는 실버라이트가 제공하는 내장 컨트롤을 보완하기 위한 여러가지 컨트롤이 있다. GridSplitter도 그 중 하나이며, System.Windows.Control 어셈블리 내 컨트롤을 사용하기 위해서는 우선 참조를 추가하고, XAML 상단에 네임스페이스를 참조하기 위한 접두사를 사용해야 한다.
Layout Panel : 콘텐트를 배치하기 위해 사용되는 엘리먼트
Layout Panel 내 추가 할 콘텐트 엘리먼트들은 UIElement 형식으로 파생된 엘리먼트이어야만 한다.
왜냐하면, Layout Panel들은 System.Windows.Controls.Panel을 상속 받고 있으며, 추가 할 콘텐트 엘리먼트들은 UIElementCollection형의 Children property에 추가 되기 때문이다.
따라서, UIElement형의 모든 콘텐트 엘리먼트들은 Layout Panel 내 추가 할 수 있다.
Canvas
Canvas 는 ElementPoint를 사용하여 엘리먼트들을 원하는 위치에 배치 할 수 있는 기능을 제공하는 Layout Panel이다.
Canvas에 콘텐트 엘리먼트를 추가 하는 방법은 두 가지 방법이 있다. 1. Attached Property를 사용하여 ElementPoint를 지정하여 배치.
위와 같이 Canvas의 Attached Property인 Left, Top 을 사용하여, 추가 될 콘텐트 엘리먼트의 ElementPoint인 (Left, Top) 을 설정하여 배치하면 된다. 또한, 이는 런타임에도 설정할 수 있다.
double left = Canvas.GetLeft(this.myRect); Canvas.SetLeft(this.myRect, left - 15); double top = Canvas.GetTop(this.myRect); Canvas.SetTop(this.myRect, top - 15);
Canvas class에서 GetLeft, GetTop 함수를 제공하며, Canvas 내 배치 된 UIElement를 파라미터로 넘기면, 파라미터로 넘겨 준 UIElement의 Canvas 내 Left 속성과 Top 속성 값을 가져온다.
마찬가지로, Canvas class에서 제공하는 SetLeft, SetTop 함수를 사용하면, Canvas 내 배치 된 UIElement의 Canvas 내 Left속성과 Top 속성 값을 변경 할 수 있다.
위 코드를 실행하면 다음과 같이 마지막 Rectangle UIElement 가 Left , Top 각각 -15만큼 이동 된다.
위와 같이 Canvas 내 배치 된 콘텐트를 겹치도록 해 보면, 나중에 추가 된 콘텐트가 이전에 추가 된 콘텐트보다 상위에 표시 되게 된다. 이것은, Canvas에 추가 된 순서대로 콘텐트가 배치 되기 때문이다.
또 한가지, ZIndex 가 지정 되지 않아서 모두 Default 값인 0으로 설정 되 있기 때문이다.
ZIndex 속성 또한 Canvas의 Attached Property이고, ZIndex 값의 변경으로 콘텐트의 배치를 다른 콘텐트 보다 앞으로 혹은 뒤로 변경 할 수 있다. 물론 XMAL에서도 런타임에서도 모두 가능하다.
int zindex = Canvas.GetZIndex(this.myRect2);
Canvas.SetZIndex(this.myRect2, 1);
Canvas class에서 GetZIndex 함수를 제공하며, Canvas 내 배치 된 UIElement를 파라미터로 넘기면, 파라미터로 넘겨 준 UIElement의 Canvas 내 ZIndex 속성 값을 가져온다.
마찬가지로, Canvas class에서 제공하는 SetZIndex 함수를 사용하면, Canvas 내 배치 된 UIElement의 Canvas 내 ZIndex 값을 변경 할 수 있다.
ZIndex 값이 상대적인 다른 콘텐트의 ZIndex 값 보다 작으면, 뒤로 배치되고, 반대의 경우 앞으로 배치 된다.
위 코드를 실행하면 다음과 같이 myRect UIElement 가 가장 앞으로 배치 되게 된다.
Canvas 의 장점
Canvas는 콘텐트를 ElementPoint로 고정시키기만 할 뿐, 재조정 하지 않기 때문에, 임의의 어떤 콘텐트의 위치가 변경 되어야 할 때마다 다른 콘텐트들의 배치를 위한 계산이 수행되거나 하지 않아, 계산의 횟수를 최소화 하기 때문에 성능상으로 빠르다는 이점이 있다.