带圆角的椭圆形画布

时间:2017-01-28 01:22:28

标签: c# wpf canvas

<Border ClipToBounds="True" CornerRadius=20>
    <Grid>
        <Canvas>
            <Ellipse Fill="Red" Height="100" Width="100"/>
        </Canvas>
        <ContentPresenter/>
    </Grid>
</Border>

What i have
如何防止在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;
}

3 个答案:

答案 0 :(得分: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>

Canvas rounded corners

希望它对你有所帮助。

答案 1 :(得分:0)

如果Border具有固定大小,您只需将其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属性。

答案 2 :(得分:0)

如果设置Border.ClipToBounds="True",其内容实际上将被剪切到其边界,但是说&#34;界限&#34;不考虑Border.CornerRadius - 界限基本上是一个源自Border.ActualWidthBorder.ActualHeight的矩形(不仅适用于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>