【问题标题】:Canvas with Ellipses in Border with rounded corners圆角边框中带有椭圆的画布
【发布时间】:2017-01-28 01:22:28
【问题描述】:
<Border ClipToBounds="True" CornerRadius=20>
    <Grid>
        <Canvas>
            <Ellipse Fill="Red" Height="100" Width="100"/>
        </Canvas>
        <ContentPresenter/>
    </Grid>
</Border>


如何防止画布内容渲染到Border之外?

UPD:感谢您的回答。找到解决方案: 使用它除了边框。不幸的是,不透明蒙版不适用于 Canvas 几何图形:(

没有装饰器(即边框)或布局面板(即 Stackpanel) 自带这种开箱即用的行为。

ClipToBounds 用于布局。 ClipToBounds 不会阻止元素 免得越界;它只是防止孩子的布局 来自“溢出”。

此外,大多数元素不需要 ClipToBounds=True,因为 他们的实现不允许他们的内容布局溢出 反正。最值得注意的例外是 Canvas。

最后,Border 认为圆角是内部的绘图 其布局的界限。

public class ClippingBorder : Border
    {
        protected override void OnRender(DrawingContext dc)
        {
            OnApplyChildClip();
            base.OnRender(dc);
        }

    public override UIElement Child
    {
        get
        {
            return base.Child;
        }
        set
        {
            if (Child != value)
            {
                if (Child != null)
                {
                    // Restore original clipping
                    Child.SetValue(ClipProperty, _oldClip);
                }

                if (value != null)
                {
                    _oldClip = value.ReadLocalValue(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 = 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;
}

【问题讨论】:

    标签: c# wpf canvas


    【解决方案1】:

    我遇到了类似的问题,在某个地方我发现答案是使用带有 ScrollViewer 的 OpacityMask。请尝试以下操作:

        <Grid>
        <Border ClipToBounds="True" CornerRadius="20"
                Background="Green">
            <Border.OpacityMask>
                <VisualBrush>
                    <VisualBrush.Visual>
                        <Border Background="Green"
                                SnapsToDevicePixels="True"
                                CornerRadius="{Binding CornerRadius, RelativeSource={RelativeSource FindAncestor,AncestorType=Border}}"
                                Width="{Binding ActualWidth,RelativeSource={RelativeSource FindAncestor, AncestorType=Border}}"
                                Height="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor,AncestorType=Border}}"/>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Border.OpacityMask>
            <ScrollViewer>
                <Canvas>
                    <Ellipse Fill="Red" Height="100" Width="100" Canvas.Left="-37" Canvas.Top="-26"/>
                    <ContentPresenter/>
                </Canvas>
            </ScrollViewer>
        </Border>
    </Grid>
    

    希望对您有所帮助。

    【讨论】:

    • ScrollViewer 完全是多余的。删除它并将 Canvas 的 ClipToBounds 属性设置为 true。
    • 感谢您的提示,但这对我没有帮助,不幸的是,我需要在画布上使用不同的颜色和动态形状。不透明蒙版不适用于它。
    【解决方案2】:

    如果你设置Border.ClipToBounds="True",它的内容实际上会被剪裁到它的边界,但是所说的“边界”不考虑Border.CornerRadius - 边界基本上是一个大小来自Border.ActualWidth和@的矩形987654324@(这不仅适用于Border,也适用于派生自UIElement 的任何元素)。

    如果您需要将Border 的内容裁剪为自定义形状(圆角属于该类别),您应该通过Border.Clip 属性指定裁剪几何(正如@Clemens 在他的回答中提到的那样) .

    现在,根据各种条件,您可以提出具有不同复杂性的解决方案,但让我介绍一个通用解决方案。这个想法是根据Border 的大小和圆角半径创建适当的几何图形。为了实现这一点,我们将创建一个IMultiValueConverter 并使用MultiBinding 连接它。这是完整的转换器:

    public class ClipConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var width = (double)values[0];
            var height = (double)values[1];
            if (width == 0 || height == 0) return null;
            var corners = (CornerRadius)values[2];
    
            //First we calculate break points in case corner radii exceed
            //the size of the Border - this is done in a proportional manner
            var topBreak = width * corners.TopLeft / (corners.TopLeft + corners.TopRight);
            var rightBreak = height * corners.TopRight / (corners.TopRight + corners.BottomRight);
            var bottomBreak = width * corners.BottomLeft / (corners.BottomLeft + corners.BottomRight);
            var leftBreak = height * corners.TopLeft / (corners.TopLeft + corners.BottomLeft);
    
            //Let's name interesting points on the path:
            //    0-------1  
            //   /         \
            //  7           2
            //  |           |
            //  6           3
            //   \         /
            //    5-------4
            //We need to take into account the break points
            var p0 = new Point(Math.Min(corners.TopLeft, topBreak), 0);
            var p1 = new Point(Math.Max(width - corners.TopRight, topBreak), 0);
            var p2 = new Point(width, Math.Min(corners.TopRight, rightBreak));
            var p3 = new Point(width, Math.Max(height - corners.BottomRight, rightBreak));
            var p4 = new Point(Math.Max(width - corners.BottomRight, bottomBreak), height);
            var p5 = new Point(Math.Min(corners.BottomLeft, bottomBreak), height);
            var p6 = new Point(0, Math.Max(height - corners.BottomLeft, leftBreak));
            var p7 = new Point(0, Math.Min(corners.TopLeft, leftBreak));
    
            var geometry = new StreamGeometry();
    
            //Draw the geometry using a StreamGeometryContext object
            var context = geometry.Open();
    
            context.BeginFigure(p0, true, true);
            if (p1 != p0)
                context.LineTo(p1, false, false);
            context.ArcTo(p2, new Size(p2.Y, width - p1.X), 90, false, SweepDirection.Clockwise, false, false);
            if (p3 != p2)
                context.LineTo(p3, false, false);
            context.ArcTo(p4, new Size(height - p3.Y, width - p4.X), 90, false, SweepDirection.Clockwise, false, false);
            if (p5 != p4)
                context.LineTo(p5, false, false);
            context.ArcTo(p6, new Size(height - p6.Y, p5.X), 90, false, SweepDirection.Clockwise, false, false);
            if (p7 != p6)
                context.LineTo(p7, false, false);
            context.ArcTo(p0, new Size(p7.Y, p0.X), 90, false, SweepDirection.Clockwise, false, false);
    
            //Close the context so that the geometry can be rendered
            context.Close();
    
            return geometry;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
    

    用法:

    <Border (...)>
        <Border.Resources>
            <local:ClipConverter x:Key="ClipConverter" />
        </Border.Resource>
        <Border.Clip>
            <MultiBinding Converter="{StaticResource ClipConverter}">
                <Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}" />
                <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}" />
                <Binding Path="CornerRadius" RelativeSource="{RelativeSource Self}" />
            </MultiBinding>
        </Border.Clip>
        (...)
    </Border>
    

    【讨论】:

      【解决方案3】:

      如果边框具有固定大小,您可以简单地将其Clip 属性设置为适当的 RectangleGeometry:

      <Border CornerRadius="20" Background="Green">
          <Border.Clip>
              <RectangleGeometry RadiusX="20" RadiusY="20" Rect="0,0,100,50"/>
          </Border.Clip>
          <Grid>
              <Canvas>
                  <Ellipse Fill="Red" Height="100" Width="100"/>
              </Canvas>
              <ContentPresenter/>
          </Grid>
      </Border>
      

      很遗憾,如果没有绑定转换器,您将无法绑定 RectangleGeometry 的 Rect 属性。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-29
        • 2018-12-16
        • 2013-02-07
        • 1970-01-01
        • 1970-01-01
        • 2023-03-30
        • 1970-01-01
        • 2013-10-14
        相关资源
        最近更新 更多