【问题标题】:Rotating & printing squares旋转和打印方块
【发布时间】:2016-10-28 13:24:13
【问题描述】:

有一个关于 c# 中的作业 wpf 的快速问题。任务是读入一个 XML 文件,其中包含一个根面板的描述,然后它遵循一个固定的模式,每个面板都有多个子面板,每个子面板可以有多个子模式。很明显。我可以很好地阅读它,并且遍历模型是没有问题的。

问题是:我必须在 wpf 画布上打印这些面板。父子面板的关系如下:

  • 根面板有 X Y 坐标来确定它的起点。其他面板没有
  • 每个面板(包括根)都有宽度和高度(不一定相同)
  • 每个面板(根面板除外)都有一个属性“attachedToSide”,其值介于 0 到 3 之间。该值表示应放置子对象的父对象的哪一侧。
  • 在针对父面板打印面板时,我们应始终将面板的“0”面与父面板相对。

所以来演示一下:看看下面的草稿。打印根面板。根有 4 个孩子。将面板放在根的右侧。该面板将有一个属性 attachToSide='1' 表示它应该贴在其父面板的一侧。现在,由于规则是 0 侧应该坚持到父级,我们必须将其翻转 90°。 故事就这样继续下去。

现在,打印本身没有问题。我有点挣扎的地方是计算每个正方形的实际位置。父母的第一个孩子很容易,但从那时起,我必须根据前面的面板进行一些计算才能正确定位它们,并且我不想走嵌套 if 语句的路线。可能存在一个非常简单的算法来解决这个问题,但由于我不在那个领域,我有点挣扎。任何人都可以在正确的方向上轻推我吗?

细节:也纯粹是做 mvvm(只是为了它),所以代码隐藏中的代码为 0。形状是带有自定义项目面板模板和项目模板的项目集合,我通过将旋转角度绑定到模型中的属性来进行旋转。

【问题讨论】:

  • 将每个面板嵌套在其父面板中并应用相应的变换(按角度旋转并按宽度或高度平移)。
  • 你能分享一下你迄今为止尝试过的东西,包括你的原型 XAML 和Panel 类型吗?
  • 将尝试 Nico 和 user3386109 建议的方法。当我开始工作时,我会在这里抛出一些代码 :) 没什么特别的,不要太兴奋 ;) 此外,每个“面板”只是网格中的一个边框,它真的很基本,但是因为它都是坐着的无论如何,在 itemcollection 中,很容易让它变得更有趣。
  • 那么只有 XML 以及您目前拥有的内容?基本问题可能是父母指的是孩子,但孩子不直接指父母,这意味着直接的数据绑定是有问题的,因为孩子没有足够的信息来更新他们的计算数据。
  • 这根本不是问题所在。请阅读开始的描述。数据绑定和在画布上绘制形状从来都不是问题,这部分已经完美地工作了。

标签: c# wpf algorithm canvas mvvm


【解决方案1】:

user3386109 的回答把我推向了正确的方向,但我得到了一些关于帮助我解决这个问题的额外信息。看看这个例子:

父项以 0 面朝下的方式打印(这是标准的)。它有 3 个孩子:右、上、左。现在,父面板是我收到 X、Y 坐标的唯一面板。 (X,Y) 是 0 边的中心。另外我得到宽度和高度。对于以后的所有孩子,我会得到它所在的父母的宽度、高度和侧面。由于孩子应该始终通过自己的 0 侧连接到其父级,因此我可以使用已经显示的 mod 包装公式 user3386109 很容易地计算孩子的底侧: 底部孩子 =(底部父母 + 6 - 父母附件侧)% 4

这是最简单的部分。现在,一个复杂的问题是每个孩子可以比父母更宽或更小,比父母更高或更低。这可能会使计算我们需要绘制的左上角 (X,Y) 点变得复杂。然而,我一直都知道的一件事是,孩子所附着的父母侧的中心点应该与接触该父母的孩子侧中心的点相同(参见图片上的红线,这将告诉你我的意思)。

现在我使用了以下方法:我决定计算左上角的坐标,假设我可以将孩子画成“直立”,所以底部是 0 边。然后,我会沿着那个点旋转。

举个例子:

注意黑色的父面板。我从 XML 中知道我需要将子面板附加到父面板的第 1 侧。因此,我从自己的 0 侧中心计算父母 1 侧的中心点。我知道这将是孩子 0 面的中心,因为那是我需要将它们连接在一起的地方。然后我计算孩子的左上角(X,Y)坐标,这很简单。之后,我可以沿着它的中心 0 侧点旋转孩子。然后我们得到以下结果,其中父子节点在中心连接,并且子节点也以正确的方式旋转。

简而言之,方法总是一样的:

  • 取父节点 0 边的中心(我们将存储在每个面板对象中)
  • 相对于该点,计算孩子的 0 边中心将在哪里
  • 如果我们有那个点,计算孩子的左上角,这样我们就知道从哪里开始绘制
  • 沿其 0 侧中心点旋转子级(我们知道从底部一侧的旋转度数)

完成。一个额外的复杂因素是每个孩子都收到了一定的“抵消”价值。简而言之,这是一个正值或负值,表示将孩子推向某个方向(仍然依附于父母)。只需调整正确的坐标即可轻松解决此问题。

现在,要计算所有点,很明显这一切都取决于父旋转、自身旋转等。在检查变化时,我得出的结论是,很多公式看起来都非常相似。总的解释需要大量的打字,坦率地说,我不会被打扰。但是:这里的代码根据给定的父矩形、子宽度、它应该在父矩形的哪一侧以及偏移量来创建子矩形。

    private static Rectangle CreateRectangle(string name, float width, float height, int sideOfParent, float offset, Rectangle parent)
    {
        Rectangle rect = new Rectangle() { Name = name, Width = width, Height = height, Offset = offset };
        // Calculate which side should be at the bottom, depending on the bottom side of the parent, 
        // and which side of the parent the new rectangle should be attached to
        rect.BottomSide = (parent.BottomSide + 6 - sideOfParent) % 4;

        // Calculate the bottom mid point of the rectangle
        // If | bottom side parent - bottom side child | = 2, just take over the mid bottom point of the parent
        if (Math.Abs(parent.BottomSide - rect.BottomSide) == 2) { rect.MidBottom = parent.MidBottom; }
        else
        {
            // Alternative cases
            // Formulas for both bottom side parent = 0 or 2 are very similar per bottom side child variation (only plus/minus changes for Y formulas)
            // Formulas for both bottom side parent = 1 or 3 are vary similar per bottom side child variation (only plus/minus changes for X formulas)
            // Therefor, we create a "mutator" 1 / -1 if needed, to multiply one part of the formula with, so that we either add or subtract
            Point parPoint = parent.MidBottom;
            if (parent.BottomSide % 2 == 0)
            {
                // Parent has 0 or 2 at the bottom
                int mutator = (parent.BottomSide == 0) ? 1 : -1;
                switch (rect.BottomSide % 2 == 0)
                {
                    case true: rect.MidBottom = new Point(parPoint.X, parPoint.Y - (mutator * parent.Height)); break;
                    case false:
                        if (rect.BottomSide == 1) rect.MidBottom = new Point(parPoint.X + (parent.Width / 2), parPoint.Y - (mutator * (parent.Height / 2)));
                        else rect.MidBottom = new Point(parPoint.X - (parent.Width / 2), parPoint.Y - (mutator * (parent.Height / 2)));
                        break;
                }
            }
            else
            {
                // Parent has 1 or 3 at the bottom
                int mutator = (parent.BottomSide == 1) ? 1 : -1;
                switch (rect.BottomSide % 2 == 1)
                {
                    case true: rect.MidBottom = new Point(parPoint.X + (mutator * parent.Height), parPoint.Y); break;
                    case false:
                        if (rect.BottomSide == 0) rect.MidBottom = new Point(parPoint.X + (mutator * (parent.Height / 2)), parPoint.Y - (parent.Width / 2));
                        else rect.MidBottom = new Point(parPoint.X + (mutator * (parent.Height / 2)), parPoint.Y + (parent.Width / 2));
                        break;
                }
            }
        }

        return rect;
    }

所有这些的真实生活结果示例:

正如我已经提到的,实际的绘制只是通过将 ItemCollection 放在标准网格上、绑定到矩形集合并在那里设置适当的 ItemsPanel 和 ItemTemplate、标准 WPF 来进行。

【讨论】:

    【解决方案2】:

    每个面板的模型由

    X,Y 坐标 宽、高尺寸 R旋转值(四种选择之一) C 最多四个孩子的名单 A 附在侧面

    旋转值可以编码为:以度为单位的角度,以弧度为单位的角度,或者只是一个介于 0 和 3 之间的数字。我会选择 0 到 3 编码,其中数字代表底部的一侧。所以根面板的旋转值为 0。

    为您提供了一组完整的根面板参数(忽略 A)。对于所有其他面板,您有参数 W、H、C、A,但缺少 X、Y、R。因此,您的任务是计算每个面板的 X、Y、R 以完成模型。

    计算孩子的旋转值

    考虑以下情况,它们显示父母的每个方向的四个可能的孩子:

    图形下方的序列是子 R 值,按子 A 值排序。例如,如果 parentR 为 0,childA 为 0,则 childR 为 2。如果 parentR 为 0,childA 为 1,则 childR 为 1,等等。

    首先要注意的是,每个序列中的第一个数字是父级顶部的数字。第二点要注意的是,数字减 1(随着 childA 的增加),在 0 之后换成 3。

    所以如果你取父母的R值,加上6,减去孩子的A值,然后取模4,你就得到孩子的旋转值:

    childR = (parentR + 6 - childA) % 4;
    

    计算孩子的 Y 值

    请注意,孩子的位置主要取决于孩子的旋转值。如果 childR 为 0,则子级高于父级。如果 childR 为 1,则孩子在右侧,依此类推。

    因此,如果 childR 是奇数,则 child 具有与 parent 相同的 Y 值。如果 childR 为 0,则 childY 是通过孩子身高调整的 parentY。当 childR 为 2 时,childY 是 parentY,通过父宽度(parentR 奇数)或父高度(parentR 偶数)调整。

    这会产生如下所示的 if-else 链:

    if ( childR % 2 )                // odd values, child left or right
        childY = parentY
    else if ( childR == 0 )          // child above
        childY = parentY - childH
    else if ( parentR % 2 )          // odd values, adjust by parent width
        childY = parentY + parentW
    else                             // even values, adjust by parent height
        childY = parentY + parentH   
    

    (我这里假设X,Y坐标代表面板左上角的位置,正Y向下。)


    X 计算类似于 Y 计算。

    因此,您从根开始,为根的孩子计算 X、Y、R,然后为每个孩子的孩子递归计算这些参数。

    这样就完成了您的模型。在视图上显示面板很容易,因为每个面板都有 X、Y、W、H、R。

    【讨论】:

    • 仅适用于“直立”的面板(以下为 0)。例如,对于侧面的面板,您必须进行调整。然后你最终会陷入我实际上想避免的同样的 switch/if-else 混乱中。
    • @TomVanSchaijk 你是对的,我更新了答案。
    【解决方案3】:

    您可以使用递归函数来打印面板的所有子项,然后将所述面板作为参数传递,这样您就可以轻松访问位置、变换等...如下所示:

    public void PrintSelfAndChildren(Panel parent)
    {
        ApplyTransform();
        PrintPanel();
        foreach(var child in parent.children)
        {
            PrintSelfAndChildren(child);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-20
      • 2013-08-17
      相关资源
      最近更新 更多