A project with dozens of WPF designs and animations

A project with dozens of WPF designs and animations

This is a WPF project for creating and collecting some fun designs and animations. It currently has dozens of demos, some of which have related blog posts introducing detailed implementation steps and principles.

Last updated 4/17/2022 5:51 PM
dino.c
10 min read
Category
WPF
Topic
WPF UI Design
Tags
.NET WPF WPF UI Design Animation Design

Wpf Design And Animation Lab

This is a WPF project for creating and collecting fun designs and animations. It currently includes dozens of demos, some of which have corresponding blog posts detailing implementation steps and principles:

Design and Animation - dino.c - Blog Garden

Through these blog posts, you will learn how to implement some cool WPF animations and designs, as well as various WPF technical details.

1. Implemented Designs and Animations

1.1 Three Ways to Implement an Arc Progress Bar

image

There are many ways to implement an arc progress bar. By using the three approaches – Path with ArcSegment, Arc, and Ellipse – you can understand the basic usage of various Shape types.

1.2 Implementing a Cylindrical Progress Bar Using Only Rectangle

image

A cylindrical progress bar is not difficult to implement, but it's interesting that the image above is entirely composed of Rectangle elements, which is a bit counterintuitive.

First, we need to revisit some basics: Rectangle displays rectangles with rounded corners. RadiusX and RadiusY specify the X-axis and Y-axis radii of the ellipse used to round the rectangle's corners.

In the following example, you can see that the rounded corners of a Rectangle with RadiusX="50" RadiusY="20" exactly overlap an Ellipse with Width="100" Height="40" (X-axis radius 50, Y-axis radius 20).

<Rectangle  Height="100"
            Width="100"
            Fill="#FF7E9EC0"
            Stroke="#FFFF0EC4"
            StrokeThickness="5"
            RadiusX="50"
            RadiusY="20" />
<Ellipse HorizontalAlignment="Left"
         VerticalAlignment="Top"
         StrokeThickness="5"
         Stroke="Yellow"
         Fill="Red"
         Width="100"
         Height="40"
         Opacity="0.5" />

image

Now if we stretch the Rectangle above, it becomes the basic shape of a cylinder; conversely, squashing it gives the cross-section of the cylinder. Making them semi-transparent yields the background of a cylindrical progress bar:

image

<Grid.Resources>
   <Style TargetType="Rectangle">
       <Setter Property="Fill" Value="#36a8e2" />
       <Setter Property="RadiusX" Value="25" />
       <Setter Property="RadiusY" Value="5" />
   </Style>
</Grid.Resources>
<Rectangle Opacity="0.2" />
<Rectangle Height="10"
          VerticalAlignment="Top"
          Opacity="0.1" />

Then add a semi‑transparent gradient layer and another cross-section to complete the cylindrical progress bar.

1.3 Playing with Rainbow Text and Animation

Splitting text with ItemsControl to achieve rainbow text is a fun approach because you can apply different transformations and animations to each character, enabling many variations. First, since a string is a collection, it can actually be used as the ItemsSource of an ItemsControl. However, writing ItemsSource="somestring" directly in Xaml causes an error. You can wrap it with a ContentControl like this:

<ContentControl Content="ItemsControl" >
    <ContentControl.Template>
        <ControlTemplate TargetType="ContentControl">
            <ItemsControl ItemsSource="{TemplateBinding Content}" >
            </ItemsControl>
        </ControlTemplate>
    </ContentControl.Template>
</ContentControl>

Then set the ItemsPanel of the ItemsControl to arrange the content horizontally, and set the DataTemplate to display each character in a TextBlock:

<ItemsControl ItemsSource="{TemplateBinding Content}" >
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Next, to give each character a different color, implement a collection class and instantiate it in Xaml, placing the colors to use:

<common:RepeatCollection x:Key="RepeatCollection">
    <SolidColorBrush>#4a0e68</SolidColorBrush>
    <SolidColorBrush>#b62223</SolidColorBrush>
    <SolidColorBrush>#fdd70c</SolidColorBrush>
    <SolidColorBrush>#f16704</SolidColorBrush>
    <SolidColorBrush>#69982d</SolidColorBrush>
    <SolidColorBrush>#0075a5</SolidColorBrush>
    <SolidColorBrush>#0b0045</SolidColorBrush>
</common:RepeatCollection>

The code for this RepeatCollection is as follows. It's essentially a circular queue; each call to the Next getter retrieves the next element (maybe CircleCollection would be a better name?):

public class RepeatCollection : Collection<object>
{
    private int _offset;

    public object Next
    {
        get
        {
            if (this.Count == 0)
                return null;

            var result = this[_offset];
            _offset++;
            if (_offset > this.Count - 1)
                _offset = 0;

            return result;
        }
    }
}

Finally, bind the TextBlock's Foreground to the Next property of the collection, so each TextBlock uses a different color:

<TextBlock Foreground="{Binding Next, Source={StaticResource RepeatCollection}}" Text="{Binding}" />

image

By modifying the code above, we can animate the rainbow text:

image

1.4 Making a Rainbow Button

Applying a LinearGradientBrush to text turns it rainbow-colored. If two GradientStops have the same Color, no gradient occurs; if their Offset values are set to transition instantly, the color jumps. Using this technique, combined with a monospaced font, we can create rainbow text where each character has a different color:

<LinearGradientBrush x:Name="RainbowBrush" StartPoint="0,0.5" EndPoint="1,.5">
    <GradientStop x:Name="G1" Offset="0" Color="#65b849" />
    <GradientStop x:Name="G2" Offset=".166" Color="#65b849" />
    <GradientStop x:Name="G3" Offset=".166" Color="#f7b423" />
    <GradientStop x:Name="G4" Offset=".3333" Color="#f7b423" />
    <GradientStop x:Name="G5" Offset="0.3333" Color="#f58122" />
    <GradientStop x:Name="G6" Offset="0.5" Color="#f58122" />
    <GradientStop x:Name="G7" Offset="0.5" Color=" #f8f8f8" />
    <GradientStop x:Name="G8" Offset="0.5" Color=" #f8f8f8" />
    <GradientStop x:Name="G9" Offset="0.50" Color="#de3a3c" />
    <GradientStop x:Name="G10" Offset="0.666" Color="#de3a3c" />
    <GradientStop x:Name="G11" Offset="0.666" Color="#943f96" />
    <GradientStop x:Name="G12" Offset="0.8633" Color="#943f96" />
    <GradientStop x:Name="G13" Offset="0.8633" Color="#009fd9" />
    <GradientStop x:Name="G14" Offset="01" Color="#009fd9" />
</LinearGradientBrush>

image

In the MouseOver Storyboard, we control the LinearGradientBrush to change its direction. There are two ways to do this: using PointAnimation to change StartPoint and EndPoint, or using DoubleAnimation to directly change the LinearGradientBrush.RelativeTransform. The latter is written as:

<Storyboard>
    <DoubleAnimation Storyboard.TargetName="textBlock"
                     Storyboard.TargetProperty="(TextBlock.Foreground).(Brush.RelativeTransform).(RotateTransform.Angle)"
                     To="90"
                     Duration="0:0:0.5">
        <DoubleAnimation.EasingFunction>
            <QuarticEase EasingMode="EaseOut" />
        </DoubleAnimation.EasingFunction>
    </DoubleAnimation>
</Storyboard>


<LinearGradientBrush x:Name="RainbowBrush" StartPoint="0,0.5" EndPoint="1,.5">
    <LinearGradientBrush.RelativeTransform>
        <RotateTransform Angle="0" CenterX="0.5" CenterY="0.5" />
    </LinearGradientBrush.RelativeTransform>

When run, the effect rotates all colors by 90 degrees, resembling the old Apple logo color scheme. In the LinearGradientBrush above, I hid two white GradientStops (named G6 and G7), both with Offset 0.5, placed right in the middle. In the button's Pressed state, using DoubleAnimation, we set the Offset of all GradientStops before and after them to 0 or 1, pushing all colors to the sides. Since it's now rotated 90 degrees, they are pushed up and down:

image

1.5 Two Loading Animations from Nintendo Switch

image

image

Using character splitting and a TimeSpanIncreaser pattern, we implemented two of the most common animations seen on the Nintendo Switch.

1.6 Writing a Lighten Effect Using Shazzam Shader Editor

In the animation above, to achieve different brightness levels for Grids, we used a LightenConverter class, but it only works with SolidColorBrush. To make it more versatile, we'll write our own Effect to achieve the same Lighten effect.

image

1.7 Implementing Inner Shadow in WPF

In WPF, we usually use DropShadow for shadow effects, but those are outer shadows. Inner shadows can be achieved, albeit with some workarounds. There are several approaches; my favorite is using another element's VisualBrush as an OpacityMask.

<Grid Width="100"
      Height="100"
      Margin="10">
    <Rectangle x:Name="Rectangle2"
               Fill="White"
               RadiusX="8"
               RadiusY="8" />
    <Border Margin="0">
        <Border.Effect>
            <DropShadowEffect BlurRadius="8" ShadowDepth="0" />
        </Border.Effect>
        <ContentControl HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Content="OpacityMask" />
    </Border>
    <Grid.OpacityMask>
        <VisualBrush Stretch="None" Visual="{Binding ElementName=Rectangle2}" />
    </Grid.OpacityMask>
</Grid>

However, the resulting shadow is not very thick. For larger, thicker inner shadows, you can use a negative Margin combined with an equally thick BorderThickness. Using the OpacityMask approach, the following code creates a thick and large inner shadow:

private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    ShadowElement.Margin = new Thickness(-e.NewValue);
    ShadowElement.BorderThickness = new Thickness(e.NewValue);
    (ShadowElement.Effect as DropShadowEffect).BlurRadius = e.NewValue * 2;
}

image

1.8 Mimicking UWP's Text Shimmer Animation with OpacityMask

image

In the UWP Windows Composition Samples, there is a Text Shimmer animation demonstrating how to use Composition Light. The animation is simple: a PointLight sweeps across a line of text from left to right. Although WPF doesn't have Composition Light, we can still simulate this simple animation using OpacityMask.

RadialGradientBrush represents a circular gradient brush. Here we care about three properties:

RadiusX/RadiusY: The horizontal/vertical radius of the circle. Center: The center of the outermost circle. GradientOrigin: The location of the two‑dimensional focal point where the gradient begins.

The role of these properties is illustrated below:

image

Using a RadialGradientBrush as the OpacityMask causes the TextBlock to gradually become transparent from the center outward:

<TextBlock HorizontalAlignment="Center"
           VerticalAlignment="Center"
           FontFamily="SegoeUI"
           FontSize="100"
           FontWeight="Thin"
           Foreground="DimGray"
           Text="Text Shimmer">
    <TextBlock.OpacityMask>
        <RadialGradientBrush x:Name="Brush" Center=".5,.5" GradientOrigin=".5,.5" RadiusX=".43" RadiusY="2">
            <GradientStop Color="Black" />
            <GradientStop Offset=".5" Color="#6000" />
            <GradientStop Offset="1" Color="#2000" />
        </RadialGradientBrush>
    </TextBlock.OpacityMask>
</TextBlock>

Then animate Center and GradientOrigin with PointAnimation to move the OpacityMask horizontally, simulating the sweep of a PointLight:

<PointAnimation RepeatBehavior="Forever"
                Storyboard.TargetName="Brush"
                Storyboard.TargetProperty="Center"
                From="-2,.5"
                To="3,.5"
                Duration="0:0:3.3" />
<PointAnimation RepeatBehavior="Forever"
                Storyboard.TargetName="Brush"
                Storyboard.TargetProperty="GradientOrigin"
                From="-2,.5"
                To="3,.5"
                Duration="0:0:3.3" />

1.9 Recreating a CSS3 Button

image

Recreate a button implemented with CSS3, while getting familiar with CSS3 in the process.

1.10 Line Light and Shadow Effect Using an Effect

image

To achieve this effect, I used the following knowledge and techniques:

  • Segoe Fluent icon font
  • Creating a Path in Blend
  • Calculating the length of a Path
  • Path border animation
  • Design-time data support in Visual Studio
  • Custom Effect

2. License

The project is released under MIT License.

3. UWP Version

Additionally, I have another project for playing with UWP animations:

https://github.com/DinoChan/uwp_design_and_animation_lab

image

Reprinted from GitHub

Author: dino.c

Repository: https://github.com/DinoChan/wpf_design_and_animation_lab

Keep Exploring

Related Reading

More Articles
Same category / Same tag 9/13/2025

Migration Series from WPF to Avalonia: Why I Must Migrate My WPF Application to Avalonia

In the past few years, our host computer software has mainly been developed using WPF and WinForm . These technologies work well on the Windows platform and have accompanied us from small-scale trial production to the current stage of large-scale delivery. However, with business development and changes in customer requirements, the single Windows technology stack has gradually become a hurdle we must overcome.

Continue Reading
Same category / Same tag 1/26/2025

Implementing Internationalization in WPF Using Custom XML Files

This article details the method of implementing internationalization in WPF applications using custom XML files, including installing the necessary NuGet packages, dynamically retrieving the language list, dynamically switching languages, using translated strings in code and XAML interfaces, and provides a source code link to help developers easily achieve internationalization in WPF applications.

Continue Reading