WPF自定义绘图神秘绘图错误

WPF custom drawing mysterious drawing error

我有一个直接绘制到自定义 canvas 的控件。自定义控件根据物理尺寸计算绘制多大的圆。无论我使用 Ellipse XAML 元素还是自定义绘制控件,我看到的行为都是相同的。

基本上,一旦元素的大小变得足够大,大于 canvas 的最小尺寸,它的绘制位置就会偏离剪裁矩形。最终结果是中心点(由不同的控件绘制)是正确的,但是圆被移动了——并且边缘被剪掉了。

示例:

只要我计算的中心和实际的中心在一条直线上,控件就可以正常渲染了。我在任何地方都找不到明确设置剪辑的地方。

我的问题是2折:

大多数情况下都有效,但当圆超过一定大小时就不行了。

圆控制代码:

public class CirclePoint : UIElement
{
    // field
    double radius;

    // additional properties
    MetersPerPixel -- set by container, attached property, affects measure
    RadiusInMeters -- set by application, affects measure
    FillColor -- set by application, affects render
    ObjectColor -- set by applicaiton, affects render
    StrokeThickness -- set by application, affects measure, render
    Location -- center point, set by application, affects layout

    protected override Size MeasureOverride(Size constraint)
    {
        base.MeasureOverride(constraint);

        radius = RadiusInMeters / MetersPerPixel;

        double halfPenWidth = StrokThickness / 2;
        double diameter = 2 * (radius + halfPenWidth);

        return new Size(diameter, diameter);
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        SolidColorBrush fillBrush = // from FillColor, frozen
        SolidColorBrush edgeBrush = // from ObjectColor, frozen
        Pen edgePen = // from edgeBrush, StrokeThickness, frozen

        double halfPenWidth = StrokeThickness / 2;

        drawingContext.DrawEllipse(fillBrush, edgePen,
            new Point(RenderSize.Width / 2, RenderSize.Height / 2),
            radius - halfPenWidth, radius - halfPenWidth);
    }
}

抱歉使用缩写符号,但我正在尝试用相关信息总结样板代码。

自定义面板要复杂得多,因为它负责消除标签冲突等,但相关信息在这里:

public class PhysicalPane : Pane
{
    // Pertinent Properties

    PhysicalArea // affects layout

    protected override Size MeasureOverride(Size constraint)
    {
        Size screen = new Screen(ActualWidth, ActualHeight);
        double metersPerDisplayUnit = // calculation based on screen and other context

        Size newDesiredSize = // from constraint, adjusting for double.Infinity

        foreach(UIElement child in InternalChildren)
        {
            child.Measure(constraint);
            SetMetersPerPixel(child, metersPerDisplayUnit);

            newDesiredSize.Width = Math.Max(newDesiredSize.Width, child.DesiredSize.Width);
            newDesiredSize.Height = Math.Max(newDesiredSize.Height, child.DesiredSize.Height);
        }

        return newDesiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach(UIElement child in InternalChildren)
        {
            Location childLocation = GetLocation(child);
            // LocationToPoint is very well tested.
            Point displayPoint = LocationToPoint(childLocation, PhysicalArea, finalSize);

            displayPoint.X -= child.DesiredSize.Width / 2;
            displayPoint.Y -= child.DesiredSize.Height / 2;

            Rect locationRect = new Rect(displayPoint, child.DesiredSize);

            child.Arrange(locationRect);
        }

        return finalSize;
    }
}

原来圆圈的位置放错了,因为DesiredSize和RenderSize不一样。为了修复我的 CirclePoint 中的位置,我必须像这样更改中心点逻辑:

double xOffset = (DesiredSize.Width - RenderSize.Width) / 2;
double yOffset = (DesiredSize.Width - RenderSize.Width) / 2;

Point center = new Point(xOffset + radius, yOffset + radius);

这个解决方案还有一个问题:

这应该return UIElement 的中心:

Point topLeftCorner = yourEllipse.PointToScreen(new Point(0, 0));//Get the point of the topleft corner of your UIElement
Point center = new Point(topLeftCorner.X + yourEllipse.Width / 2, topLeftCorner.Y + yourEllipse.Height / 2);//The center of your ellipse

事实证明,我的定位问题的答案是,一旦子容器在任何维度上都大于父容器,DesiredSize 和 RenderSize 之间就会存在差异。我不得不像这样调整我的 OnRender 方法:

protected override void OnRender(DrawingContext drawingContext)
{
    base.OnRender(drawingContext);

    SolidColorBrush fillBrush = // from FillColor, frozen
    SolidColorBrush edgeBrush = // from ObjectColor, frozen
    Pen edgePen = // from edgeBrush, StrokeThickness, frozen

    double halfPenWidth = StrokeThickness / 2;
    double xOffset = (DesiredSize.Width - RenderSize.Width) / 2;
    double yOffset = (DesiredSize.Width - RenderSize.Width) / 2;

    Point center = new Point(xOffset + radius, yOffset + radius);

    drawingContext.DrawEllipse(fillBrush, edgePen, center,
        radius - halfPenWidth, radius - halfPenWidth);
}

无论圆相对于父级有多大,都可以在正确的位置绘制圆。

我的剪辑问题是由于布局窗格有 ClipToBounds=True。那完全是另一个问题。