0

I have below drawing image which is an arc of a circumference and now I would like to apply rotations from 0 to 360 in a loop indefinitely to simulate a spinner animated. Something like you can see here, an arc of circumference rotating forever.

enter image description here

<DrawingImage x:Key="ico_spinnerDrawingImage">
  <DrawingImage.Drawing>
    <DrawingGroup ClipGeometry="M0,0 V12 H12 V0 H0 Z">
      <DrawingGroup.Transform>
        <TranslateTransform X="9.5367397534573684E-07" Y="1.1465158453161615E-14" />
      </DrawingGroup.Transform>
      <GeometryDrawing Brush="#FF00AA2B" Geometry="F1 M12,12z M0,0z M12,12C12,10.4241 11.6896,8.86371 11.0866,7.4078 10.4835,5.95189 9.59958,4.62902 8.48528,3.51472 7.37098,2.40042 6.04811,1.5165 4.5922,0.913445 3.13629,0.310389 1.57586,-6.88831E-08 -9.53674E-07,0L0,3C1.1819,3 2.35222,3.23279 3.44415,3.68508 4.53608,4.13738 5.52823,4.80031 6.36396,5.63604 7.19969,6.47177 7.86262,7.46392 8.31492,8.55585 8.76721,9.64778 9,10.8181 9,12L12,12z" />
    </DrawingGroup>
  </DrawingImage.Drawing>
</DrawingImage>

Above drawingimage is inclosed within a dictionary that I import to my WPF view and I associate it to the source property of an WPF image by doing this (i removed the not relevant properties):

    <Image Source="{Binding Path=MySpinnerIcon}"/>

Now, how can apply that animation to the drawingimage to simulate an animated spinner circling continuously forever?

Willy
  • 9,848
  • 22
  • 141
  • 284

3 Answers3

1

You may animate the Angle property of a RotateTransform that is used as the RenderTransform of a UI element.

The animation itself is performed by a DoubleAnimation in a StoryBoard that can for example be started by an EventTrigger on the Loaded event of the UI element.

The example below uses a simple Path element instead of an Image. It draws a 20 units wide elliptical arc with round ends and radius 90 into a 200x200 box.

<Path Data="M100,10 A90,90 0 0 1 190,100"
      Width="200" Height="200"
      Stroke="Turquoise" StrokeThickness="20"
      StrokeStartLineCap="Round" StrokeEndLineCap="Round"
      RenderTransformOrigin="0.5,0.5">
    <Path.RenderTransform>
        <RotateTransform />
    </Path.RenderTransform>
    <Path.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard TargetProperty="RenderTransform.Angle">
                    <DoubleAnimation By="360" Duration="0:0:1"
                                     RepeatBehavior="Forever"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Path.Triggers>
</Path>
Clemens
  • 123,504
  • 12
  • 155
  • 268
1

Example:

<StackPanel>
    <FrameworkElement.Resources>
        <Pen x:Key="сircle" Brush="LightGray" Thickness="5"/>
        <Pen x:Key="arc" Brush="LightGreen" Thickness="5" EndLineCap="Round" StartLineCap="Round"/>
        <DrawingImage x:Key="ico_spinnerDrawingImage">
            <DrawingImage.Drawing>
                <DrawingGroup>
                    <DrawingGroup.Transform>
                        <RotateTransform Angle="0"/>
                    </DrawingGroup.Transform>
                    <GeometryDrawing Pen="{StaticResource сircle}">
                        <GeometryDrawing.Geometry>
                            <EllipseGeometry Center="0,0" RadiusX="10" RadiusY="10"/>
                        </GeometryDrawing.Geometry>
                    </GeometryDrawing>
                    <GeometryDrawing Geometry="M10,0 A10,10 90 0 1 0,10"
                                        Pen="{StaticResource arc}"/>
                </DrawingGroup>
            </DrawingImage.Drawing>
        </DrawingImage>
    </FrameworkElement.Resources>

    <Image Source="{DynamicResource ico_spinnerDrawingImage}" Width="100">
        <Image.Triggers>
            <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation From="0" To="360" Duration="0:0:1"
                                            Storyboard.TargetProperty="Source.Drawing.Transform.Angle"
                                            RepeatBehavior="Forever"/>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Image.Triggers>
    </Image>
</StackPanel>

P.S. With such a pen, in my opinion, the animation will look better:

    <Pen x:Key="arc" Thickness="5" EndLineCap="Round" StartLineCap="Round">
        <Pen.Brush>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="Transparent"/>
                <GradientStop Color="LawnGreen" Offset="1"/>
            </LinearGradientBrush>
        </Pen.Brush>
    </Pen>
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • in my case there's no gray circle, only the arc. It was an example. – Willy Nov 21 '22 at 15:50
  • @Rodri , DrawingImage creates a frame based on the actual shape. If you remove the circle, the frame will change and the image will twitch. If the circle is not needed, then you need to set it a transparent brush: `` – EldHasp Nov 21 '22 at 15:59
  • My Drawing.Image is completely different to yours. How can I adapt mine to yours? I need all the things in my Drawing.Image to be respected, i mean, values for ClipGeometry, TranslateTransform and GeometryDrawing. – Willy Nov 21 '22 at 16:14
  • @Rodri You do actually not need anything of that. A Path with a simple PathGeometry is sufficient. – Clemens Nov 21 '22 at 16:29
  • @Rodri , Your geometry and getting an image from it will not work correctly. They create a cropped frame of an incomplete circle. Because of this, when it is rotated, the image will twitch, change size and scale. Whatever it was, you need to create a frame sufficient for a full circle. To do this, in the DrawingGroup you need to place either a full circle, as in my example, or a describing square. Or you need not to create a DrawingImage, but a Path, as in Clement's example. – EldHasp Nov 21 '22 at 16:38
-1

You could try using background workers.

In your xaml:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <ed:Arc Grid.Column="0" x:Name="Arc1" ArcThickness="5" Height="32" Width="32" StartAngle="0" EndAngle="360" Stretch="None" Fill="Gray"/>
    <ed:Arc Grid.Column="0" x:Name="Arc2" ArcThickness="5" Height="32" Width="32" StartAngle="0" EndAngle="45"  Stretch="None" Fill="DeepSkyBlue"/>
</Grid>

Colours & sizes can be changed to your liking.

Add Microsoft.Expression.Drawing DLL to your project.

In your xaml.cs:

Add

using System.Drawing;
using System.Threading;
using System.Threading.Tasks;

Declare a global variable bool runProcess = true;

Then add the event that will trigger your spinner to start moving

private void EventName (object sender, RoutedEventArgs e)
    {
    runProcess = true;
    BackgroundWorker worker = new BackgroundWorker();
    worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
    worker.WorkerReportsProgress = true;
    worker.DoWork += Worker_DoWork;
    worker.ProgressChanged += Worker_ProgressChanged;
    worker.RunWorkerAsync();
    //Function you want to call while the spinner rotates
    }

Add the following two functions to make the spinner move

private void Worker_DoWork (object sender, DoWorkEventArgs e)
    {
    BackgroundWorker worker = sender as BackgroundWorker;
    while ( runProcess )
        {
        Thread.Sleep(50);    //Any value <60 will give you a smooth movement
        worker.ReportProgress(0 /*Dummy value*/);
        }
    }
private void Worker_ProgressChanged (object sender, ProgressChangedEventArgs e)
    {
    Arc2.StartAngle += 10;    //Any value can be chosen for this
    Arc2.EndAngle += 10;      //Just keep the same value for both
    }

Add this function to do whatever need's do be done once the spinner stops

private void Worker_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e)
    {
    //Whatever you want to do after the spinner stops
    }

Then add the function you want to run while your spinner is moving.

ReturnType FunctionName(InputParameters)
    {
    //Whatever your function is supposed to do
    runProcess = false;
    }
New Guy
  • 53
  • 6
  • Thread.Sleep in BackgroundWorker.DoWork. Seems you have reinvented a Timer. Also note that [BackgroundWorker is obsolete](https://stackoverflow.com/questions/12414601/async-await-vs-backgroundworker) since a decade. Besides that, you would certainly only use timer-based animations when the built-in animations won't work for you specific scenario. This is definitely not the case here. – Clemens Nov 18 '22 at 09:02
  • I see. I'm new to programming so didn't know that was obsolete. What I've used here was something that got accepted at work, so I posted it here. Could you tell me why it was phased out? – New Guy Nov 18 '22 at 12:27