wpf 如何使圆角边框的内容也圆角?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/324641/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to make the contents of a round-cornered border be also round-cornered?
提问by Gus Cavalcanti
I have a border element with rounded corners containing a 3x3 grid. The corners of the grid are sticking out of the border. How can I fix that? I tried using ClipToBounds but didn't get anywhere. Thanks for your help
我有一个包含 3x3 网格的圆角边框元素。网格的角伸出边界。我该如何解决?我尝试使用 ClipToBounds,但没有成功。谢谢你的帮助
回答by Micah
Here are the highlights of this threadmentioned by Jobi
- None of the decorators (i.e. Border) or layout panels (i.e. Stackpanel) come with this behavior out-of-the-box.
- ClipToBounds is for layout. ClipToBounds does not prevent an element from drawing outside its bounds; it just prevents children's layouts from 'spilling'. Additionally ClipToBounds=True is not needed for most elements because their implementations dont allow their content's layout to spill anyway. The most notable exception is Canvas.
- Finally Border considers the rounded corners to be drawings inside the bounds of its layout.
- 没有任何装饰器(即边框)或布局面板(即 Stackpanel)带有这种开箱即用的行为。
- ClipToBounds 用于布局。ClipToBounds 不会阻止元素在其边界之外绘制;它只是防止儿童的布局“溢出”。此外,大多数元素不需要 ClipToBounds=True ,因为它们的实现无论如何都不允许其内容的布局溢出。最显着的例外是 Canvas。
- 最后,Border 将圆角视为布局边界内的绘图。
Here is an implementation of a class that inherits from Border and implements the proper functionality:
这是一个继承自 Border 并实现适当功能的类的实现:
/// <Remarks>
/// As a side effect ClippingBorder will surpress any databinding or animation of
/// its childs UIElement.Clip property until the child is removed from ClippingBorder
/// </Remarks>
public class ClippingBorder : Border {
protected override void OnRender(DrawingContext dc) {
OnApplyChildClip();
base.OnRender(dc);
}
public override UIElement Child
{
get
{
return base.Child;
}
set
{
if (this.Child != value)
{
if(this.Child != null)
{
// Restore original clipping
this.Child.SetValue(UIElement.ClipProperty, _oldClip);
}
if(value != null)
{
_oldClip = value.ReadLocalValue(UIElement.ClipProperty);
}
else
{
// If we dont set it to null we could leak a Geometry object
_oldClip = null;
}
base.Child = value;
}
}
}
protected virtual void OnApplyChildClip()
{
UIElement child = this.Child;
if(child != null)
{
_clipRect.RadiusX = _clipRect.RadiusY = Math.Max(0.0, this.CornerRadius.TopLeft - (this.BorderThickness.Left * 0.5));
_clipRect.Rect = new Rect(Child.RenderSize);
child.Clip = _clipRect;
}
}
private RectangleGeometry _clipRect = new RectangleGeometry();
private object _oldClip;
}
回答by Andrew Mikhailov
Pure XAML:
纯 XAML:
<Border CornerRadius="30" Background="Green">
<Border.OpacityMask>
<VisualBrush>
<VisualBrush.Visual>
<Border
Background="Black"
SnapsToDevicePixels="True"
CornerRadius="{Binding CornerRadius, RelativeSource={RelativeSource AncestorType=Border}}"
Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=Border}}"
Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Border}}"
/>
</VisualBrush.Visual>
</VisualBrush>
</Border.OpacityMask>
<TextBlock Text="asdas das d asd a sd a sda" />
</Border>
Update:Found a better way to achieve the same result. You can also replace Borderwith any other element now.
更新:找到了实现相同结果的更好方法。您现在还可以用任何其他元素替换Border。
<Grid>
<Grid.OpacityMask>
<VisualBrush Visual="{Binding ElementName=Border1}" />
</Grid.OpacityMask>
<Border x:Name="Border1" CornerRadius="30" Background="Green" />
<TextBlock Text="asdas das d asd a sd a sda" />
</Grid>
回答by Artru
As Micah mentioned ClipToBounds
will not work with Border.ConerRadius
.
正如 Micah 所提到的,它ClipToBounds
不适用于Border.ConerRadius
.
There is UIElement.Clipproperty, which Border
inheritances.
有继承的UIElement.Clip属性Border
。
If you know the exact size of the border, then here is the solution:
如果您知道边框的确切大小,那么这里是解决方案:
<Border Background="Blue" CornerRadius="3" Height="100" Width="100">
<Border.Clip>
<RectangleGeometry RadiusX="3" RadiusY="3" Rect="0,0,100,100"/>
</Border.Clip>
<Grid Background="Green"/>
</Border>
If the size is not known or dynamic then Converter
for Border.Clip
can be used. See the solution here.
如果大小未知或动态,则可以使用Converter
for Border.Clip
。请参阅此处的解决方案。
回答by DXM
So I just came across this solution, then followed into msdn forum link that Jobi provided and spent 20 minutes writing my own ClippingBorder control.
所以我刚刚遇到了这个解决方案,然后进入了 Jobi 提供的 msdn 论坛链接,并花了 20 分钟编写了我自己的 ClippingBorder 控件。
Then I realized that CornerRadius property type is not a double, but System.Windows.CornerRaduis which accepts 4 doubles, one for each corner.
然后我意识到 CornerRadius 属性类型不是双精度型,而是 System.Windows.CornerRaduis 接受 4 个双精度型,每个角一个。
So I'm going to list another alternative solution now, which will most likely satisfy the requirements of most people who will stumble upon this post in the future...
所以我现在要列出另一个替代解决方案,它很可能满足大多数将来偶然发现这篇文章的人的需求......
Let's say you have XAML which looks like this:
假设您有如下所示的 XAML:
<Border CornerRadius="10">
<Grid>
... your UI ...
</Grid>
</Border>
And the problem is that the background for the Grid element bleeds through and shows past the rounded corners. Make sure your <Grid>
has transparent background instead of assigning the same brush to "Background" property of the <Border>
element. No more bleeding past the corners and no need for a whole bunch of CustomControl code.
问题是 Grid 元素的背景渗出并显示超过圆角。确保您<Grid>
具有透明背景,而不是将相同的画笔分配给<Border>
元素的“背景”属性。不再流血过去,也不需要一大堆 CustomControl 代码。
It's true that in theory, client area still have the potential of drawing past the edge of the corner, but you control that content so you as developer should be able to either have enough padding, or make sure the shape of the control next to the edge is appropriate (in my case, my buttons are round, so fit very nicely in the corner without any problems).
确实,理论上,客户区仍然有可能超过角落的边缘,但是您可以控制该内容,因此作为开发人员,您应该能够有足够的填充,或者确保旁边的控件的形状边缘是合适的(在我的例子中,我的按钮是圆形的,所以非常适合放在角落里,没有任何问题)。
回答by James M
Using @Andrew Mikhailov's solution, you can define a simple class, which makes defining a VisualBrush
for each affected element manually unnecessary:
使用@Andrew Mikhailov 的解决方案,您可以定义一个简单的类,这样就VisualBrush
不需要为每个受影响的元素手动定义一个:
public class ClippedBorder : Border
{
public ClippedBorder() : base()
{
var e = new Border()
{
Background = Brushes.Black,
SnapsToDevicePixels = true,
};
e.SetBinding(Border.CornerRadiusProperty, new Binding()
{
Mode = BindingMode.OneWay,
Path = new PropertyPath("CornerRadius"),
Source = this
});
e.SetBinding(Border.HeightProperty, new Binding()
{
Mode = BindingMode.OneWay,
Path = new PropertyPath("ActualHeight"),
Source = this
});
e.SetBinding(Border.WidthProperty, new Binding()
{
Mode = BindingMode.OneWay,
Path = new PropertyPath("ActualWidth"),
Source = this
});
OpacityMask = new VisualBrush(e);
}
}
To test this, just compile the following two samples:
要对此进行测试,只需编译以下两个示例:
<!-- You should see a blue rectangle with rounded corners/no red! -->
<Controls:ClippedBorder
Background="Red"
CornerRadius="10"
Height="425"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="425">
<Border Background="Blue">
</Border>
</Controls:ClippedBorder>
<!-- You should see a blue rectangle with NO rounded corners/still no red! -->
<Border
Background="Red"
CornerRadius="10"
Height="425"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="425">
<Border Background="Blue">
</Border>
</Border>
回答by Der_Meister
I don't like to use a custom control. Created a behavior instead.
我不喜欢使用自定义控件。而是创建了一个行为。
using System.Linq;
using System.Windows;
using System.Windows.Interactivity;
/// <summary>
/// Base class for behaviors that could be used in style.
/// </summary>
/// <typeparam name="TComponent">Component type.</typeparam>
/// <typeparam name="TBehavior">Behavior type.</typeparam>
public class AttachableForStyleBehavior<TComponent, TBehavior> : Behavior<TComponent>
where TComponent : System.Windows.DependencyObject
where TBehavior : AttachableForStyleBehavior<TComponent, TBehavior>, new()
{
#pragma warning disable SA1401 // Field must be private.
/// <summary>
/// IsEnabledForStyle attached property.
/// </summary>
public static DependencyProperty IsEnabledForStyleProperty =
DependencyProperty.RegisterAttached("IsEnabledForStyle", typeof(bool),
typeof(AttachableForStyleBehavior<TComponent, TBehavior>), new FrameworkPropertyMetadata(false, OnIsEnabledForStyleChanged));
#pragma warning restore SA1401
/// <summary>
/// Sets IsEnabledForStyle value for element.
/// </summary>
public static void SetIsEnabledForStyle(UIElement element, bool value)
{
element.SetValue(IsEnabledForStyleProperty, value);
}
/// <summary>
/// Gets IsEnabledForStyle value for element.
/// </summary>
public static bool GetIsEnabledForStyle(UIElement element)
{
return (bool)element.GetValue(IsEnabledForStyleProperty);
}
private static void OnIsEnabledForStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement uie = d as UIElement;
if (uie != null)
{
var behColl = Interaction.GetBehaviors(uie);
var existingBehavior = behColl.FirstOrDefault(b => b.GetType() ==
typeof(TBehavior)) as TBehavior;
if ((bool)e.NewValue == false && existingBehavior != null)
{
behColl.Remove(existingBehavior);
}
else if ((bool)e.NewValue == true && existingBehavior == null)
{
behColl.Add(new TBehavior());
}
}
}
}
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
/// <summary>
/// Behavior that creates opacity mask brush.
/// </summary>
internal class OpacityMaskBehavior : AttachableForStyleBehavior<Border, OpacityMaskBehavior>
{
protected override void OnAttached()
{
base.OnAttached();
var border = new Border()
{
Background = Brushes.Black,
SnapsToDevicePixels = true,
};
border.SetBinding(Border.CornerRadiusProperty, new Binding()
{
Mode = BindingMode.OneWay,
Path = new PropertyPath("CornerRadius"),
Source = AssociatedObject
});
border.SetBinding(FrameworkElement.HeightProperty, new Binding()
{
Mode = BindingMode.OneWay,
Path = new PropertyPath("ActualHeight"),
Source = AssociatedObject
});
border.SetBinding(FrameworkElement.WidthProperty, new Binding()
{
Mode = BindingMode.OneWay,
Path = new PropertyPath("ActualWidth"),
Source = AssociatedObject
});
AssociatedObject.OpacityMask = new VisualBrush(border);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.OpacityMask = null;
}
}
<Style x:Key="BorderWithRoundCornersStyle" TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="50" />
<Setter Property="behaviors:OpacityMaskBehavior.IsEnabledForStyle" Value="True" />
</Style>
回答by Vilx-
Make the grid smaller or the border larger. So that the border element completely contains the grid.
使网格更小或边框更大。这样边框元素就完全包含了网格。
Alternatively see if you can make the grid's background transparent, so that the "sticking out" isn't noticeable.
或者看看是否可以使网格的背景透明,以便“突出”不明显。
Update:Oops, didn't notice this was a WPF question. I'm not familiar with that. This was general HTML/CSS advice. Maybe it helps...
更新:糟糕,没有注意到这是一个 WPF 问题。我对此并不熟悉。这是一般的 HTML/CSS 建议。也许它有帮助...