ContainerVisual.Transform 的问题

发布于 2024-10-02 06:16:42 字数 2213 浏览 1 评论 0原文

在我的自定义控件中,我有一个 ContainerVisual 对象和一个 DrawingVisual 对象。

我重写 ArrangeOverride 并根据给定的大小和控件的填充计算要绘制的矩形。

之后,我将 ContainerVisual 对象的变换设置为矩形的左上角,以便渲染绘图的方法不必考虑矩形并假设绘图原点位于点 0,0。

这不起作用,并且绘图被移位。相反,如果我设置 DrawingVisual 对象的变换,它就会起作用,并且矩形会按预期的方式显示。

我认为如果我在容器上设置变换,它将自动应用于其下方的视觉效果。是这样吗?

感谢您的帮助

编辑:更新了源代码以显示完整的代码。


class MyControl : Control
{
    private readonly ContainerVisual container = new ContainerVisual();
    private readonly DrawingVisual drawing = new DrawingVisual();
    private Rect rect;

    private void RenderDrawing()
    {
        using (var c = drawing.RenderOpen())
        {
            var p = new Pen(new SolidColorBrush(Colors.Black), 1);

            c.DrawRectangle(null, p, new Rect(0, 0, rect.Width, rect.Height));
        }
    }

    protected override Size ArrangeOverride(Size s)
    {
        var h = Math.Max(0, s.Height - Padding.Top - Padding.Bottom);
        var w = Math.Max(0, s.Width - Padding.Left - Padding.Right);

        var r = new Rect(Padding.Left, Padding.Top, w, h);

        if (rect != r)
        {
            rect = r;

            container.Clip = new RectangleGeometry(rect);
            container.Transform = new TranslateTransform(rect.Left, rect.Top);

            // replace the line above with the following line to make it work
            // drawing.Transform = new TranslateTransform(rect.Left, rect.Top);

            RenderDrawing();
        }
        return s;
    }

    protected override Visual GetVisualChild(int index)
    {
        return container;
    }

    protected override Size MeasureOverride(Size s)
    {
        return new Size();
    }

    protected override int VisualChildrenCount
    {
        get { return 1; }
    }

    public MyControl()
    {
        container.Children.Add(drawing);
        AddVisualChild(container);
    }
}

<Window x:Class="MyApp.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:c="clr-namespace:MyApp"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
    <c:MyControl Padding="20" />
  </Grid>
</Window>

in my custom control i have a ContainerVisual object and a DrawingVisual under it.

I override ArrangeOverride and calculate the rectangle that i want to draw in based on the given size and the control's padding.

after that i set my ContainerVisual object's transform to the upper left corner of the rectangle so that the methods that render the drawing would not have to take account of the rectangle and assume that the drawing origin is at point 0,0.

this does not work, and the drawing is displaced. if instead i set transform of the DrawingVisual object it works and the rectangle is displayed the way it is supposed to be.

i thought that if i set transform on the container, it will automatically be applied to the visuals under it. is that so?

thanks for any help

EDIT: Updated the source code to show complete code.


class MyControl : Control
{
    private readonly ContainerVisual container = new ContainerVisual();
    private readonly DrawingVisual drawing = new DrawingVisual();
    private Rect rect;

    private void RenderDrawing()
    {
        using (var c = drawing.RenderOpen())
        {
            var p = new Pen(new SolidColorBrush(Colors.Black), 1);

            c.DrawRectangle(null, p, new Rect(0, 0, rect.Width, rect.Height));
        }
    }

    protected override Size ArrangeOverride(Size s)
    {
        var h = Math.Max(0, s.Height - Padding.Top - Padding.Bottom);
        var w = Math.Max(0, s.Width - Padding.Left - Padding.Right);

        var r = new Rect(Padding.Left, Padding.Top, w, h);

        if (rect != r)
        {
            rect = r;

            container.Clip = new RectangleGeometry(rect);
            container.Transform = new TranslateTransform(rect.Left, rect.Top);

            // replace the line above with the following line to make it work
            // drawing.Transform = new TranslateTransform(rect.Left, rect.Top);

            RenderDrawing();
        }
        return s;
    }

    protected override Visual GetVisualChild(int index)
    {
        return container;
    }

    protected override Size MeasureOverride(Size s)
    {
        return new Size();
    }

    protected override int VisualChildrenCount
    {
        get { return 1; }
    }

    public MyControl()
    {
        container.Children.Add(drawing);
        AddVisualChild(container);
    }
}

<Window x:Class="MyApp.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:c="clr-namespace:MyApp"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
    <c:MyControl Padding="20" />
  </Grid>
</Window>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

橙味迷妹 2024-10-09 06:16:42

奇怪的剪切行为的解释

现在您已经发布了完整的源代码,我终于能够看到您所看到的内容了。您的问题根本不在于变换:而是在剪辑中!

  • 如果注释掉 container.Clip 赋值语句,则无论将变换放在 container 还是 drawing 上,您都会得到相同的结果
  • container.Clip赋值语句,绘图变换时剪切区域完美居中,但容器变换时剪切区域发生偏移,因此只有矩形的下线和右线可见(并不是所有这些)

发生这种情况的原因是为 container.Clip 指定的几何图形是容器的一部分,因此它受到 container.Transform 的影响,但是not drawing.Transform

通过查看相对于窗口左上角的容器、绘图、矩形和剪辑区域的左上角可以更好地理解这一点:

当您设置绘图上的变换:

  • 容器相对于窗口位于 (0,0)(空变换)
  • 剪辑区域位于 (20,20) 相对于窗口(空变换 + 矩形几何)
  • 绘图位于 ( 20,20) 相对于窗口 (null tr​​ansform + TranslateTransform)
  • 矩形位于 (20,20) 相对于窗口 (null tr​​ansform + TranslateTransform + 0,0)

当您在容器上设置变换时:

  • 容器位于 (20,20) 相对于窗口 (TranslateTransform)
  • 剪辑区域位于 (40,40) 相对于窗口 (TranslateTransform + RectangleGeometry)
  • 绘图位于 (20,20) 相对于窗口 ( TranslateTransform + null 变换)
  • 矩形位于 (20,20) 相对于窗口 (TranslateTransform + null 变换 + 0,0)

所以你的问题不是变换没有发生:而是变换也会移动剪辑区域,因此剪辑区域不再与矩形重合,您只能看到矩形的两侧。

原始代码的答案(保留,因为它有一些有用的解释)

事实上,您发布的代码从未使用“容器”,因此您将看到的只是一个空白屏幕。

在您的实际代码中,您错误地使用了“容器”,导致事件无法以正确的顺序发生,从而导致其 Transform 被拾取并传递到 MIL 层。

请记住,当视觉对象设置了变换时,实际处理该变换的不是视觉对象本身,而是视觉对象的视觉父对象。例如,如果您使用 ReachFramework 将页面渲染到 XPS 或进行命中测试,则最外层 Visual 上的 Transform 将被忽略。

您的理解是正确的:如果您的视觉树是按照所有规则构建的,那么您的变换是在您的“容器”上还是在您的“绘图”上并不重要。

既然您无论如何都在使用 Control,我很好奇为什么您不只是让普通的基于 UIElement 的布局系统来处理您的布局需求。

第一次更新(出于同样的原因保留)

感谢您的代码更正。正如我怀疑的那样:您错误地构建了视觉树。如果您使用 AddVisualChild,您还必须重写 GetVisualChild 和 VisuaChildrenCount。这是因为 Visual 不存储子级列表:这取决于子类(您的类)来执行此操作。发生的情况是:

  • 当您调用 AddVisualChild 时,容器的转换为 null,因此这就是传递给 MILCore 的内容。
  • 稍后,当您更改容器的变换时,它会使用其父指针(在 AddVisualChild 中设置)来指示必须刷新其变换数据。此更新需要使用 GetVisualChild 和 VisualChildrenCount 扫描可视化树的一部分。
  • 由于您没有实现这些方法,这部分更新失败。

您说您是“WPF 新手”。您是否意识到您正在使用 WPF 的一些最低级和深奥的功能,这些功能在最普通的 WPF 应用程序中永远不会使用?相当于开始学习使用机器语言编程。通常,您会使用路径、矩形等模板来实现此目的。有时您可能会进入较低级别并使用 DrawingBrush 和包含 GeometryDrawings 等的 DrawingGroup。但是您几乎永远不会一直深入到 DrawingVisual 和 RenderOpen!您唯一会这样做的时候是当您拥有由数百万个单独项目组成的巨大绘图,因此您希望绕过较高层的所有布局和结构开销以获得绝对最大性能时。

自己操作可视化树(AddVisualChild等)也是一项高级功能。我总是建议刚接触 WPF 的人在最初几个月坚持使用 UIElement 及以上版本,并使用 Control 和模板。我建议他们在绘图中使用 Path 和其他形状子类,并在需要高级绘图效果时使用 VisualBrushes。

希望这有帮助。

Explanation of strange clipping behavior

Now that you have posted your full source code I was finally able to see what you were seeing. Your problem isn't in the transform at all: It is in the clip!

  • If you comment out the container.Clip assignment statement, you get identical results no matter whether you put the transform on container or drawing
  • If you uncommented container.Clip assignment statement, the clipping region is perfectly centered on when the drawing is transformed, but when the container is transformed the clipping area is offset, so that only the lower and right lines of the rectangle were visible (and not all of those)

The reason this occurs is that the geometry specified for container.Clip is part of the container, so it is affected by container.Transform but not drawing.Transform:

This can be better understood by looking at the upper-left corners of the container, drawing, rectangle, and clip area relative to the upper-left corner of the window:

When you set the transform on the drawing:

  • Container is at (0,0) relative to window (null transform)
  • Clip area is at (20,20) relative to window (null transform + RectangleGeometry)
  • Drawing is at (20,20) relative to window (null transform + TranslateTransform)
  • Rectangle is at (20,20) relative to window (null transform + TranslateTransform + 0,0)

When you set the transform on the container:

  • Container is at (20,20) relative to window (TranslateTransform)
  • Clip area is at (40,40) relative to window (TranslateTransform + RectangleGeometry)
  • Drawing is at (20,20) relative to window (TranslateTransform + null transform)
  • Rectangle is at (20,20) relative to window (TranslateTransform + null transform + 0,0)

So your problem isn't that the transform isn't happening: It is that the transform is moving the clip area too, so the clip area no longer coincides with the rectangle and you can only see two sides of the rectangle.

Answer given for original code (retained because it has some useful explanation)

In fact, the code you posted never uses "container" so all you will see is a blank screen.

In your actual code you are using "container" incorrectly, preventing the events from occurring in the correct sequence to cause its Transform to be picked up and passed to the MIL layer.

Remember that when a Visual has a Transform set, it is not the visual itself but that Visual's visual parent that actually handles that transform. For example, if you render a page to XPS using ReachFramework or do hit testing, the Transform on the outermost Visual is ignored.

Your understanding is correct: If your visual tree is built following all the rules, it doesn't matter whether your transform is on your "container" or your "drawing".

Since you are using Control anyway, I'm curious why you don't just let the normal UIElement-based layout system handle your layout needs.

First update (retained for the same reason)

Thanks for the code correction. It is as I suspected: You are building your visual tree incorrectly. If you are using AddVisualChild you also must also override GetVisualChild and VisuaChildrenCount. This is because Visual does not store a list of children: It is up to the subclass (your class) to do this. What is happening is:

  • When you call AddVisualChild the container's transform is null so that is what is passed down to MILCore.
  • Later when you change the container's transform, it uses its parent pointer (that was set in AddVisualChild) to signal that its transform data must be refreshed. This update requires part of the visual tree to be scanned using GetVisualChild and VisualChildrenCount.
  • Since you didn't implement these methods this part of the update fails.

You say you are "new to WPF." Are you aware that you are playing with some of WPF's most low-level and esoteric features, ones that would never be used in a most ordinary WPF applications? It is equivalent to starting to learn programming using machine language. Normally you would use templates with Path, Rectangle, etc for this purpose. Sometimes you might go lower level and use a DrawingBrush with a DrawingGroup containing GeometryDrawings, etc. But you would almost never go all the way down to DrawingVisual and RenderOpen! The only time you would do that is when you have huge drawings consisting of millions of individual items and so you want to bypass all the layout and structure overhead of the higher layers for absolute maximum performance.

Manipulating the visual tree yourself (AddVisualChild, etc) is also an advanced feature. I always recommend people new to WPF stick with UIElement and above for the first few months, using Control with templates. I recommend they use Path and other shape subclasses for their drawings, and use VisualBrushes when advanced drawing effects are needed.

Hope this helps.

审判长 2024-10-09 06:16:42

问题出在container.Clip 上。应该是


container.Clip = new RectangleGeometry(new Rect(0, 0, w, h));

the problem is with the container.Clip. it should be


container.Clip = new RectangleGeometry(new Rect(0, 0, w, h));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文