Graphics, Animation & Multimedia

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

WPF draws everything itself, with a retained-mode, vector-based renderer that sits on top of DirectX. That is why a Button, a Path, a video and a rotating 3-D cube can all live in one visual tree, be transformed, animated and data bound in the same way. This page covers the drawing and media features of that renderer, from the highest level (Shape elements) to the lowest (DrawingVisual), then animation, media playback, a short look at 3-D, and the rendering tiers and RenderOptions that control how the GPU is used.

The page was written against these versions, current in October 2026:

  • .NET 10 LTS, latest patch 10.0.13, with C# 14. .NET 11 is at release candidate 2 (general availability is expected 10 to 13 November 2026). The graphics, animation and media pages of Microsoft Learn cited below are, for the most part, written for .NET Framework and dated 2017. They list no .NET 10 specific change, and the System.Windows.Media, System.Windows.Media.Animation and System.Windows.Media.Media3D APIs shown here are documented as available for .NET Framework 4.8 and for .NET up to 11.

  • Visual Studio 2026 18.11.1.

  • CommunityToolkit.Mvvm 8.4.2, used for the view model in the examples.

  • Windows App SDK 2.5.1 (stable channel) for the WinUI 3 comparison.

  • Windows 10 and 11. The WPF rendering-tier definitions are expressed in DirectX 9 level terms.

What Microsoft Learn Provides, and What It Does Not

The WPF documentation has a "Graphics and Multimedia" section with overviews for shapes, geometry, brushes, transforms, animation, storyboards, easing functions, multimedia and 3-D graphics, plus the rendering-tier page under the advanced topics. These are conceptual overviews with isolated snippets, mostly in XAML with C# and Visual Basic versions.

Microsoft Learn does not provide:

  • A single end-to-end sample that combines shapes, animation and media in an MVVM application. The examples below that tie the features to the Bookshelf Desktop scenario are original; each one is followed by the official page its APIs come from.

  • Guidance for animating from a view model. Storyboards live in XAML, in styles and templates (see the Styles, Templates & Triggers page of this section once published), or in code-behind; keeping animation in the view and driving it with state, as shown later, is a convention of this page, not an official pattern.

  • Current numbers for the rendering tiers on modern GPUs. The tier definitions refer to DirectX 9 level hardware and to video memory sizes of 60 MB and 120 MB, which any current GPU exceeds.

The Layers of WPF Graphics

WPF offers several ways to put pixels on screen. They are layers over the same Visual class, so the choice is a trade between convenience and cost.

Layer What it gives you What it costs

Shape (Rectangle, Ellipse, Path, and others)

A FrameworkElement: layout, input events, data binding, styles and templates

The most memory per item, because every shape is a full element

Geometry with Path.Data, Clip or GeometryDrawing

A freezable description of a region: drawing, clipping, hit testing, animation paths

Cannot draw itself; needs a Path, a Drawing or a DrawingContext

Drawing (GeometryDrawing, ImageDrawing, GlyphRunDrawing, DrawingGroup) with DrawingImage or DrawingBrush

A light-weight, freezable drawing that you can use as an image source or a brush

No layout or events of its own

DrawingVisual in a host element

The lightest drawable object: no layout, no events, drawn through a DrawingContext

You write the host element, the child bookkeeping and the hit testing

OnRender(DrawingContext) of a custom element

Draw straight into an element you own, keeping layout and events

You manage invalidation yourself

Microsoft Learn summarises the trade this way: drawings are simpler constructs than shapes and perform better, and shapes derive from FrameworkElement, which can add significantly more memory consumption, so use Drawing objects when the FrameworkElement features are not needed. Every Visual stores its output as a serialised list of drawing instructions rather than pixels; the DrawingContext you receive from DrawingVisual.RenderOpen records instructions, it does not paint to the screen immediately. This retained-mode design is what lets WPF redraw only what changed and scale vector content without distortion.

Shapes

All shapes derive from Shape (namespace System.Windows.Shapes): Ellipse, Line, Path, Polygon, Polyline and Rectangle. They share these properties.

Property Meaning

Fill

The Brush that paints the interior; has no effect on a Line, which has no area

Stroke

The Brush that paints the outline

StrokeThickness

Outline thickness in device-independent pixels

Stretch

How the shape fills its layout slot: None, Fill, Uniform or UniformToFill; available on Line, Path, Polygon, Polyline and Rectangle

Data properties

Points, X1/Y1/X2/Y2, RadiusX/RadiusY for a rounded Rectangle, and Data for a Path

A shape is a layout participant, so it takes part in a Grid or StackPanel like any other element. A Canvas is the usual host when you want absolute coordinates. The Stretch value matters when the points are small numbers: a Polygon with points 0,0 0,1 1,1 and Stretch="Fill" fills its whole slot, which is how a resolution-free icon is made. The outline is drawn after stretching, so the stroke keeps its thickness.

The Bookshelf Desktop details pane shows a five-star rating as a row of Path shapes whose Fill depends on the rating, using path markup for the star:

<UserControl x:Class="Bookshelf.Desktop.Controls.StarRating"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <UserControl.Resources>
        <!-- A frozen geometry shared by all five stars. -->
        <PathGeometry x:Key="StarGeometry"
                      Figures="M 10,0 L 12.6,7 L 20,7.6 L 14.2,12.4 L 16,20 L 10,15.8 L 4,20 L 5.8,12.4 L 0,7.6 L 7.4,7 Z" />
        <SolidColorBrush x:Key="StarOn" Color="#F5A623" />
        <SolidColorBrush x:Key="StarOff" Color="#D9D9D9" />
    </UserControl.Resources>

    <StackPanel Orientation="Horizontal">
        <Path Data="{StaticResource StarGeometry}" Fill="{StaticResource StarOn}"
              Stroke="#B9790F" StrokeThickness="0.75" Margin="0,0,2,0" />
        <Path Data="{StaticResource StarGeometry}" Fill="{StaticResource StarOn}"
              Stroke="#B9790F" StrokeThickness="0.75" Margin="0,0,2,0" />
        <Path Data="{StaticResource StarGeometry}" Fill="{StaticResource StarOn}"
              Stroke="#B9790F" StrokeThickness="0.75" Margin="0,0,2,0" />
        <Path Data="{StaticResource StarGeometry}" Fill="{StaticResource StarOff}"
              Stroke="#9A9A9A" StrokeThickness="0.75" Margin="0,0,2,0" />
        <Path Data="{StaticResource StarGeometry}" Fill="{StaticResource StarOff}"
              Stroke="#9A9A9A" StrokeThickness="0.75" />
    </StackPanel>
</UserControl>

The star control above is static on purpose. In the real application, a rating is better modelled as a data template that repeats one Path per star; the Converters, Collection Views & Data Templates page of this section shows the rating-to-stars converter that feeds it.

Geometries

A Geometry describes a 2-D region. It derives from Freezable, not from FrameworkElement, so it cannot render or lay itself out, but it can be declared as a resource, shared, frozen, cloned and used for clipping, hit testing and animation paths.

Class Use

LineGeometry, RectangleGeometry, EllipseGeometry

Simple regions; RectangleGeometry takes RadiusX/RadiusY for rounded corners

PathGeometry

One or more PathFigure objects, each a StartPoint plus PathSegment objects (LineSegment, ArcSegment, BezierSegment, QuadraticBezierSegment, PolyLineSegment, PolyBezierSegment, PolyQuadraticBezierSegment); can be bound, animated and modified

StreamGeometry

A lightweight alternative to PathGeometry: it does not support data binding, animation or modification and is optimised for many shapes

GeometryGroup

Several geometries as one, without combining their areas (cheaper than combining)

CombinedGeometry and Geometry.Combine

A Boolean operation on exactly two geometries, selected by GeometryCombineMode: Union, Intersect, Exclude or Xor

Each simple geometry is defined by a start and end point (LineGeometry), a Rect (RectangleGeometry) or a centre and two radii (EllipseGeometry).

Most XAML paths are written in the path markup syntax, a string of move and draw commands. Commands are case-sensitive: an uppercase letter uses absolute coordinates and a lowercase letter relative ones.

Command Meaning

M / m

Move to a new start point (begins a figure)

L / l, H / h, V / v

Line to a point, horizontal line (an x value), vertical line (a y value)

C / c, Q / q, S / s

Cubic Bezier, quadratic Bezier, smooth cubic Bezier

A / a

Elliptical arc

Z / z

Close the figure

F0 / F1

A prefix that selects the fill rule, even-odd or non-zero

The Bookshelf cover placeholder is built from three geometries: a rounded rectangle for the book, a Union for the ribbon bookmark notch and a clip so that the cover image has rounded corners.

<Grid Width="120" Height="170" Margin="8">
    <Grid.Resources>
        <!-- Rounded corners for the cover; used both to fill and to clip. -->
        <RectangleGeometry x:Key="CoverShape" Rect="0,0,120,170" RadiusX="8" RadiusY="8" />
    </Grid.Resources>

    <!-- Filled shape built from a combined geometry: the cover minus a bookmark notch. -->
    <Path Fill="#3F5F8A" Stroke="#243A57" StrokeThickness="1">
        <Path.Data>
            <CombinedGeometry GeometryCombineMode="Exclude">
                <CombinedGeometry.Geometry1>
                    <RectangleGeometry Rect="0,0,120,170" RadiusX="8" RadiusY="8" />
                </CombinedGeometry.Geometry1>
                <CombinedGeometry.Geometry2>
                    <PathGeometry Figures="M 96,-1 L 108,14 L 120,-1 Z" />
                </CombinedGeometry.Geometry2>
            </CombinedGeometry>
        </Path.Data>
    </Path>

    <!-- A cover image clipped to the same rounded rectangle. -->
    <Image Source="pack://application:,,,/Assets/DefaultCover.png"
           Stretch="UniformToFill"
           Clip="{StaticResource CoverShape}" />
</Grid>

Brushes

Everything visible is painted by a Brush. Brushes are Freezable and Animatable; all of them except VisualBrush can be frozen.

Brush Paints with

SolidColorBrush

One Color

LinearGradientBrush

A gradient along an axis defined by StartPoint and EndPoint, with GradientStop colours

RadialGradientBrush

A gradient across a circle, with a movable GradientOrigin

ImageBrush

An ImageSource

DrawingBrush

A Drawing: shapes, images, text and media; can tile through Viewport and TileMode

VisualBrush

A live Visual, for reflections or magnifiers; it cannot be frozen

BitmapCacheBrush

A cached bitmap of part of the visual tree (WPF 4 and later)

Properties common to all brushes: Opacity (multiplied with the opacity of colours inside the brush; changing a brush opacity is more efficient than changing UIElement.Opacity of a whole element), and Transform and RelativeTransform to rotate, scale, skew or translate the painted content. Predefined colours live in Brushes and Colors; system colours live in SystemColors.

A Freezable brush can be declared once in a resource dictionary and shared by many elements. The Bookshelf spine and a tiled "paper" background show gradients and a tiled DrawingBrush:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <!-- Book spine: a vertical gradient. -->
    <LinearGradientBrush x:Key="SpineBrush" StartPoint="0,0" EndPoint="1,0">
        <GradientStop Color="#2C3E5C" Offset="0.0" />
        <GradientStop Color="#4A6FA5" Offset="0.5" />
        <GradientStop Color="#2C3E5C" Offset="1.0" />
    </LinearGradientBrush>

    <!-- Subtle checker tile for the shelf background. -->
    <DrawingBrush x:Key="PaperBrush" Viewport="0,0,16,16" ViewportUnits="Absolute"
                  TileMode="Tile" Stretch="None">
        <DrawingBrush.Drawing>
            <DrawingGroup>
                <GeometryDrawing Brush="#FAF6EE">
                    <GeometryDrawing.Geometry>
                        <RectangleGeometry Rect="0,0,16,16" />
                    </GeometryDrawing.Geometry>
                </GeometryDrawing>
                <GeometryDrawing Brush="#F1EBDD">
                    <GeometryDrawing.Geometry>
                        <GeometryGroup>
                            <RectangleGeometry Rect="0,0,8,8" />
                            <RectangleGeometry Rect="8,8,8,8" />
                        </GeometryGroup>
                    </GeometryDrawing.Geometry>
                </GeometryDrawing>
            </DrawingGroup>
        </DrawingBrush.Drawing>
    </DrawingBrush>
</ResourceDictionary>

Brushes can also be created and frozen in code, which is what you want for brushes used by a DrawingVisual on a background thread:

using System.Windows.Media;

namespace Bookshelf.Desktop.Graphics;

internal static class BookshelfBrushes
{
    public static readonly Brush Accent = Freeze(new SolidColorBrush(Color.FromRgb(0x4A, 0x6F, 0xA5)));

    public static readonly Brush AccentFill = Freeze(new SolidColorBrush(Color.FromArgb(0x33, 0x4A, 0x6F, 0xA5)));

    public static readonly Pen AccentPen = FreezePen(new Pen(Accent, 1.5));

    private static T Freeze<T>(T brush) where T : Freezable
    {
        brush.Freeze();
        return brush;
    }

    private static Pen FreezePen(Pen pen)
    {
        pen.Freeze();
        return pen;
    }
}

Transforms

A Transform maps points from one coordinate space to another through a 3 by 3 affine matrix (WPF uses row vectors, so the matrix is row-major and its last column is always 0, 0, 1).

Class Properties

RotateTransform

Angle in degrees clockwise, CenterX, CenterY

ScaleTransform

ScaleX, ScaleY, CenterX, CenterY

SkewTransform

AngleX, AngleY, CenterX, CenterY

TranslateTransform

X, Y; no centre, because the offset is the same wherever it is centred

MatrixTransform

A raw Matrix (M11, M12, M21, M22, OffsetX, OffsetY)

TransformGroup

Several transforms applied in order

By default a transform is centred on the origin (0,0) of the element, its upper-left corner. Either set CenterX and CenterY in the element coordinate space, or set the element property RenderTransformOrigin to a point relative to the element size, for example 0.5,0.5 for its centre.

A FrameworkElement has two transform properties, and the difference is important.

Property Behaviour

RenderTransform

Applied after the layout pass; does not change the space the element takes or what its neighbours do; cheaper, and the right choice for animation

LayoutTransform

Applied before layout, so the parent sees the transformed size and re-arranges around it; a TranslateTransform has no visible effect here because layout moves the element back

The same transform classes are Freezable and Animatable, so a RotateTransform can be animated through a name or by a path in a storyboard, as shown below. In Bookshelf Desktop, a "loading" spinner is a Path arc rotated by an animated RenderTransform, and a vertical column header uses a LayoutTransform so the header row grows to fit.

<StackPanel Orientation="Horizontal" Margin="12">
    <!-- A rotated element still occupies its original slot: RenderTransform. -->
    <Border Background="#E8EEF6" Padding="8,4" Margin="0,0,16,0"
            RenderTransformOrigin="0.5,0.5">
        <Border.RenderTransform>
            <RotateTransform Angle="-6" />
        </Border.RenderTransform>
        <TextBlock Text="New arrival" FontWeight="SemiBold" />
    </Border>

    <!-- A rotated element makes the parent adjust to the new size: LayoutTransform. -->
    <Border Background="#E8EEF6" Padding="8,4">
        <Border.LayoutTransform>
            <RotateTransform Angle="-90" />
        </Border.LayoutTransform>
        <TextBlock Text="Shelf A" FontWeight="SemiBold" />
    </Border>
</StackPanel>

Effects

An Effect (namespace System.Windows.Media.Effects) processes the rendered pixels of an element and is set through UIElement.Effect. The built-in effects are BlurEffect (Radius, KernelType, RenderingBias) and DropShadowEffect (BlurRadius, Color, Direction, Opacity, ShadowDepth, RenderingBias). Both expose a RenderingBias that tells WPF whether to favour speed or quality. For anything else, derive from ShaderEffect and supply a compiled pixel shader.

Item Notes

BlurEffect, DropShadowEffect

Built in, hardware accelerated when the GPU supports pixel shader 2.0; their properties can be animated

ShaderEffect

Abstract; you load a PixelShader from precompiled HLSL bytecode (a .ps file, for example referenced through a pack URI), register sampler inputs with RegisterPixelShaderSamplerProperty, and map dependency properties to shader constant registers with PixelShaderConstantCallback; a shader effect has at most 4 samplers, or 8 with a pixel shader 3.0 shader

BitmapEffect family (BlurBitmapEffect, DropShadowBitmapEffect, and others)

Obsolete since .NET Framework 4: using the class throws; use Effect instead, which is significantly faster

Two caveats from the documentation: pixel shader 2.0 shaders run when rendering in software, but pixel shader 3.0 shaders do not run in software, and a pixel shader 3.0 shader on a machine whose hardware lacks support is ignored. With several video cards, behaviour follows the least capable card.

The Bookshelf card below uses a drop shadow that gets stronger when the card is selected. A shadow is an Effect on the card Border; avoid effects on large containers, because the effect is applied to the whole visual subtree of the element it is set on.

<Border x:Name="Card" Width="140" Height="190" CornerRadius="8" Background="White" Margin="12">
    <Border.Effect>
        <DropShadowEffect BlurRadius="12" ShadowDepth="3" Direction="270"
                          Color="Black" Opacity="0.25" RenderingBias="Performance" />
    </Border.Effect>
    <TextBlock Text="The Left Hand of Darkness" TextWrapping="Wrap" Margin="10"
               FontWeight="SemiBold" />
</Border>

A custom ShaderEffect for a "dim read books" greyscale effect follows the pattern from the official sample: a static PixelShader, a Brush input property registered as a sampler and one dependency property per constant. The shader source is a separate .fx file that you compile with the DirectX Effect-Compiler tool to a .ps file, and add to the project as a Resource.

using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;

namespace Bookshelf.Desktop.Effects;

/// <summary>Fades a visual toward grey; the amount is a shader constant.</summary>
public sealed class DimEffect : ShaderEffect
{
    private static readonly PixelShader Shader = new()
    {
        UriSource = new Uri("pack://application:,,,/Bookshelf.Desktop;component/Effects/Dim.ps"),
    };

    public static readonly DependencyProperty InputProperty =
        RegisterPixelShaderSamplerProperty(nameof(Input), typeof(DimEffect), 0);

    public static readonly DependencyProperty AmountProperty =
        DependencyProperty.Register(
            nameof(Amount),
            typeof(double),
            typeof(DimEffect),
            new UIPropertyMetadata(0.0, PixelShaderConstantCallback(0)));

    public DimEffect()
    {
        PixelShader = Shader;
        UpdateShaderValue(InputProperty);
        UpdateShaderValue(AmountProperty);
    }

    public Brush Input
    {
        get => (Brush)GetValue(InputProperty);
        set => SetValue(InputProperty, value);
    }

    public double Amount
    {
        get => (double)GetValue(AmountProperty);
        set => SetValue(AmountProperty, value);
    }
}

Effects are rendered per frame. For a complex but static part of the tree, WPF 4 added BitmapCache (set through UIElement.CacheMode) and BitmapCacheBrush, which render that part once to a bitmap and reuse it. The bitmap stays responsive to input. This is the first thing to try when a heavily nested card list scrolls slowly.

DrawingVisual

A DrawingVisual is the lightest drawable object in WPF. It has no layout and no events, which is why it is fast. To show one you need a host: a FrameworkElement that keeps the visuals in a VisualCollection and overrides two members so the framework can find its visual children.

  • VisualChildrenCount returns how many child visuals there are.

  • GetVisualChild(int) returns the child at an index.

You then draw by calling DrawingVisual.RenderOpen(), which returns a DrawingContext, drawing with its DrawRectangle, DrawEllipse, DrawLine, DrawGeometry, DrawText and similar methods, and closing it (a using statement does that). The host element must have Visibility set to Visible, or none of its children are visible, and hit testing goes through VisualTreeHelper.HitTest.

The Bookshelf details pane has a reading-progress sparkline: one polyline of the pages read per day. It draws as a single StreamGeometry in one DrawingVisual, which is far lighter than a hundred Line shapes.

using System.Globalization;
using System.Windows;
using System.Windows.Media;

namespace Bookshelf.Desktop.Graphics;

/// <summary>Draws pages read per day as a line, with the last point highlighted.</summary>
public sealed class ReadingSparkline : FrameworkElement
{
    private readonly VisualCollection _children;
    private readonly DrawingVisual _visual = new();

    public ReadingSparkline()
    {
        _children = new VisualCollection(this) { _visual };
    }

    public static readonly DependencyProperty ValuesProperty =
        DependencyProperty.Register(
            nameof(Values),
            typeof(IReadOnlyList<double>),
            typeof(ReadingSparkline),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, OnValuesChanged));

    public IReadOnlyList<double>? Values
    {
        get => (IReadOnlyList<double>?)GetValue(ValuesProperty);
        set => SetValue(ValuesProperty, value);
    }

    protected override int VisualChildrenCount => _children.Count;

    protected override Visual GetVisualChild(int index)
    {
        ArgumentOutOfRangeException.ThrowIfNegative(index);
        ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _children.Count);
        return _children[index];
    }

    protected override Size MeasureOverride(Size availableSize) =>
        new(
            double.IsInfinity(availableSize.Width) ? 120 : availableSize.Width,
            double.IsInfinity(availableSize.Height) ? 32 : availableSize.Height);

    protected override Size ArrangeOverride(Size finalSize)
    {
        Redraw(finalSize);
        return finalSize;
    }

    private static void OnValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
        ((ReadingSparkline)d).Redraw(((ReadingSparkline)d).RenderSize);

    private void Redraw(Size size)
    {
        using DrawingContext dc = _visual.RenderOpen();

        var values = Values;
        if (values is null || values.Count < 2 || size.Width <= 0 || size.Height <= 0)
        {
            return;
        }

        double max = Math.Max(values.Max(), 1);
        double stepX = size.Width / (values.Count - 1);

        Point PointAt(int i) => new(i * stepX, size.Height - (values[i] / max) * (size.Height - 4) - 2);

        // StreamGeometry is the light-weight way to describe a line that never changes after it is built.
        var geometry = new StreamGeometry();
        using (StreamGeometryContext ctx = geometry.Open())
        {
            ctx.BeginFigure(PointAt(0), isFilled: false, isClosed: false);
            for (int i = 1; i < values.Count; i++)
            {
                ctx.LineTo(PointAt(i), isStroked: true, isSmoothJoin: true);
            }
        }

        geometry.Freeze();

        dc.DrawGeometry(null, BookshelfBrushes.AccentPen, geometry);
        dc.DrawEllipse(BookshelfBrushes.Accent, null, PointAt(values.Count - 1), 3, 3);

        var label = new FormattedText(
            values[^1].ToString("0", CultureInfo.CurrentCulture),
            CultureInfo.CurrentCulture,
            FlowDirection.LeftToRight,
            new Typeface("Segoe UI"),
            10,
            BookshelfBrushes.Accent,
            VisualTreeHelper.GetDpi(this).PixelsPerDip);
        dc.DrawText(label, new Point(size.Width - label.Width, 0));
    }
}

Using the control is one line, bound to a list the view model exposes:

<local:ReadingSparkline Width="160" Height="36"
                        Values="{Binding SelectedBook.PagesPerDay}" />

The StreamGeometry is opened once, filled by a StreamGeometryContext and then frozen; that is what the geometry overview recommends for complex geometries that need no binding, animation or modification. The PixelsPerDip argument to FormattedText is required in current .NET versions so text is sharp on high-DPI displays; the Documents, Printing & Text page of this section covers text drawing in more detail.

Animation

WPF animates dependency properties by applying a timeline to them. Three requirements for a property to be animatable: it is a dependency property, it belongs to a class that derives from DependencyObject and implements IAnimatable, and a compatible animation type exists. Most UI properties of elements, brushes, transforms and geometries qualify; Opacity, Width, Height, a SolidColorBrush.Color or a RotateTransform.Angle are typical targets. You can use animations in styles, templates and for non-visual objects.

Animation types

Animation classes follow a strict naming convention, one set per property type.

Pattern Meaning

<Type>Animation (DoubleAnimation, ColorAnimation, PointAnimation, and others)

A "From/To/By" animation: interpolate from a start to an end value, or add an offset

<Type>AnimationUsingKeyFrames

Any number of key frames with their own interpolation (discrete, linear, spline, or easing); some types, such as String, can only be animated this way

<Type>AnimationUsingPath

Produces values from a PathGeometry (available for Double, Point and Matrix)

<Type>AnimationBase

The abstract base you derive from for a custom animation

Animations are timelines, so they share the timing properties of Timeline:

Property Default Meaning

Duration

1 second for an animation

How long one iteration takes

AutoReverse

false

Play backwards after reaching the end

RepeatBehavior

1.0 (play once)

Iteration count, a duration, or Forever

FillBehavior

HoldEnd

Hold the final value after the end, or Stop and return to the base value

BeginTime

none specified

Delay before the timeline starts

SpeedRatio

1

Relative speed of time for the timeline

FillBehavior is the source of the most common animation bug. With the default HoldEnd, an animation that ended keeps overriding the property, so a later attempt to set that property in code appears to do nothing (the value is set, but the animation still wins). Use FillBehavior="Stop", remove the storyboard, or call BeginAnimation with a null animation to regain control.

Storyboards

A Storyboard is a container timeline: it adds targeting information to the animations it holds, through two attached properties.

  • Storyboard.TargetName names the object to animate.

  • Storyboard.TargetProperty names the property, as a property path such as Opacity or (UIElement.RenderTransform).(RotateTransform.Angle).

Storyboarding is a framework-level feature, so the target must belong to the namescope of a FrameworkElement or FrameworkContentElement. A FrameworkElement is named with Name; a Freezable such as a brush or transform is named with x:Name, and in code it is registered with RegisterName. When a name is not usable (a Freezable in a style, for example) you target indirectly, through the property path of the owning element. In a Style you may not set TargetName (the storyboard targets the styled element); in a ControlTemplate it may only refer to the template’s own children.

A storyboard is started by a BeginStoryboard trigger action, normally inside an EventTrigger, or in code by Storyboard.Begin. The Bookshelf book card fades and slides in when it is loaded:

<Border x:Name="BookCard" Width="140" Height="190" CornerRadius="8" Background="White"
        RenderTransformOrigin="0.5,0.5" Opacity="0">
    <Border.RenderTransform>
        <TransformGroup>
            <ScaleTransform x:Name="CardScale" ScaleX="0.9" ScaleY="0.9" />
            <TranslateTransform x:Name="CardShift" Y="16" />
        </TransformGroup>
    </Border.RenderTransform>

    <Border.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="BookCard"
                                     Storyboard.TargetProperty="Opacity"
                                     From="0" To="1" Duration="0:0:0.30" />
                    <DoubleAnimation Storyboard.TargetName="CardScale"
                                     Storyboard.TargetProperty="ScaleX"
                                     To="1" Duration="0:0:0.30" />
                    <DoubleAnimation Storyboard.TargetName="CardScale"
                                     Storyboard.TargetProperty="ScaleY"
                                     To="1" Duration="0:0:0.30" />
                    <DoubleAnimation Storyboard.TargetName="CardShift"
                                     Storyboard.TargetProperty="Y"
                                     To="0" Duration="0:0:0.30">
                        <DoubleAnimation.EasingFunction>
                            <CubicEase EasingMode="EaseOut" />
                        </DoubleAnimation.EasingFunction>
                    </DoubleAnimation>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Border.Triggers>

    <TextBlock Text="{Binding Title}" Margin="10" TextWrapping="Wrap" FontWeight="SemiBold" />
</Border>

The animations here have no From on the transform properties. A From/To/By animation without a From starts at the current base value of the property, which keeps the storyboard reusable.

Controlling a storyboard

A storyboard started with BeginStoryboard can be controlled by trigger actions when the BeginStoryboard is given a Name: PauseStoryboard, ResumeStoryboard, SetStoryboardSpeedRatio, SkipStoryboardToFill, StopStoryboard and RemoveStoryboard. In code, call Storyboard.Begin(FrameworkElement, bool isControllable) with true, and then use Pause, Resume, Seek, SkipToFill, Stop and Remove. The interactive actions take effect on the next tick of the timing engine, shortly before the next render, not immediately.

The Bookshelf "refresh" spinner is a repeating rotation that the view starts and stops with the view model IsBusy state. The storyboard is a resource, the code-behind only listens to the view model, so the view model stays free of UI types:

<Window x:Class="Bookshelf.Desktop.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Bookshelf Desktop" Height="640" Width="960">
    <Window.Resources>
        <Storyboard x:Key="SpinStoryboard" RepeatBehavior="Forever">
            <DoubleAnimation Storyboard.TargetName="SpinnerRotation"
                             Storyboard.TargetProperty="Angle"
                             From="0" To="360" Duration="0:0:1" />
        </Storyboard>
    </Window.Resources>

    <Grid>
        <Path x:Name="Spinner" Data="M 12,2 A 10,10 0 1 1 2,12" Stroke="#4A6FA5" StrokeThickness="3"
              StrokeStartLineCap="Round" StrokeEndLineCap="Round"
              Width="24" Height="24" Visibility="Collapsed"
              HorizontalAlignment="Right" VerticalAlignment="Top" Margin="12"
              RenderTransformOrigin="0.5,0.5">
            <Path.RenderTransform>
                <RotateTransform x:Name="SpinnerRotation" />
            </Path.RenderTransform>
        </Path>
    </Grid>
</Window>
using System.ComponentModel;
using System.Windows;
using System.Windows.Media.Animation;

namespace Bookshelf.Desktop;

public partial class MainWindow : Window
{
    private Storyboard? _spin;

    public MainWindow(BooksViewModel viewModel)
    {
        InitializeComponent();
        DataContext = viewModel;
        viewModel.PropertyChanged += OnViewModelPropertyChanged;
    }

    private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(BooksViewModel.IsBusy) && sender is BooksViewModel { IsBusy: var busy })
        {
            SetSpinner(busy);
        }
    }

    private void SetSpinner(bool busy)
    {
        _spin ??= (Storyboard)FindResource("SpinStoryboard");

        if (busy)
        {
            Spinner.Visibility = Visibility.Visible;

            // true makes the storyboard controllable, so it can be stopped and removed later.
            _spin.Begin(this, isControllable: true);
        }
        else
        {
            _spin.Remove(this);
            Spinner.Visibility = Visibility.Collapsed;
        }
    }
}

A few rules apply when you put storyboards in a Style or ControlTemplate: the timing system freezes a storyboard there to make it thread-safe, so it cannot use dynamic resource references or data-binding expressions in its properties, and you cannot attach event handlers to storyboard or animation events in XAML. Animations started by a property Trigger hand off to other trigger animations, but compose with EventTrigger and method-started animations. For data-bound or animated animation properties, the clock is not regenerated automatically; reapply the storyboard to pick up the change.

Easing functions

An easing function reshapes the progress of an animation so the motion is not linear. Set it through the EasingFunction property of an animation, or on an EasingDoubleKeyFrame, and choose its direction with EasingMode: EaseIn (follows the formula), EaseOut (the reverse of it) or EaseInOut (in for the first half, out for the second).

Class Effect

BackEase

Pulls back slightly before moving on

BounceEase

A bounce (Bounces, Bounciness)

ElasticEase

A spring oscillating until it rests (Oscillations, Springiness)

CircleEase, SineEase, ExponentialEase

Circular, sine and exponential curves

QuadraticEase, CubicEase, QuarticEase, QuinticEase

Power curves with exponents 2, 3, 4 and 5

PowerEase

A power curve with a Power property; the same as the four above with Power of 2 to 5

You can write your own by deriving from EasingFunctionBase and overriding EaseInCore (and CreateInstanceCore, because easing functions are Freezable); EasingMode then works for free. A bounce when a book is dropped on a shelf looks like this:

<DoubleAnimation Storyboard.TargetName="BookSpine" Storyboard.TargetProperty="Height"
                 From="30" To="170" Duration="0:0:0.9">
    <DoubleAnimation.EasingFunction>
        <BounceEase Bounces="2" Bounciness="3" EasingMode="EaseOut" />
    </DoubleAnimation.EasingFunction>
</DoubleAnimation>

Animating in code

You do not need a storyboard to animate one property. Every Animatable object has BeginAnimation, and an AnimationClock can be applied directly. The Bookshelf toast that confirms "Book saved" can fade out from code:

using System.Windows;
using System.Windows.Media.Animation;

namespace Bookshelf.Desktop.Graphics;

internal static class Fade
{
    public static void Out(UIElement element, TimeSpan duration)
    {
        var animation = new DoubleAnimation
        {
            To = 0,
            Duration = duration,
            FillBehavior = FillBehavior.Stop,
        };

        // Collapse the element once the animation completes, so the final value is not held forever.
        animation.Completed += (_, _) => element.Visibility = Visibility.Collapsed;

        // HandoffBehavior.SnapshotAndReplace is the default: it starts from the current animated value.
        element.BeginAnimation(UIElement.OpacityProperty, animation);
    }
}

FillBehavior.Stop returns the property to its base value after the animation ends, which is why the code collapses the element in Completed. Passing a null animation to BeginAnimation removes the animation, which is how to regain control of an animated property.

Multimedia

WPF has two playback classes for audio and video. Both can use the Windows Media Player 10 control for playback, and both support the media types that Windows Media Player supports on the machine.

Aspect MediaElement MediaPlayer

Kind

A UIElement with layout, usable in XAML

Not an element; cannot be used in XAML

Display

Placed in the visual tree

Rendered through a VideoDrawing or directly with a DrawingContext

State model

Has loaded and unloaded states (LoadedBehavior, UnloadedBehavior)

Stateless

Use

UI media: a player in a window

Performance-sensitive or audio-only scenarios, drawing-based UI

Media can be driven in two modes, decided by the Clock property:

  • Independent mode (Clock is null, the default): the media drives itself; you set Source, call Play, Pause, Stop and Close, and change Position and SpeedRatio.

  • Clock mode (Clock is not null): a MediaTimeline drives playback, either created in code or inside a Storyboard. The control methods of the element are unavailable and throw InvalidOperationException; use the ClockController instead.

Playback behaviour of MediaElement is set by LoadedBehavior (default Play) and UnloadedBehavior (default Close), both of type MediaState with values Play, Pause, Stop, Close and Manual. The interactive methods work only when LoadedBehavior is Manual. With the defaults, a plain <MediaElement Source="..."/> starts when it loads and, once it ends, closes the media and releases the resources, including video memory. UnloadedBehavior takes priority over a MediaClock, which takes priority over LoadedBehavior.

Sizing needs care: a MediaElement has ActualWidth and ActualHeight of zero until content loads, and always for audio-only content. Set Width or Height, but not both, to keep the aspect ratio. Setting both stretches the media to that fixed size. Setting LoadedBehavior to Play or Pause makes WPF pre-roll the media so that the size is known; after MediaOpened is raised, the sizes are available.

The scenario in Bookshelf Desktop is a sample-audio preview for audiobooks: a small player on the details pane with play, pause, a position slider and error handling. The media file is a content file, not a resource: set the build action to Content with CopyToOutputDirectory set to PreserveNewest or Always.

<StackPanel Margin="12" Width="320">
    <MediaElement x:Name="Preview"
                  Source="{Binding SelectedBook.SampleUri}"
                  LoadedBehavior="Manual" UnloadedBehavior="Stop"
                  MediaOpened="OnPreviewOpened"
                  MediaEnded="OnPreviewEnded"
                  MediaFailed="OnPreviewFailed" />

    <StackPanel Orientation="Horizontal" Margin="0,8,0,0">
        <Button Content="Play" Click="OnPlay" Margin="0,0,6,0" />
        <Button Content="Pause" Click="OnPause" Margin="0,0,6,0" />
        <Button Content="Stop" Click="OnStop" />
    </StackPanel>

    <Slider x:Name="PositionSlider" Margin="0,8,0,0" Minimum="0"
            Thumb.DragStarted="OnSeekStarted" Thumb.DragCompleted="OnSeekCompleted" />
    <TextBlock x:Name="Status" Foreground="#9A1F1F" TextWrapping="Wrap" />
</StackPanel>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;

namespace Bookshelf.Desktop.Views;

public partial class SamplePlayerView : UserControl
{
    private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromMilliseconds(250) };
    private bool _seeking;

    public SamplePlayerView()
    {
        InitializeComponent();
        _timer.Tick += (_, _) =>
        {
            if (!_seeking && Preview.NaturalDuration.HasTimeSpan)
            {
                PositionSlider.Value = Preview.Position.TotalSeconds;
            }
        };
    }

    private void OnPreviewOpened(object sender, RoutedEventArgs e)
    {
        // The natural size and duration are only known after MediaOpened.
        PositionSlider.Maximum = Preview.NaturalDuration.HasTimeSpan
            ? Preview.NaturalDuration.TimeSpan.TotalSeconds
            : 0;
        Status.Text = string.Empty;
    }

    private void OnPreviewEnded(object sender, RoutedEventArgs e)
    {
        _timer.Stop();
        Preview.Stop();
        PositionSlider.Value = 0;
    }

    private void OnPreviewFailed(object? sender, ExceptionRoutedEventArgs e)
    {
        _timer.Stop();
        Status.Text = $"The sample cannot be played: {e.ErrorException.Message}";
    }

    private void OnPlay(object sender, RoutedEventArgs e)
    {
        Preview.Play();
        _timer.Start();
    }

    private void OnPause(object sender, RoutedEventArgs e)
    {
        Preview.Pause();
        _timer.Stop();
    }

    private void OnStop(object sender, RoutedEventArgs e)
    {
        Preview.Stop();
        _timer.Stop();
        PositionSlider.Value = 0;
    }

    private void OnSeekStarted(object sender, DragStartedEventArgs e) => _seeking = true;

    private void OnSeekCompleted(object sender, DragCompletedEventArgs e)
    {
        Preview.Position = TimeSpan.FromSeconds(PositionSlider.Value);
        _seeking = false;
    }
}

MediaElement raises MediaOpened, MediaEnded and MediaFailed (and buffering events for streams), and exposes Position, NaturalDuration, Volume, Balance, IsMuted, SpeedRatio and ScrubbingEnabled. Always handle MediaFailed: a missing codec or file does not throw at the call site, it is only reported through that event.

For a video drawn into a Drawing, a MediaPlayer is attached to a VideoDrawing:

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

namespace Bookshelf.Desktop.Graphics;

internal static class TrailerDrawing
{
    public static VideoDrawing Create(Uri uri, Rect area)
    {
        var player = new MediaPlayer();
        player.Open(uri);

        var drawing = new VideoDrawing
        {
            Rect = area,
            Player = player,
        };

        player.Play();
        return drawing;
    }
}

3-D in Brief

WPF has a modest retained 3-D system. Microsoft Learn is explicit that it is not designed to be a full-featured game development platform. 3-D content lives inside a Viewport3D, which is a normal 2-D element that acts as a window into the scene, and you cannot interpenetrate 2-D and 3-D objects within it.

A scene needs these parts:

Part Role

Coordinate space

The origin is the centre of the viewport; +x right, +y up (unlike 2-D), +z toward the viewer

Camera

PerspectiveCamera (vanishing-point foreshortening: Position, LookDirection, UpDirection, FieldOfView, NearPlaneDistance, FarPlaneDistance) or OrthographicCamera (parallel projection); set through Viewport3D.Camera

Model

GeometryModel3D with a MeshGeometry3D (Positions, TriangleIndices, Normals, TextureCoordinates); WPF does not provide predefined spheres or cubes

Material

DiffuseMaterial, SpecularMaterial (with SpecularPower), EmissiveMaterial, combined with MaterialGroup; each wraps a Brush; BackMaterial sets the back face, which is otherwise culled

Light

AmbientLight, DirectionalLight, PointLight, SpotLight; with no light, nothing is visible

Container

Model3DGroup inside a ModelVisual3D in Viewport3D.Children

Transform and animation

Transform3D classes such as RotateTransform3D; 3-D properties are animated with the same timeline system as 2-D

A Bookshelf cover preview that shows the cover as a textured plane, slightly rotated, is a short scene:

<Viewport3D Width="220" Height="260" ClipToBounds="True">
    <Viewport3D.Camera>
        <PerspectiveCamera Position="0,0,3.2" LookDirection="0,0,-1" UpDirection="0,1,0"
                           FieldOfView="45" NearPlaneDistance="0.5" FarPlaneDistance="20" />
    </Viewport3D.Camera>

    <ModelVisual3D>
        <ModelVisual3D.Content>
            <Model3DGroup>
                <AmbientLight Color="#555555" />
                <DirectionalLight Color="White" Direction="-1,-1,-2" />

                <GeometryModel3D>
                    <GeometryModel3D.Geometry>
                        <MeshGeometry3D Positions="-0.8,-1.1,0  0.8,-1.1,0  -0.8,1.1,0  0.8,1.1,0"
                                        Normals="0,0,1  0,0,1  0,0,1  0,0,1"
                                        TextureCoordinates="0,1  1,1  0,0  1,0"
                                        TriangleIndices="0 1 2  1 3 2" />
                    </GeometryModel3D.Geometry>
                    <GeometryModel3D.Material>
                        <DiffuseMaterial>
                            <DiffuseMaterial.Brush>
                                <ImageBrush ImageSource="pack://application:,,,/Assets/DefaultCover.png" />
                            </DiffuseMaterial.Brush>
                        </DiffuseMaterial>
                    </GeometryModel3D.Material>
                    <GeometryModel3D.BackMaterial>
                        <DiffuseMaterial Brush="#3F5F8A" />
                    </GeometryModel3D.BackMaterial>
                    <GeometryModel3D.Transform>
                        <RotateTransform3D>
                            <RotateTransform3D.Rotation>
                                <AxisAngleRotation3D Axis="0,1,0" Angle="-18" />
                            </RotateTransform3D.Rotation>
                        </RotateTransform3D>
                    </GeometryModel3D.Transform>
                </GeometryModel3D>
            </Model3DGroup>
        </ModelVisual3D.Content>
    </ModelVisual3D>
</Viewport3D>

For anything beyond simple scenes, such as loading model files or game-like rendering, WPF has no built-in answer: the usual choices are a third-party 3-D library or hosting a Direct3D surface through interop (see the Interop with Win32, Windows Forms & Web Content page of this section once published).

Rendering Tiers and the GPU

A rendering tier classifies the graphics hardware of the machine. WPF defines three:

Tier Definition Requirements

0

No hardware acceleration; everything is software rendered

DirectX version lower than 9.0

1

Some features accelerated

DirectX 9.0 or later, video RAM of at least 60 MB, pixel shader 2.0 or later

2

Most features accelerated

DirectX 9.0 or later, video RAM of at least 120 MB, pixel shader 2.0 and vertex shader 2.0 or later, at least 4 multitexture units

Tier 1 was redefined in .NET Framework 4 to include only DirectX 9.0 and later hardware; DirectX 7 and 8 hardware is tier 0. The tier is read at run time from RenderCapability.Tier for the current thread’s dispatcher, and the RenderCapability.TierChanged event fires when it changes, for example when a window moves to another monitor or a remote session starts. RenderCapability also reports pixel shader support through IsPixelShaderVersionSupported and the largest bitmap the hardware can create through MaxHardwareTextureSize.

using System.Windows.Media;

namespace Bookshelf.Desktop.Graphics;

public static class RenderingInfo
{
    /// <summary>The rendering tier, 0 to 2, of the dispatcher on the calling thread.</summary>
    public static int CurrentTier => RenderCapability.Tier >> 16;

    /// <summary>True when expensive visuals (shadows, blur, 3-D) should be turned off.</summary>
    public static bool PreferSimpleVisuals => CurrentTier < 2;

    public static void Watch(Action<int> onChanged) =>
        RenderCapability.TierChanged += (_, _) => onChanged(CurrentTier);
}

The tier tells you which features are hardware accelerated. For tier 1 and 2, most 2-D rendering, most 3-D rasterisation, radial gradients, 3-D lighting and sub-pixel text are. Content that is not hardware accelerated, whatever the tier:

  • printed content, which always uses the WPF software pipeline;

  • anything rendered with RenderTargetBitmap.Render;

  • tiled content with a TileBrush whose TileMode is Tile;

  • surfaces larger than the maximum texture size of the hardware (commonly 2048 by 2048 or 4096 by 4096 pixels);

  • operations whose video RAM need exceeds what the card has;

  • layered windows on operating systems without the Windows Display Driver Model (a non-issue on current Windows); a window with AllowsTransparency="True", WindowStyle="None" and a transparent Background is a layered window.

WPF also exposes four registry values for rendering: disabling hardware acceleration, the maximum multisample value for 3-D anti-aliasing, a required driver date check and use of the reference rasterizer. These are for diagnosing driver problems; do not ship an application that changes them.

RenderOptions

RenderOptions is a static class of attached properties that trade quality for speed on a single element or brush, plus one process-wide setting.

Member Use

BitmapScalingMode attached property

Resampling algorithm for scaled bitmaps; LowQuality switches to a faster algorithm and gives smoother animation when the scale of a bitmap is animated

EdgeMode attached property

Edge rendering of non-text primitives; Aliased turns off anti-aliasing, which is useful for crisp one-pixel lines

CachingHint attached property

With Cache, the rendered content of a TileBrush (DrawingBrush, VisualBrush) is cached instead of re-rendered each frame; combine with CacheInvalidationThresholdMinimum and CacheInvalidationThresholdMaximum (relative sizes that trigger a regeneration)

ClearTypeHint attached property

Whether text can use ClearType; needed when the text sits on a transparent surface

ProcessRenderMode static property

The render mode preference for the whole process, for example forcing software rendering when a driver misbehaves

using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Bookshelf.Desktop.Graphics;

internal static class RenderTuning
{
    /// <summary>Faster resampling for an image that is being zoomed or animated.</summary>
    public static void PrepareForZoom(UIElement cover) =>
        RenderOptions.SetBitmapScalingMode(cover, BitmapScalingMode.LowQuality);

    /// <summary>Keep a static tiled background cached.</summary>
    public static void CacheTile(DrawingBrush brush)
    {
        RenderOptions.SetCachingHint(brush, CachingHint.Cache);

        // Re-render the tile when it grows to twice or shrinks to half the cached size.
        RenderOptions.SetCacheInvalidationThresholdMinimum(brush, 0.5);
        RenderOptions.SetCacheInvalidationThresholdMaximum(brush, 2.0);
    }

    /// <summary>Crisp, aliased one-pixel dividers between shelves.</summary>
    public static void Crisp(UIElement line) =>
        RenderOptions.SetEdgeMode(line, EdgeMode.Aliased);

    /// <summary>Decode a cover straight to thumbnail size instead of full size.</summary>
    public static BitmapImage LoadThumbnail(Uri uri)
    {
        var bitmap = new BitmapImage();
        bitmap.BeginInit();
        bitmap.UriSource = uri;
        bitmap.DecodePixelWidth = 140;
        bitmap.CacheOption = BitmapCacheOption.OnLoad;
        bitmap.EndInit();
        bitmap.Freeze();
        return bitmap;
    }
}

The thumbnail loader applies the imaging advice from the same page: by default WPF decodes an image to its full size and then scales it down, so decode to the size you need and combine many small images into one where you can. The page shows BitmapScalingMode and CachingHint with the cache thresholds; EdgeMode and the BitmapImage members DecodePixelWidth and CacheOption come from the API reference, not from that page.

Performance Guidance

The WPF documentation and the rendering model point to the same short list:

Do Why

Prefer Drawing and DrawingVisual to Shape for large numbers of items

Shapes are full elements with layout and events; drawings are lighter

Freeze brushes, pens, geometries and transforms that never change

A frozen Freezable is read-only, cheaper, shareable and safe to use on any thread

Use StreamGeometry for complex static geometry

No binding, animation or change tracking overhead

Animate RenderTransform and Opacity rather than Width, Height or LayoutTransform

Layout properties trigger a layout pass on every frame

Avoid Effect on large or frequently changing containers

An effect is applied to the whole visual subtree each frame

Use BitmapCache for complex static subtrees

The tree is rendered once and reused

Use BitmapScalingMode.LowQuality while a bitmap is animated or zoomed

Faster resampling avoids dropped frames

Decode images to display size

Less memory and faster loading

Avoid RadialGradientBrush on large objects

Called out by the rendering-tier documentation

Check the tier and offer a simple mode

Software rendering is much slower for effects and 3-D

Measure before you tune. The WPF Performance Suite (Perforator and Visual Profiler) is the documented tool for rendering analysis, but it ships with the old Windows SDK for .NET Framework 4; on a current setup, use the Live Visual Tree and Live Property Explorer of Visual Studio described in the Development Environment & Tooling page of this section, and the performance profiler of Visual Studio 2026.

WPF, WinUI 3 and WinForms

Concern WPF WinUI 3 Windows Forms

Rendering model

Retained-mode visual tree on Direct3D 9, vector based

Retained-mode visual tree on the Windows composition engine

Immediate-mode GDI+ (System.Drawing) painting in OnPaint

Shapes and geometry

System.Windows.Shapes, System.Windows.Media.Geometry

Microsoft.UI.Xaml.Shapes, Microsoft.UI.Xaml.Media geometries (a subset of the WPF set)

Graphics, GraphicsPath, Region

Brushes

Many, including DrawingBrush, VisualBrush

SolidColorBrush, LinearGradientBrush, ImageBrush, XamlCompositionBrushBase, acrylic and Mica materials

Brush classes (SolidBrush, LinearGradientBrush, TextureBrush)

Animation

Storyboard and timelines, property triggers, BeginAnimation

Storyboard, theme transitions and the composition animation system

Timer and manual painting; no animation framework in the box

Effects

BlurEffect, DropShadowEffect, ShaderEffect

Composition effects and shadows through the composition layer

None built in

Media

MediaElement, MediaPlayer

MediaPlayerElement and MediaPlayer

Windows Media Player ActiveX control, or a third-party player

3-D

Viewport3D

None in XAML; use Direct3D interop

None

The WinUI 3 and WinForms columns summarise the commonly documented differences and are not drawn from the WPF pages cited here; check the WinUI and WinForms documentation (see the Windows App Platforms page of this section) before relying on a specific member. The practical consequences when porting: WPF storyboards and easing functions port to WinUI with small changes; DrawingVisual, VisualBrush, ShaderEffect and 3-D do not.

Pitfalls

  • A property stuck after its animation ends. The default FillBehavior is HoldEnd; the animated value overrides later writes to the property. Use FillBehavior="Stop", call Remove, or BeginAnimation with null.

  • Animating a property that is not a dependency property. The animation system needs a dependency property on a DependencyObject that implements IAnimatable. A plain CLR property, such as a view model property, cannot be the target of a storyboard.

  • TargetName not found. The target must be in the namescope: name elements with Name, freezables with x:Name, and register names with RegisterName in code. In a ControlTemplate, TargetName can refer only to template children.

  • Binding in storyboards inside a Style or ControlTemplate. Dynamic resource and binding expressions prevent freezing and are not allowed; animate a bound element property instead, or build the storyboard in code.

  • Using LayoutTransform for animation. It makes every frame a layout pass; use RenderTransform plus RenderTransformOrigin.

  • TranslateTransform as a LayoutTransform. It has no effect, because layout moves the element back.

  • Effects on big containers. A DropShadowEffect on a root Grid re-renders the whole window content. Put the effect on the smallest element, or use BitmapCache.

  • A bitmap effect. BitmapEffect classes are obsolete since .NET Framework 4 and throw; use Effect.

  • Pixel shader 3.0 on a software or older renderer. PS 3.0 effects are skipped without an error on hardware that lacks support, and do not run in software rendering; keep a fallback visual.

  • Media as a project resource. A MediaElement cannot play a media file embedded as a Resource; set the build action to Content with a copy-to-output setting.

  • Ignoring MediaFailed. A missing codec or file shows nothing and does not throw; handle the event and show a message.

  • Calling Play in clock mode. It throws InvalidOperationException; drive the MediaClock through its ClockController.

  • Media that never releases. Under default behaviour a finished MediaElement closes itself, but one set to Manual keeps video memory until you call Close. Call Close when you are done with it.

  • MediaElement with both Width and Height. The video is stretched to a fixed size and the aspect ratio is lost; set one of them.

  • Visual host that does not report its children. A DrawingVisual host must override both VisualChildrenCount and GetVisualChild, and its Visibility must be Visible, or nothing is drawn or hit tested.

  • Thread affinity. A frozen Freezable can be shared across threads; an unfrozen one belongs to the thread that created it. Build brushes and geometries on a worker thread only if you freeze them before handing them over.

  • Assuming the tier on a remote desktop. Hardware acceleration may be off in a remote session; listen to TierChanged and degrade visuals rather than caching the first value.

  • Animations without an off switch. Heavy motion can be a problem for some users. The system exposes a preference for client-area animation through SystemParameters; honour it by shortening durations to zero when it is off (see the Accessibility & UI Automation page of this section once published).