Toby
Toby

Reputation: 13

WPF displaying a gif in an ellipse

I need to display a gif in an ellipse in xaml. I know the NuGet-Package "wpfanimatedgif" but it doesnt work for a ImageBrush.

is there any way to implement this? Here is my Examplecode

<Ellipse Grid.Column="2" Grid.Row="0" 
                         Grid.RowSpan="5" 
                         Height="140" Width="140" 
                         HorizontalAlignment="Right" VerticalAlignment="Top" Margin="15">
                          <Ellipse.Fill>
               <ImageBrush Stretch="Fill" ImageSource="..\Resources\MyAnimatedGif.gif"></ImageBrush>
                         </Ellipse.Fill>
                         <Ellipse.Effect>
                            <DropShadowEffect BlurRadius="8" Direction="300"></DropShadowEffect>
                         </Ellipse.Effect>
</Ellipse>

Upvotes: 1

Views: 375

Answers (1)

walterlv
walterlv

Reputation: 2376

You can use a normal UIElement and set Clip property to clip it into an ellipse. In that case, you can show any UI as an ellipse ignoring how it works. But updating the size of the clipping geometry is not very easy.

So I write an AttachedProperty named EllipseClipper.IsClipping for you. By setting this value to True, you can clip an UIElement into an ellipse. Additional, the EllipseClipper.IsClipping property is a common property that anyone can use it in other situations.

<Grid Grid.Column="2" Grid.Row="0" 
      Grid.RowSpan="5" 
      HorizontalAlignment="Right" VerticalAlignment="Top" Margin="15">
    <Image Stretch="Fill" Height="140" Width="140"
           local:EllipseClipper.IsClipping="True"
           Source="..\Resources\MyAnimatedGif.gif" />
    <Grid.Effect>
        <DropShadowEffect BlurRadius="8" Direction="300"></DropShadowEffect>
    </Grid.Effect>
</Grid>

Because I don't have your image, so I write a simpler sample and run it to view the result. It works correctly.

The clipped image

You can copy and paste the helper code below to introduce the EllipseClipper.IsClipping property into your project.

/// <summary>
/// Provide the ability to clip an UIElement to an ellipse.
/// </summary>
public static class EllipseClipper
{
    /// <summary>
    /// The attached property of IsClipping.
    /// </summary>
    public static readonly DependencyProperty IsClippingProperty = DependencyProperty.RegisterAttached(
        "IsClipping", typeof(bool), typeof(EllipseClipper), new PropertyMetadata(false, OnIsClippingChanged));

    public static void SetIsClipping(DependencyObject element, bool value)
        => element.SetValue(IsClippingProperty, value);

    public static bool GetIsClipping(DependencyObject element)
        => (bool) element.GetValue(IsClippingProperty);

    private static void OnIsClippingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var source = (UIElement) d;
        if (e.NewValue is false)
        {
            // If IsClipping is set to false, clear the Clip property.
            source.ClearValue(UIElement.ClipProperty);
            return;
        }

        // If the Clip property is using for other usage, throw an exception.
        var ellipse = source.Clip as EllipseGeometry;
        if (source.Clip != null && ellipse == null)
        {
            throw new InvalidOperationException(
                $"{typeof(EllipseClipper).FullName}.{IsClippingProperty.Name} " +
                $"is using {source.GetType().FullName}.{UIElement.ClipProperty.Name} " +
                "for clipping, dont use this property manually.");
        }

        // Use the clip property.
        ellipse = ellipse ?? new EllipseGeometry();
        source.Clip = ellipse;

        // Update the clip property by Bindings.
        var xBinding = new Binding(FrameworkElement.ActualWidthProperty.Name)
        {
            Source = source,
            Mode = BindingMode.OneWay,
            Converter = new HalfConverter(),
        };
        var yBinding = new Binding(FrameworkElement.ActualHeightProperty.Name)
        {
            Source = source,
            Mode = BindingMode.OneWay,
            Converter = new HalfConverter(),
        };
        var xyBinding = new MultiBinding
        {
            Converter = new SizeToClipCenterConverter(),
        };
        xyBinding.Bindings.Add(xBinding);
        xyBinding.Bindings.Add(yBinding);
        BindingOperations.SetBinding(ellipse, EllipseGeometry.RadiusXProperty, xBinding);
        BindingOperations.SetBinding(ellipse, EllipseGeometry.RadiusYProperty, yBinding);
        BindingOperations.SetBinding(ellipse, EllipseGeometry.CenterProperty, xyBinding);
    }

    /// <summary>
    /// Convert the size to ellipse center point.
    /// </summary>
    private sealed class SizeToClipCenterConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            => new Point((double) values[0], (double) values[1]);

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            => throw new NotSupportedException();
    }

    /// <summary>
    /// Calculate half of a double so that RadiusX and RadiusY can be calculated correctly.
    /// </summary>
    private sealed class HalfConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            => (double) value / 2;

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            => throw new NotSupportedException();
    }
}

Upvotes: 1

Related Questions