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 元素或控件裁剪成圓形(橢圓)