1. 程式人生 > >WPF 中使用附加屬性,將任意 UI 元素或控件裁剪成圓形(橢圓)

WPF 中使用附加屬性,將任意 UI 元素或控件裁剪成圓形(橢圓)

change exce fill dingo 4.0 pes white 表示 lin

原文:WPF 中使用附加屬性,將任意 UI 元素或控件裁剪成圓形(橢圓)

版權聲明:本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名呂毅(包含鏈接:http://blog.csdn.net/wpwalter/),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。如有任何疑問,請與我聯系([email protected])。 https://blog.csdn.net/WPwalter/article/details/80820818

不知從什麽時候開始,頭像流行使用圓形了,於是各個平臺開始追逐顯示圓形裁剪圖像的技術。WPF 作為一個優秀的 UI 框架,當然有其內建的機制支持這種圓形裁剪。

不過,內建的機制僅支持畫刷,而如果被裁剪的元素支持交互,或者擁有普通畫刷無法達到的顯示效果,那麽就需要本文介紹的更加通用的解決方法了。


UWP 的圓形裁剪請左轉參考:UWP 將圖片裁剪成圓形(橢圓)。

WPF 的 UIElement 提供了 Clip 依賴項屬性,可以使用一個 Geometry 來裁剪任意的 UIElement。由於 Geometry 幾乎可以表示任意形狀,這意味著我們可以才建成任意想要的樣子。

於是,我們可以利用這一點,使用 EllipseGeometry 將任意 UIElement 裁剪成圓形或者橢圓形。比如,寫成下面這樣:

<Grid>
    <Grid.Clip>
        <EllipseGeometry Center="120 180" RadiusX="120" RadiusY="180" />
    </Grid.Clip>
    <Image Source="demo.jpg" Stretch="Fill" />
    <TextBlock Text="https://walterlv.github.io"
Foreground="White" Margin="171,172,51,21"/>
</Grid>

最終可以出現如下的效果。

技術分享圖片

不過,稍微改變下窗口的大小,就會發現裁剪的範圍不對了。因為我們寫死了圓形裁剪的中心點和兩個不同方向的半徑(這裏可不好說是長半軸還是短半軸啊)。

技術分享圖片

我們需要一個可以自動修改裁剪圓形的一種機制,於是,我們想到了 Binding。為了使 XAML 的代碼好看一點,我將 Binding 封裝到了一個單獨的類中處理,使用附加屬性提供 API。

我封裝好的類如下:

/// <summary>
/// 提供將任意控件裁剪為圓形或橢圓的附加屬性。
/// </summary>
public static class EllipseClipper
{
    /// <summary>
    /// 標識 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)
        {
            // 如果 IsClipping 附加屬性被設置為 false,則清除 UIElement.Clip 屬性。
            source.ClearValue(UIElement.ClipProperty);
            return;
        }

        // 如果 UIElement.Clip 屬性被用作其他用途,則拋出異常說明問題所在。
        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.");
        }

        // 使用 UIElement.Clip 屬性。
        ellipse = ellipse ?? new EllipseGeometry();
        source.Clip = ellipse;

        // 使用綁定來根據控件的寬高更新橢圓裁剪範圍。
        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);
    }

    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();
    }

    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();
    }
}

在 XAML 中只需要很簡單的一個屬性賦值即可達到圓形或橢圓形裁剪。

<Grid local:EllipseClipper.IsClipping="True">
    <Image Source="fluentdesign-app-header.jpg" Stretch="Fill" />
    <TextBlock Text="https://walterlv.github.io" Foreground="White" Margin="171,172,51,21"/>
</Grid>

而且才控件的大小改變的時候也能夠正常更新裁剪範圍。

技術分享圖片

這篇博客的核心代碼我也貼在了 StackOverflow 上:c# - WPF displaying a gif in an ellipse - Stack Overflow

WPF 中使用附加屬性,將任意 UI 元素或控件裁剪成圓形(橢圓)