Layout

이 항목에서는 WPF(Windows Presentation Foundation) 레이아웃 시스템을 설명합니다. WPF에서 사용자 인터페이스를 만들려면 언제, 어떻게 레이아웃을 계산해야 하는지를 이해해야 합니다.

이 항목에는 다음과 같은 섹션이 포함되어 있습니다.

요소 경계 상자

WPF에서 레이아웃을 고려할 경우에는 모든 요소를 둘러싸고 있는 경계 상자를 이해해야 합니다. 레이아웃 시스템에서 사용하는 각 FrameworkElement를 레이아웃에 배치되는 사각형으로 생각할 수 있습니다. LayoutInformation 클래스는 요소의 레이아웃 할당의 경계 또는 슬롯을 반환합니다. 사각형의 크기는 사용 가능한 화면 공간, 모든 제약 조건의 크기, 레이아웃에 따른 속성(예: 여백 및 안쪽 여백), 부모 Panel 요소의 개별 동작을 계산하여 결정합니다. 레이아웃 시스템은 이 데이터를 처리하여 특정 Panel의 모든 자식 위치를 계산할 수 있습니다. Border와 같이 부모 요소에 정의된 크기 조정 특성은 해당 자식에 영향을 준다는 것을 유념해야 합니다.

다음 그림에서는 간단한 레이아웃을 보여 줍니다.

경계 상자가 없어 겹쳐 놓은 일반적인 그리드를 보여주는 스크린샷.

다음 XAML을 사용하여 이 레이아웃을 실현할 수 있습니다.

<Grid Name="myGrid" Background="LightSteelBlue" Height="150">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="250"/>
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
  </Grid.RowDefinitions>
  <TextBlock Name="txt1" Margin="5" FontSize="16" FontFamily="Verdana" Grid.Column="0" Grid.Row="0">Hello World!</TextBlock>
  <Button Click="getLayoutSlot1" Width="125" Height="25" Grid.Column="0" Grid.Row="1">Show Bounding Box</Button>
  <TextBlock Name="txt2" Grid.Column="1" Grid.Row="2"/>
</Grid>

단일 TextBlock 요소는 Grid 내에서 호스트됩니다. 텍스트는 첫 번째 열의 왼쪽 위 모퉁이만 채우지만 TextBlock에 할당된 공간은 실제로 훨씬 큽니다. FrameworkElement의 경계 상자는 GetLayoutSlot 메서드를 사용하여 검색할 수 있습니다. 다음 그림에서는 TextBlock 요소에 대한 경계 상자를 보여 줍니다.

TextBlock 경계 사각형이 이제 보임을 보여주는 스크린샷.

노란색 사각형으로 표시되는 TextBlock 요소에 대해 할당된 공간은 실제로는 표시되는 공간보다 훨씬 큽니다. Grid에 요소가 추가될 때 해당 요소의 형식과 크기에 따라 이 할당이 축소되거나 확장될 수 있습니다.

TextBlock의 레이아웃 슬롯은 GetLayoutSlot 메서드를 사용하여 Path로 변환됩니다. 이 기술은 요소의 경계 상자를 표시하는 데 유용합니다.

private void getLayoutSlot1(object sender, System.Windows.RoutedEventArgs e)
{
    RectangleGeometry myRectangleGeometry = new RectangleGeometry();
    myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1);
    Path myPath = new Path();
    myPath.Data = myRectangleGeometry;
    myPath.Stroke = Brushes.LightGoldenrodYellow;
    myPath.StrokeThickness = 5;
    Grid.SetColumn(myPath, 0);
    Grid.SetRow(myPath, 0);
    myGrid.Children.Add(myPath);
    txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString();
}
Private Sub getLayoutSlot1(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim myRectangleGeometry As New RectangleGeometry
    myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1)
    Dim myPath As New Path
    myPath.Data = myRectangleGeometry
    myPath.Stroke = Brushes.LightGoldenrodYellow
    myPath.StrokeThickness = 5
    Grid.SetColumn(myPath, 0)
    Grid.SetRow(myPath, 0)
    myGrid.Children.Add(myPath)
    txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString()
End Sub

레이아웃 시스템

가장 간단한 레이아웃은 재귀 시스템이며 이를 통해 요소의 크기가 조정되고 요소가 배치되고 그려집니다. 더 구체적으로, 레이아웃은 Panel 요소의 Children 컬렉션 멤버를 측정하고 정렬하는 프로세스를 설명합니다. 레이아웃은 집약적인 프로세스입니다. Children 컬렉션이 클수록 수행해야 하는 계산도 많아집니다. 컬렉션을 소유하는 Panel 요소가 정의하는 레이아웃 동작에 따라 복잡해질 수도 있습니다. Canvas 등의 비교적 간단한 PanelGrid 등의 더 복잡한 Panel보다 성능이 훨씬 더 뛰어날 수 있습니다.

자식 UIElement가 해당 위치를 변경할 때마다 레이아웃 시스템에 의해 새로운 단계가 트리거될 수 있습니다. 따라서 불필요한 호출로 인해 애플리케이션 성능이 저하될 수 있으므로 레이아웃 시스템을 호출할 수 있는 이벤트를 이해해야 합니다. 다음에서 레이아웃 시스템이 호출될 때 발생하는 프로세스에 대해 설명합니다.

  1. 자식 UIElement는 먼저 핵심 속성이 측정되도록 하여 레이아웃 프로세스를 시작합니다.

  2. Width, Height, MarginFrameworkElement에 정의된 크기 조정 속성이 평가됩니다.

  3. Dock 방향 또는 누적 Orientation과 같은 Panel 관련 논리가 적용됩니다.

  4. 모든 자식을 측정한 후에 콘텐츠가 정렬됩니다.

  5. Children 컬렉션이 화면에 그려집니다.

  6. Children이 컬렉션에 추가되거나 LayoutTransform이 적용되거나 UpdateLayout 메서드가 호출되면 프로세스가 다시 호출됩니다.

이 프로세스와 해당 프로세스가 호출되는 방법은 다음 섹션에 자세히 정의되어 있습니다.

자식 측정 및 정렬

레이아웃 시스템은 Children 컬렉션의 각 멤버에 대한 두 단계(측정 단계 및 정렬 단계)를 완료합니다. 각 자식 Panel은 고유한 특정 레이아웃 동작을 실현하기 위해 고유의 MeasureOverrideArrangeOverride 메서드를 제공합니다.

측정 처리 단계에서 Children 컬렉션의 각 멤버가 평가됩니다. 이 프로세스는 Measure 메서드를 호출하여 시작됩니다. 이 메서드는 부모 Panel 요소의 구현 내에서 호출되며 레이아웃이 발생하도록 명시적으로 호출될 필요는 없습니다.

먼저 ClipVisibility와 같은 UIElement의 기본 크기 속성이 평가됩니다. 이때 MeasureCore로 전달되는 constraintSize라는 값이 생성됩니다.

두 번째로, FrameworkElement에 정의된 프레임워크 속성이 처리되어 constraintSize 값에 영향을 줍니다. 이 속성은 일반적으로 Height, Width, Margin, Style 등 기본 UIElement의 크기 조정 특성을 설명합니다. 이러한 각 속성은 요소를 표시하는 데 필요한 공간을 변경할 수 있습니다. 그런 다음, 매개 변수로 constraintSize를 사용하여 MeasureOverride가 호출됩니다.

참고

HeightWidth 속성과 ActualHeightActualWidth 속성 간에는 차이점에 있습니다. 예를 들어, ActualHeight 속성은 다른 높이 입력 및 레이아웃 시스템에 따라 계산된 값입니다. 이 값은 실제 렌더링 단계에 따라 레이아웃 시스템 자체에서 설정되므로 입력 변경의 기준인 Height와 같은 속성의 설정 값 뒤로 약간 지연될 수 있습니다.

ActualHeight는 계산된 값이므로 레이아웃 시스템의 다양한 작업의 결과로 여러 번 점증적인 변경이 보고될 수 있습니다. 레이아웃 시스템은 자식 요소에 필요한 측정 공간, 부모 요소에 의한 제약 조건 등을 계산할 수도 있습니다.

측정 처리 단계의 궁극적인 목표는 MeasureCore 호출 중 발생하는 DesiredSize를 자식 항목이 확인할 수 있도록 하는 것입니다. DesiredSize 값은 콘텐츠 정렬 단계 중에 사용하도록 Measure를 통해 저장됩니다.

정렬 단계는 Arrange 메서드 호출을 통해 시작됩니다. 정렬 단계 중에 부모 Panel 요소는 자식의 경계를 나타내는 사각형을 생성합니다. 이 값은 처리를 위해 ArrangeCore 메서드에 전달됩니다.

ArrangeCore 메서드는 자식의 DesiredSize를 평가하고 렌더링된 요소 크기에 영향을 줄 수 있는 추가 여백을 평가합니다. ArrangeCorePanelArrangeOverride 메서드에 매개 변수로 전달되는 arrangeSize를 생성합니다. ArrangeOverride는 자식의 finalSize를 생성합니다. 마지막으로, ArrangeCore 메서드는 여백, 맞춤 등의 오프셋 속성을 최종적으로 평가하고 자식을 레이아웃 슬롯 내에 놓습니다. 자식은 할당된 공간을 모두 채울 필요가 없으며 실제 모두 채우는 경우가 드뭅니다. 그런 다음, 컨트롤이 부모 Panel에 반환되고 레이아웃 프로세스가 완료됩니다.

패널 요소 및 사용자 지정 레이아웃 동작

WPF에는 Panel에서 파생되는 요소 그룹이 포함됩니다. 이러한 Panel 요소는 많은 복잡한 레이아웃을 가능하게 합니다. 예를 들어 요소 누적은 StackPanel 요소를 사용하여 쉽게 실현될 수 있는 반면 보다 복잡하고 흐름이 자유로운 레이아웃은 Canvas를 사용하여 가능합니다.

다음 표에서는 사용 가능한 레이아웃 Panel 요소를 요약합니다.

패널 이름 Description
Canvas Canvas 영역에 상대적인 좌표를 사용하여 자식 요소를 명시적으로 배치할 수 있는 영역을 정의합니다.
DockPanel 자식 요소를 서로 맞춰 가로 또는 세로로 정렬할 수 있는 영역을 정의합니다.
Grid 열 및 행으로 구성되는 유연한 모눈 영역을 정의합니다.
StackPanel 가로 또는 세로 방향으로 한 줄로 자식 요소를 정렬합니다.
VirtualizingPanel 자식 데이터 컬렉션을 가상화하는 Panel 요소를 위한 프레임워크를 제공합니다. 이 클래스는 추상 클래스입니다.
WrapPanel 콘텐츠를 컨테이너의 가장자리에서 다음 줄로 나눠 왼쪽에서 오른쪽으로 자식 요소의 위치를 지정합니다. Orientation 속성의 값에 따라 위쪽에서 아래쪽으로 또는 오른쪽에서 왼쪽으로 순차적으로 정렬됩니다.

미리 정의된 Panel 요소를 사용해도 가능하지 않은 레이아웃이 필요한 애플리케이션의 경우 Panel에서 상속하고 MeasureOverrideArrangeOverride 메서드를 재정의하여 사용자 지정 레이아웃 동작을 실현할 수 있습니다.

레이아웃 성능 고려 사항

레이아웃은 재귀적인 프로세스입니다. Children 컬렉션에 있는 각 자식 요소는 레이아웃 시스템이 호출될 때마다 처리됩니다. 따라서 필요하지 않을 때 레이아웃 시스템을 트리거하는 것을 피해야 합니다. 다음은 보다 뛰어난 성능을 얻는 데 도움이 되는 고려 사항입니다.

  • 레이아웃 시스템에서 재귀적으로 업데이트하도록 하는 속성 값 변경에 주의해야 합니다.

    레이아웃 시스템을 초기화하는 값을 가진 종속성 속성은 공용 플래그로 표시됩니다. AffectsMeasureAffectsArrange는 레이아웃 시스템에서 재귀적으로 업데이트하도록 하는 속성 값 변경에 대한 유용한 단서를 제공합니다. 일반적으로 요소의 경계 상자 크기에 영향을 줄 수 있는 속성은 AffectsMeasure 플래그를 true로 설정해야 합니다. 자세한 내용은 종속성 속성 개요를 참조하세요.

  • 가능한 경우 LayoutTransform 대신 RenderTransform을 사용합니다.

    LayoutTransform은 UI(사용자 인터페이스)의 콘텐츠에 영향을 주는 매우 유용한 방법이 될 수 있습니다. 그러나 변환 효과가 다른 요소의 위치에 영향을 줄 필요가 없는 경우에는 RenderTransform이 레이아웃 시스템을 호출하지 않으므로 RenderTransform을 대신 사용하는 것이 좋습니다. LayoutTransform은 변환을 적용하고 영향을 받는 요소의 새 위치를 고려하도록 재귀적 레이아웃 업데이트를 적용합니다.

  • UpdateLayout에 대한 불필요한 호출은 피합니다.

    UpdateLayout 메서드는 재귀적 레이아웃을 업데이트하도록 하며, 불필요한 경우가 많습니다. 전체 업데이트가 필요하다고 확신하는 경우를 제외하고는 레이아웃 시스템에서만 이 메서드를 호출합니다.

  • Children 컬렉션으로 작업할 경우 일반 StackPanel 대신 VirtualizingStackPanel을 사용하는 것이 좋습니다.

    자식 컬렉션을 가상화하여 VirtualizingStackPanel은 현재 부모의 뷰포트 내에 있는 메모리의 개체만 유지합니다. 이를 통해 대부분의 시나리오에서 성능이 크게 향상됩니다.

하위 픽셀 렌더링 및 레이아웃 반올림

WPF 그래픽 시스템에서는 해상도와 디바이스의 영향을 받지 않기 위해 디바이스 독립적 단위를 사용합니다. 각 디바이스 독립적 픽셀은 시스템의 dpi(인치당 도트 수) 설정에 맞게 자동으로 스케일링됩니다. 이를 통해 WPF 애플리케이션에서 다양한 dpi 설정에 적합하게 스케일링할 수 있으며 자동으로 dpi를 인식할 수 있도록 합니다.

그러나 이러한 dpi 독립성은 앤티앨리어싱으로 인해 불규칙한 가장자리 렌더링을 만들 수 있습니다. 일반적으로 흐리거나 반투명한 가장자리로 표시되는 이러한 아티팩트는 가장자리의 위치가 디바이스 픽셀 사이가 아닌 디바이스 픽셀 가운데에 있을 때 발생할 수 있습니다. 레이아웃 시스템에서는 레이아웃 반올림을 사용하여 조정할 수 있는 방법을 제공합니다. 레이아웃 반올림에서 레이아웃 단계 동안 모든 비정수 픽셀 값을 레이아웃 시스템이 반올림합니다.

레이아웃 반올림은 기본적으로 사용되지 않습니다. 레이아웃 반올림을 사용하려면 UseLayoutRounding 속성을 모든 FrameworkElement에서 true로 설정합니다. 종속성 속성이므로 값이 시각적 트리의 모든 자식에 전파됩니다. 전체 UI에 대해 레이아웃 반올림을 사용하려면 루트 컨테이너에서 UseLayoutRoundingtrue로 설정합니다. 예제를 보려면 UseLayoutRounding를 참조하세요.

다음 단계

요소의 측정 방법과 정렬 방법을 이해하는 것이 레이아웃을 이해하기 위한 첫 단계입니다. 사용 가능한 Panel 요소에 대한 자세한 내용은 Panel 개요를 참조하세요. 레이아웃에 영향을 줄 수 있는 다양한 배치 속성을 더 잘 이해하려면 맞춤, 여백 및 안쪽 여백 개요를 참조하세요. 간단한 애플리케이션 내에 모두 구현할 준비가 되었으면 연습: 내 첫 WPF 데스크톱 애플리케이션을 참조하세요.

참고 항목