【问题标题】:How to make the contents of a round-cornered border be also round-cornered?如何使圆角边框的内容也圆角?
【发布时间】:2010-09-24 09:21:17
【问题描述】:

我有一个包含 3x3 网格的圆角边框元素。网格的角伸出边界。我该如何解决?我尝试使用 ClipToBounds 但没有得到任何结果。 感谢您的帮助

【问题讨论】:

    标签: wpf


    【解决方案1】:

    这里是Jobi提到的这个thread的亮点

    • 装饰器(即边框)或布局面板(即 Stackpanel)都没有开箱即用的这种行为。
    • ClipToBounds 用于布局。 ClipToBounds 不会阻止元素在其边界之外绘制;它只是防止孩子的布局“溢出”。此外,大多数元素不需要 ClipToBounds=True,因为它们的实现无论如何都不允许其内容的布局溢出。最值得注意的例外是 Canvas。
    • 最后,Border 将圆角视为其布局范围内的绘图。

    这是一个继承自 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;
        }
    

    【讨论】:

    • 这个解决方案在这里使用转换器而不是创建一个新类:stackoverflow.com/questions/5649875/… 注意:我必须嵌套边框对象​​以保留我的彩色边框(背景工作正常,无需嵌套)
    • 您能解释一下您的代码中的逻辑吗?我似乎无法理解_oldClip 的用途以及为什么您选择RadiusXRadiusYthis.CornerRadius.TopLeft - (this.BorderThickness.Left * 0.5)
    • 如果你不想剪裁某些类型的对象(比如我的例子中的Canvas),你可以将此控件添加到OnApplyChildClip()方法中的if语句:&amp;&amp; !(child is Canvas) .
    【解决方案2】:

    纯 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>
    

    更新: 找到了更好的方法来实现相同的结果。您现在也可以将 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>
    

    【讨论】:

    • 要消除第一个示例中圆角后面的伪影(例如,当您使用 Snoop 工具放大它时可以看到),您还必须绑定 BorderThickness(以相同方式)并设置 BorderBrush对白。这将切断边界的边界。
    • 第一个例子更通用,因为它可以是透明的(剪裁形状不必是可见的,有它的背景)。
    • 我也推荐第一个例子,因为它支持透明度
    【解决方案3】:

    正如 Micah 提到的,ClipToBounds 不适用于 Border.ConerRadius

    UIElement.Clip属性,其中Border继承。

    如果你知道边框的确切大小,那么解决方法如下:

    <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>
    

    如果大小是未知的或动态的,则可以使用Converter for Border.Clip。查看解决方案here

    【讨论】:

      【解决方案4】:

      所以我刚刚遇到了这个解决方案,然后进入了 Jobi 提供的 msdn 论坛链接,并花了 20 分钟编写我自己的 ClippingBorder 控件。

      然后我意识到 CornerRadius 属性类型不是双精度数,而是 System.Windows.CornerRaduis 接受 4 个双精度数,每个角一个。

      所以我现在要列出另一个替代解决方案,它很可能会满足大多数将来会偶然发现这篇文章的人的要求......

      假设您有如下所示的 XAML:

      <Border CornerRadius="10">
          <Grid>
              ... your UI ...
          </Grid>
      </Border>
      

      问题是 Grid 元素的背景渗出并显示在圆角之外。确保您的&lt;Grid&gt; 具有透明背景,而不是将相同的画笔分配给&lt;Border&gt; 元素的“背景”属性。不再流血,也不需要一大堆 CustomControl 代码。

      确实,从理论上讲,客户区仍然有可能会越过角落的边缘,但是您可以控制该内容,因此您作为开发人员应该能够有足够的填充,或者确保控件的形状靠近边缘是合适的(在我的情况下,我的按钮是圆形的,所以非常适合放在角落里,没有任何问题)。

      【讨论】:

      • 将我的网格背景设置为透明正是我所需要的。而是将颜色放在边框上:)
      • 我的图像仍然显示在使用此代码的边框之外
      【解决方案5】:

      使用@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);
          }
      }
      

      要对此进行测试,只需编译以下两个示例:

      <!-- 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>
      

      【讨论】:

        【解决方案6】:

        使网格变小或边框变大。使边框元素完全包含网格。

        或者看看你是否可以让网格的背景透明,这样“突出”就不明显了。

        更新:糟糕,没有注意到这是一个 WPF 问题。我对此并不熟悉。这是一般的 HTML/CSS 建议。也许有帮助...

        【讨论】:

        • 但你是对的,这是一个合乎逻辑的答案,它也适用于 WPF 的许多情况。
        【解决方案7】:

        我不喜欢使用自定义控件。而是创建了一个行为。

        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>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-12-30
          • 1970-01-01
          • 2020-01-06
          • 1970-01-01
          • 1970-01-01
          • 2013-05-15
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多