본문 바로가기
프로그래밍&IT/C# (Winfrom, WPF)

[WPF] Effects and Visuals

by 성장의 용 2025. 8. 10.
728x90
반응형

WPF에서 그래픽을 더 풍부하고 멋있게 만드는 이펙트 (Effects)와 하위 레벨 렌더링 API 비주얼(Visual)에 대해서

 

1. 이펙트 (Effects)

주로 Pixel Shader 기반의 이펙트를 사용해 UI 요소에 실시간 그래픽 효과를 줄 수 있는데 대표적인 내장 이펙트는 다음과 같다

 

  • DropShadowEffect: 그림자 효과
  • BlurEffect: 블러(흐림) 효과
  • BitmapEffect (구버전, 권장 안 함)

- 사용법 예제 : DropShadowEffect

 

  • Effect 속성에 이펙트를 지정해서 해당 요소 전체에 효과 적용
  • GPU 가속 지원, 고성능 실시간 효과 가능

 

 

<Button Content="Shadow Button" Width="150" Height="40">
    <Button.Effect>
        <DropShadowEffect Color="Black" Direction="320" ShadowDepth="5" BlurRadius="10" Opacity="0.5"/>
    </Button.Effect>
</Button>

 

* Custom Shader Effects

 

  • WPF는 HLSL 기반의 픽셀 셰이더를 커스터마이징해서 직접 작성할 수 있음
  • 컴파일된 .ps 파일을 프로젝트에 포함하고 ShaderEffect 클래스를 상속해 커스텀 효과 제작 가능
  • 하지만 고급 주제이며, 책에서는 기초 및 예제를 소개함

 

 

2. 비주얼 (Visual)

WPF의 렌더링 하위 레벨 객체 모델.

Visual 클래스는 화면에 그려지는 모든 요소의 기본 클래스이며, 고성능 그래픽 작업, 직접 그리기, 커스텀 컨트롤 구현 시 주로 사용

 

  • DrawingVisual: 벡터 그래픽, 텍스트, 이미지 등 빠른 그리기용
  • ContainerVisual: 자식 Visual을 포함하는 그룹 역할
  • HostVisual: 다른 스레드에서 렌더링 할 때 사용

- DrawingVisual 예제

DrawingVisual visual = new DrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
    dc.DrawRectangle(Brushes.LightBlue, new Pen(Brushes.Blue, 2), new Rect(10, 10, 100, 50));
    dc.DrawText(
        new FormattedText("Hello Visuals",
            System.Globalization.CultureInfo.CurrentCulture,
            FlowDirection.LeftToRight,
            new Typeface("Segoe UI"),
            16, Brushes.Black),
        new Point(15, 15));
}

* DrawingContext

WPF의 저수준 그래픽 렌더링 API에서 사용하는 핵심 클래스

DrawingVisual이나 OnRender 오버라이드 안에서 DrawingContext를 통해 직접 그리기 작업을 수행

  • 역할: 선, 도형, 텍스트, 이미지 등을 화면에 그리는 메서드를 제공
  • 사용처: DrawingVisual.RenderOpen(), OnRender(DrawingContext dc), Visual 하위 클래스 등에서 사용
  • 생명주기: RenderOpen()으로 획득 → 그림 명령 실행 → Close()로 마무리
  • 불변성: 그리기 후 DrawingContext는 닫히고 재사용 불가 → 매번 새로 열고 닫음

1) 주요 메서드

메서드 설명 주요 파라미터
DrawLine 직선 그리기 Pen, 시작점(Point), 끝점(Point)
DrawRectangle 사각형 채우기 및 테두리 그리기 Brush, Pen, Rect
DrawEllipse 원 또는 타원 그리기 Brush, Pen, 중심점(Point), 반지름(x,y)
DrawGeometry 복잡한 Geometry 그리기 Brush, Pen, Geometry
DrawText 텍스트 그리기 FormattedText, 위치(Point)
DrawImage 이미지 그리기 ImageSource, Rect
PushClip 클리핑 영역 지정 Geometry
PushOpacity 투명도 적용 double (0~1)
PushTransform 변환 행렬 적용 Transform
Pop 이전에 푸시한 상태 되돌리기 없음

2) DrawingContext 사용예시

protected override void OnRender(DrawingContext dc)
{
    // 파란 사각형 그리기
    dc.DrawRectangle(Brushes.LightBlue, new Pen(Brushes.Blue, 2), new Rect(10, 10, 100, 60));

    // 빨간 원 그리기
    dc.DrawEllipse(Brushes.Red, new Pen(Brushes.DarkRed, 1), new Point(200, 50), 40, 30);

    // 직선 그리기
    dc.DrawLine(new Pen(Brushes.Green, 3), new Point(10, 100), new Point(200, 100));

    // 텍스트 그리기
    var ft = new FormattedText(
        "Hello DrawingContext",
        System.Globalization.CultureInfo.CurrentCulture,
        FlowDirection.LeftToRight,
        new Typeface("Segoe UI"),
        16,
        Brushes.Black,
        VisualTreeHelper.GetDpi(this).PixelsPerDip);

    dc.DrawText(ft, new Point(10, 120));
}

3) Push / Pop 상태 스택

DrawingContext는 그리기 상태를 스택 형태로 관리한다.

  • Push* 메서드는 현재 상태를 변경하면서 이전 상태를 스택에 저장
  • 반드시 Pop()으로 되돌려야 함 (스택 언더플로우 주의)
dc.PushOpacity(0.5);
dc.DrawRectangle(Brushes.Blue, null, new Rect(0, 0, 100, 100));
dc.Pop();  // 이전 상태 복원

4) 활용팁

  • DrawingVisual과 함께 고성능 커스텀 그리기에 필수
  • 복잡한 UI는 DrawingGroup과 함께 사용해 재활용 가능
  • 성능 최적화를 위해 Freeze() 가능한 브러시, 펜, Geometry는 미리 얼려 두는 게 좋음

* Wrapping visuals in an Element

Visual 객체를 직접 관리하면서 UIElement처럼 동작하게 감싸는 (Wrapping) 기법

Visual 또는 DrawingVisual 같은 저수준 그래픽 객체를 컨트롤처럼 다루고 레이아웃, 입력 이벤트 처리 등 UI 요소 역할을 하도록 하는 패턴.

- Wrapping하는 이유

 

  • Visual은 UIElement가 아니므로 기본적인 레이아웃, 입력, 포커스 처리 기능이 없음
  • 따라서 Visual을 컨테이너 역할을 하는 UIElement 상속 클래스 내에서 자식으로 등록하고 관리해야 함
  • 이를 통해 커스텀 컨트롤 개발 시 효율적이고 가벼운 렌더링 가능

- AddVisualChild 와 RemoveVisualChild

 

  • UIElement 기반 클래스에 Visual 자식을 수동으로 등록/해제하는 메서드
  • 등록된 Visual은 WPF 렌더링 트리에 포함되고, 렌더링과 입력 처리가 가능해짐

VisualChildrenCount 와 GetVisualChild 는 Visual 트리 탐색에 반드시 구현해야 하는 메서드 (VisualCollection 관리 시 필수)

protected override int VisualChildrenCount => _visuals.Count;
protected override Visual GetVisualChild(int index)
{
    return _visuals[index];
}

 

 

예시) Visual래핑하는 커스텀 컨트롤

 

  • isualCollection을 통해 Visual 자식을 관리
  • VisualChildrenCount, GetVisualChild 오버라이드 필수
  • 이제 이 VisualHost를 XAML이나 코드에서 UI 요소처럼 사용 가능

 

 

public class VisualHost : FrameworkElement
{
    private readonly VisualCollection _children;

    public VisualHost()
    {
        _children = new VisualCollection(this);

        DrawingVisual drawingVisual = new DrawingVisual();
        using (DrawingContext dc = drawingVisual.RenderOpen())
        {
            dc.DrawEllipse(Brushes.LightBlue, new Pen(Brushes.DarkBlue, 2), new Point(50, 50), 40, 30);
        }

        _children.Add(drawingVisual);
    }

    protected override int VisualChildrenCount => _children.Count;

    protected override Visual GetVisualChild(int index)
    {
        if (index < 0 || index >= _children.Count)
            throw new ArgumentOutOfRangeException();
        return _children[index];
    }
}

 

* Visual Layer의 장점

 

  • 높은 성능: Visual은 UIElement보다 가볍고 렌더링 속도가 빠름
  • 직접 그리기 가능: 세밀한 그래픽 처리, 애니메이션 효과 구현에 적합
  • 낮은 메모리 오버헤드

- 주의점

 

  • Visual은 기본적으로 입력 이벤트 처리 기능 없음
  • 직접 이벤트 처리 구현해야 함 (HitTest 등)
  • 레이아웃 관리 기능 없음 (크기 조정, 정렬 따로 구현 필요)

 

 

using System.Windows;
using System.Windows.Media;
using System.Windows.Input;

public class VisualHost : FrameworkElement
{
    private readonly VisualCollection _children;

    public VisualHost()
    {
        _children = new VisualCollection(this);

        // 새로운 DrawingVisual 생성
        DrawingVisual visual = new DrawingVisual();

        using (DrawingContext dc = visual.RenderOpen())
        {
            // 사각형 그리기
            dc.DrawRectangle(Brushes.LightBlue, new Pen(Brushes.DarkBlue, 2), new Rect(20, 20, 100, 80));
            // 텍스트 그리기
            var ft = new FormattedText(
                "Hello Visual Layer",
                System.Globalization.CultureInfo.CurrentCulture,
                FlowDirection.LeftToRight,
                new Typeface("Segoe UI"),
                16,
                Brushes.Black,
                VisualTreeHelper.GetDpi(this).PixelsPerDip);
            dc.DrawText(ft, new Point(30, 50));
        }

        // VisualCollection에 추가하여 렌더링 대상에 포함
        _children.Add(visual);
    }

    protected override int VisualChildrenCount => _children.Count;

    protected override Visual GetVisualChild(int index)
    {
        if (index < 0 || index >= _children.Count)
            throw new ArgumentOutOfRangeException(nameof(index));
        return _children[index];
    }
}

 

'프로그래밍&IT > C# (Winfrom, WPF)' 카테고리의 다른 글

[WPF] Geometries and Drawings  (2) 2025.08.09
[WPF] Shapes, Brushes and Transforms  (1) 2025.08.09
[WPF] Styles and Behaviors  (1) 2025.08.09
[WPF] Resources  (3) 2025.08.07
[WPF] Commands  (0) 2025.08.06