본문 바로가기

Silverlight

ContentControl?? ContentPresenter

바로 리스트 박스로 들어가자고 하니 초반부터 낙오자가 많을 것같다는 생각이 들더군요. 그래서 아주 기본 컨트롤부터 시작하기로 했습니다. 

  ContentControl은 아주 유용한 Control이면서 아주 기본적인 Control입니다. 우리가 알고 있는 대부분의 Control이 ContentControl을 상속받고 있죠. 일단 모든 Button류가 상속하고 있고 ListBoxItem등도 상속하고 있는 클래스입니다. 

   그럼 이것이 도데체 어떤 컨트롤인지 알아보기 위해 한번 화면에 뿌려보도록 하겠습니다. 

다음처럼 간단한 코드를 Xaml에 추가해보죠..

<ContentControl Content="This is a ContentControl!!"/>

화면에 단순히 "This is a ContentControl!!" 이라고 뿌려지는 것을 볼 수 있을 겁니다. 

뭐야... 그냥 TextBlock 인거야?... 하지만 다음과 같은 코드를 뿌려보면 단번에 뭐하는 녀석인지 알 수 있습니다. 

       <ContentControl>
            <ContentControl.Content>
                <Ellipse Width="50" Height="50" Fill="Purple"/>
            </ContentControl.Content>
        </ContentControl>

결과는 단순히 보라색 동그라미가 화면에 뿌려지는 것일 겁니다. 

아하 그러면 결국 이놈은 그냥 Content를 표시해주는 역활을 해주는 것이군요...

사실 좀더 세부적으로 어떻게 구현되는 가를 살펴보기 위해 ContentControl의 Template을 살펴보도록 하죠. 

<ControlTemplate TargetType="ContentControl" >
    <ContentPresenter Content="{TemplateBinding Content}"  ContentTemplate="{TemplateBinding ContentTemplate}" Cursor="{TemplateBinding Cursor}" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</ControlTemplate>


여러가지 속성들이 TemplateBinding 되어있는데 이런 속성들은 그냥 ContentControl의 속성을 ContentPresenter에 셋팅시켜주는 것일 뿐이고 결국은 내부적으로 ContentPresenter만 달랑 들어있는 형상이네요. 

  그럼 이 ContentPresenter 란 놈은 무슨 일을 하는지 알아봐야 겠죠. 아쉽게도 ContentPresenter는 Template도 없고 더 뜯어볼 코드도 없습니다. 

  예전 beta1때 공개되었던 코드를 참고하자면 다음과 같습니다. (현재의 CotentPresenter와 다른 점이 있을 수 있습니다. 일단 Text관련 Property들이 대부분 사라졌습니다. 현재는 ContentPresenter에 Content와 ContentTemplate 두가지 속성만이 있습니다. )


  코드를 살펴보면 다음과 같은 DefaultTemplate을 가지고 있는 것을 알 수 있습니다. 

private const string ContentPresenterDefaultTemplate =
            "<ControlTemplate " +
              "xmlns=\"
http://schemas.microsoft.com/client/2007\" " + 
              "xmlns:x=\"
http://schemas.microsoft.com/winfx/2006/xaml\" " + 
              "xmlns:controls=\"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls\" " +
              "TargetType=\"controls:ContentPresenter\">" + 
                "<Grid x:Name=\"RootElement\" " +
                  "Background=\"{TemplateBinding Background}\" " +
                  "Cursor=\"{TemplateBinding Cursor}\">" + 
                    "<TextBlock x:Name=\"TextElement\" " +
                      "FontFamily=\"{TemplateBinding FontFamily}\" " +
                      "FontSize=\"{TemplateBinding FontSize}\" " + 
                      "FontStretch=\"{TemplateBinding FontStretch}\" " + 
                      "FontStyle=\"{TemplateBinding FontStyle}\" " +
                      "FontWeight=\"{TemplateBinding FontWeight}\" " + 
                      "Foreground=\"{TemplateBinding Foreground}\" " +
                      "HorizontalAlignment=\"{TemplateBinding HorizontalContentAlignment}\" " +
                      "Padding=\"{TemplateBinding Padding}\" " + 
                      "TextAlignment=\"{TemplateBinding TextAlignment}\" " +
                      "TextDecorations=\"{TemplateBinding TextDecorations}\" " +
                      "TextWrapping=\"{TemplateBinding TextWrapping}\" " + 
                      "VerticalAlignment=\"{TemplateBinding VerticalContentAlignment}\" " + 
                      "Visibility=\"Collapsed\" />" +
                "</Grid>" + 
            "</ControlTemplate>";

  간단히 Grid 안에 TextBlock 하나 있는 그런 Template인 거죠. 

  내부적인 구현을 보면 Content와 ContentTemplate Property Change 시 마다 자신의 DataContext에 Content나 ContentTemplate을 엮어준 후 PrepareContentPresenter라는 메소드를 호출해줍니다. 여기서 PrepareContentPresenter 라는 메소드에서는 다음과 같은 작업을 수행합니다. 
  1. 먼저 Default Template을 통해 들어있던 객체를 Grid로부터 제거 합니다. 
  2. 그리고 새로 받은 Template이 있으면 Template을 Content에 UIElement가 있으면 Content를 엘리먼트에 집어넣어줍니다. 
  3. 새로 셋팅된 Template도 없고 Content가 UIElement도 아닌 경우 Default Template에 있던 TextBlock의 Text값에  Content를 ToString 해서 넣어줍니다.



         아래의 글은 클라인스님이 제기해주신 의문사항인데요. 이 내용이 맞다고 하네요.
         원글은 http://cafe.naver.com/mssilverlight/3703서 확인하실 수 있습니다.

1번에 말씀하셨던 Grid로부터 제거된다고 말씀하셨는데요..

테스트를 해보니 Grid자체(하위 엘리먼트 포함)가 제거되고

<Grid>

    <Ellipse~~

</Grid>

 

부분이 추가되는거 아닌가요?? 해당 부분의 <Grid></Grid>를 Canvas로 바꾼 후 컴파일하여 Spy로 찍어보면

처럼 보입니다.

 

ContentPresenter->Grid->Canvas->Ellipse, TextBlock처럼 나오지 않구요~

 

또한 새로움 템플릿이 아닌 컨텐츠 할당시에도(아래처럼)

Grid로부터 제거가 되는 것이 아닌 Grid자체가 제거되고

ContentPresenter에 해당 Content(UIElement)가 포함되는 것 같습니다.

 

위 소스를 컴파일 후 Spy로 찍어 보면

 

처럼 Grid가 사라지고 ContentPresenter에 Ellipse가 바로 붙어있는 것을 볼 수 있습니다.

ContentPresenter->Grid->Ellipse가 아니구요~^^a





 

결국 ContentPresenter는 단순한 PlaceHolder 역활을 해준다는 것입니다. 여기서 한가지 알아보지 않은 것이 ContentTemplate 인데 ContentPresenter에 새로운 Template을 넣어준다고 생각하면 쉬울 것같습니다. 다음과 같은 코드를 실행해 보죠.

        <ContentControl Content="This is a ContentControl!!!">
            <ContentControl.ContentTemplate>
                <DataTemplate>
                    <Grid>
                        <Ellipse Width="200" Height="50" Fill="LightSteelBlue"/>
                        <TextBlock Text="{Binding }" VerticalAlignment="Center" HorizontalAlignment="Center" />
                    </Grid>
                </DataTemplate>
            </ContentControl.ContentTemplate>
        </ContentControl>

결과는  
  요렇습니다. 
여기서 Text="{Binding }" 부분은 ContentControl의 DataContext 바로 Content를 Binding 하겠다는 뜻입니다. 
그러니까 "This is a ContentControl!!!" 요 문장을 Text에 바인딩한 것이죠. 

Content 값이 바뀜에 따라 글자가 얼마든지 바뀔 수 있습니다. 그러면 다음과 같은 시도를 해보면 어떨까요?

        <ContentControl x:Name="contentControl">
           <ContentControl.ContentTemplate>
               <DataTemplate>
                    <Grid>
                       <Ellipse Width="200" Height="50" Fill="LightSteelBlue"/>
                       <TextBlock Text="{Binding Tag}" />
                    </Grid>
                </DataTemplate>
            </ContentControl.ContentTemplate>
        </ContentControl>

그리고 비하인드 코드에 다음과 같은 코드를 추가 시킵니다. 

  public partial class Page : UserControl
  {
        public Page()
        {
            InitializeComponent();
            contentControl.Content = new TestData() { Tag = "This is a ContentControl!!!" };
         }
   }

    public class TestData
   {
        public string Tag { get; set; }
   }

결과는 바로위의 예제와 같습니다. 

이번에는 TextBlock의 Text가 ,Content로 엮인 TestData의 Tag Property와 Binding 이 된 것이죠.

그럼 ContentControl에 대해 이해가 잘 되셨는지 모르겠군요.^^ 

마지막으로 한가지 팁을 알려드리자면. ContentControl은 Grid 나 Popup 같이 DataContext를 줄 수 없는 객체에 바인딩을 하는 용도로도 쓸 수 있어요.   Popup 이나 Grid 등을 ContentControl로 감싸고 ContentControl에 DataContext 값을 주면 되죠.. 이런게 어디 쓸모 있을까 싶겠지만 나중에 복잡한 바인딩 모델을 쓰다보면 불가피하게 써야 하는 경우가 생기더군요. 

  그럼 모두 삽질 덜 하시길..~^^

                                                                                                                         - smile -

p.s. 샘플프로젝트를 첨부합니다. 




[출처] 실버라이트 네이버 카페