2008. 12. 22. 12:59

Custom Panel 만들기.

원래 제가 직접강좌를 쓰려고 했으나.. 외국분이 아주 잘 정리해 놓은 포스트가 있어 번역합니다. (영어공부도 할겸..^^;;)

  원본 링크는 다음과 같습니다. 


그럼 이제 번역을 해보죠. 오역이 있더라도 이해해주시고 피드백 주시길 바랍니다. 

----------------------------------------------------------------------------------------------------------

  저는 지난 몇주간 CustomPanel을 만드는 방법을 알아내려 많은 시간을 보냈습니다. 처음에 저는 매우 혼란스러웠습니다.  조그만 변경에도 제 Panel의 전체적인 Layout은 심하게 바뀌었습니다. 그러나 이렇게 한동안 어둠속에서 헤맨 끝에 드디어 빛을 본 것 같은 느낌이 들었고 그래서 제가 Custom Panel 에 대한 조언을 줄 수 있는지 알아보려고 합니다..  
 첫번째로 Measuring 과 Arranging 에 대해 알아야만 합니다. Silverlight의 layout 시스템은 Mesure 단계에서 부터 시작합니다. 여기선 각각의 자식 Element들이 layout 시스템으로 자신의 Desired Size(원하는 사이즈: 실제 프로퍼티에 존재하는 이름임으로 앞으로는 구지 번역하지 않겠습니다. ) 를 보고합니다. 여기선 Panel 자신의 보고도 포함됩니다. 그 다음 단계는 Arrange 단계입니다. 여기선 패널 자신의 최종 사이즈를 참고하여 각각의 자식의 경계 영역(Bounding box)의 크기와 위치를 지정합니다. 

  그래서 Custom Panel을 만들기 위해서는 Panel을 상속하고 MeasureOverride 와 ArrangeOverride 메소드를 구현한 클래스를 만들어야만 합니다. 저는 BlockPanel이라고 이름 붙인 Panel을 만들었습니다. 3x3 블럭 엘리먼트를 만들고 각 블럭이 나란히 놓여서 Panel의 오른쪽 끝에 도달했을 때 Wrapping(줄바꿈) 되도록 하였습니다. 그리고 각각의 엘리먼트의 사이즈가 100x100 이 되도록 제한하였습니다.  각 element가 원하는 사이즈를 계산하고 블럭들을 조정하는 좀 더 복잡한 잡업을 하려고 했지만  SDK 예제로써 코드가 너무 복잡해지고 길어졌습니다.  그래서 여기 제 Panel에서 자식들이 어떻게 정열될것인지보여주는 그림이 있습니다. 
자 그럼 이제 코드를 봅시다. 여기 제 Panel의 MesureOverride 메소드가 있습니다. 

public class BlockPanel : Panel

    {

        //First measure all children and return available size of panel

        protected override Size MeasureOverride(SizeavailableSize)

        {

            //Measure each child

            foreach (FrameworkElement child in Children)

            {

                child.Measure(new Size(100,100));

            }

 

            //return the available size

            return availableSize;

        }

 

 

}


  여기선 Layout System이 이 Panel의 가능한 사이즈(availableSize)를 넘겨줍니다. 만약 Panel을 생성할 때 Width나 Height 를 특별히 설정해주었다면 그 값이 바로 여기서 얻을 수 있는 값이  됩니다. MeasureOverride 함수 안에서는, 현재 Panel에서 가능한 사이즈를(이 경우에는 100x100) 각각의 자식들의 Measure 함수로 넘겨줍니다. 제가 봤던 많은 예제에서는 기본적으로 Child가  Panel의 전체 영역을 다 차지 할 수 있는 영역을 말하는, availableSize 가 다시 되넘겨졌습니다.  Measure 함수가 호출된 후 Layout System은 자식들의 DesiredSize 를 결정하게 됩니다. 저는 이부분에 대해 아직 상세하게 모든 부분을 이해하지는 못했지만 대체로 고유사이즈(native size) 와 가능한 사이즈(available size) 중에 작은 부분을 DesiredSize로 결정하는 것 같습니다. 
  그래서 이 BlockPanel의 경우 만약 자식 Rectangle의 사이즈가 200x200 이라면 Desired Size는 100x100으로 설정될 것입니다. 또 만약 Rectangle의 사이즈가 50x50 일면 DesiredSize는 50x50으로 설정될 것입니다.  
(역자주: 이부분에 대해 어떻게 결정되는지에 대한 정확한 문서가 나와있지 않습니다. 다만 막연히 다음 링크를 보시면 DeisiredSize에 대한 설명은 보실 수 있습니다. 
DesiredSize는 실제사이즈나 RenderSize와 다르게 Layout을 구성하는데 필요한 계산 값으로만 작동하는 것같습니다.)

모든 객체를 측정(measure)한 후에 전체 Panel로 availiableSize를 반환합니다. 저는 들어왔던 availableSize를 그대로 반환함으로써 Measure 단계에서 Panel의 사이즈를 전혀 제한 하지 않았습니다. 

 다음으로 Layout System 은 ArrangeOverride를 살펴보게 됩니다. 여기 제 Panel의 ArrangeOverride 함수가 있습니다.

public class BlockPanel : Panel

    {

        //Second arrange all children and return final size of panel

        protected override Size ArrangeOverride(Size finalSize)

        {

            //Get the collection of children

            UIElementCollection mychildren = Children;

 

            //Get the total number of children

            int total = mychildren.Count;

 

            //Calculate the number of 3x3 blocks needed

            int blocks = (int)Math.Ceiling((double)total/9.00);

 

            //Calculate how many 3x3 blocks fit on a row

            int blocksInRow = (int)Math.Floor(finalSize.Width / 300); //assuming blocks of 9 element 300x300

 

            //Arrange children

            int i;

           

            double maxWidth = 0;

            double maxHeight = 0;

            for (i = 0; i < total; i++)

            {

                //Find out which 3x3 block you are in

                int block = FindBlock(i);

 

                //Get (left, top) origin point for your 3x3 block

                Point blockOrigin = GetOrigin(block, blocksInRow,new Size(300,300));

 

                //Get (left, top) origin point for the element inside its 3x3 block

                int numInBlock = i-9*block;

                Point cellOrigin = GetOrigin(numInBlock, 3, newSize(100,100));

 

                //Arrange child

                //Get desired height and width. This will not be larger than 100x100 as set in MeasureOverride.

                double dw = mychildren[i].DesiredSize.Width;

                double dh = mychildren[i].DesiredSize.Height;

                

                mychildren[i].Arrange(new Rect(blockOrigin.X + cellOrigin.X, blockOrigin.Y + cellOrigin.Y, dw, dh));

 

                //Determine the maximum width and height needed for the panel

                maxWidth = Math.Max(blockOrigin.X + 300, maxWidth);

                maxHeight = Math.Max(blockOrigin.Y + 300, maxHeight);

            }

 

 

            //Return final size of the panel

            return new Size(maxWidth,maxHeight);

        }

}


기본적으로 각 객체에 대해 계산해주고 있는 것들입니다. 
  1. 어떤 3x3 블럭에 들어갈 것인가?
  2. 3x3 블럭안에 어떤 구역 숫자가 들어갈 것인가?
  3. 3x3 블럭의 Left,Top 코너 위치
  4. 3x3 블럭안 구역의 Left,Top 코너 위치.
  5. 각 객체의 DesiredSize.

제가 이 모든 정보를 가지고 있어야지만, 저는 각 자식 객체의 경계 영역(bounding box)의 사이즈와 위치를 계산할 수 있습니다. 경계영역은 Rect 값으로 Arrange 함수로 넘겨지게 됩니다. 

  저는 또한 모든 3x3 블럭을 포함하는데 필요한 width, height 값을 추적하였습니다.(역자주:maxWidth와 maxHeight 를 갱신함) 그 후  모든 블럭을 포함할 수 있을만큼 충분히 큰 패널의 사이즈를 최종 사이즈(final size)로 설정해주었습니다. 저는  또한 finalSize를 넘겨줄 수 있었고 Panel이 자신의 부모 Container로 부터 가능한 전체영역을 채우게 될 것입니다.

 그래서 잠시동안 저를 혼란스럽게 했던 몇가지 점을 정리하면:
  1. 우리가 child.Measure를 호출 했을 때, 우리는 자식들이 가능한 사이즈를 넘겨주는 것입니다. 자식들의 실제 사이즈를 설정해주는 것이 아닙니다. 
  2. Measure 함수를 호출 한 뒤에 Layout System 은 Element의 DesiredSize를 결정할 것입니다. 다시 말해서 제가 말할 수 있는 것은 우리가 직접 DesiredSize를 설정할 수 있는 방법은 없다는 것입니다..
  3. 우리가 child.Arrange 를 호출 했을 때 우리는 child의 최종 사이즈(final size)를 설정하는 것이 아닙니다. 저는 잠시동안 이것이 child의 사이즈를 설정하는 것이라고 착각했지만 이것은 child를 포함할 경계 영역(bounding box)를 설정하는 것입니다. 그래서 만약 child가 경계영역보다 크다면 그것은 Clip 될 것이며 만약 child가 더 작다면 그것은 기본 정렬(역자주: 보통은 Center 정열)이나 당신이 정해준 정렬에 기반하여 경계 영역안에 위치될 것입니다. 이 Panel 경우에는 경계 영역이 child의 DesiredSize가 되도록 설정했습니다.
  
  제가 호출 한 ArrangeOverride 함수를 포함한 나머지 코드와 함께 Panel을 생성하고 몇개의 Rectangle을 안에 넣은 Xaml 화일을 첨부합니다. 이 예제에는 제가 아직 이해하지 못한 Layout System에 대한 상세한 것들이 있습니다. 그래서 만약 아직 읽어 보지 않았다면 "Object Positioning and Layout"을 읽어보길 권합니다. 또한 Xaml 안에 새로 만든 Custom Panel을 생성해주기 위해서는 새로운 namespace를 선언하는 것이 필요할 것입니다. 이것에 관한 글은 "Mapping to Custom Classes and Assemblies" 을 확인 해보길 바랍니다.


-----------------------------------------------------------------------------------------------------------------------------------------------------------------


번역이 끝났습니다.   흠.. 이번 번역은 생각보다 긴 문장이 많아서 골치가 조금 아팠습니다. 그래도 전체적으로 잘 정리되어있어서 예제를 보시면서 따라하시면 이해하시는데 큰 무리는 없을 것으로 보입니다. 

  그럼 제가 만들었던 예제도 같이 첨부해 드리죠. 


                                                                                                                                                                                    - smile -