c# - 旋转和打印正方形

标签 c# wpf algorithm canvas mvvm

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

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

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

所以为了证明:请看下面的草稿。打印根面板。根节点有 4 个子节点。将面板置于根的右侧。该面板将具有属性 attachedToSide='1' 以表示它应该贴在其父面板的一侧。现在由于规则是 0 侧应该粘在父级上,所以我们必须将它翻转 90°。 故事就这样继续下去。

enter image description here

现在,打印本身就没有问题了。我有点挣扎的地方是计算每个方 block 的实际位置。 parent 的第一个 child 很容易,但从那时起,我必须根据前面的面板进行一些计算以正确定位它们,并且我不想走嵌套 if 语句的路线。可能存在一个非常简单的算法来解决这个问题,但由于我不在那个领域,所以我有点挣扎。谁能给我一个正确方向的插入?

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

最佳答案

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

父级打印时 0 面朝下(这是标准的)。它有 3 个 child :右、上、左。现在,父级是我收到 X、Y 坐标的唯一面板。 (X,Y) 是 0 边的中心。另外我得到宽度和高度。对于以后的所有子项,我会得到父项所在的宽度、高度和边。由于子项应始终通过自己的 0 边连接到其父项,因此我可以使用 user3386109 已经显示的 mod-wrapping 公式非常轻松地计算出子项的底边: bottom side child = (bottom side parent + 6 - parents attachment side) % 4

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

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

举个例子:

enter image description here

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

enter image description here

简而言之,它始终是相同的方法:

  • 以父级的 0 侧为中心(我们将存储在每个面板对象中)
  • 相对于那个点,计算 child 的0侧中心在什么地方
  • 如果我们有那个点,计算 child 的左上角点,这样我们就知道从哪里画画了
  • 沿着它的 0 侧中心点旋转 child (我们知道从底部那一侧开始的旋转度数)

完成。一个额外的并发症是每个 child 都会收到一个特定的“偏移”值。简而言之,这是一个正值或负值,表示将子项推向某个方向(仍然依附于父项)。这个问题只要调整好坐标就很容易解决了。

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

    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;
    }

所有这一切的现实生活结果示例:

enter image description here

正如我已经提到的,实际绘图只是通过将 ItemCollection 放在标准网格上、绑定(bind)到矩形集合并在其中设置适当的 ItemsPanel 和 ItemTemplate、标准 WPF 来完成。

关于c# - 旋转和打印正方形,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40306066/

相关文章:

MATLAB 中的图像上采样使用 image() 和 imshow() 生成白色图像

c# - 在 NUnit 测试中创建 App Domain 时出现异常

c# - 强制轴在 Visiblox 图表中使用给定数量的主要刻度线

c# - 需要帮助开发和绑定(bind) Double <--> 转换器

c# - 有没有办法使用 PropertyPath 类获取对象的属性值?

algorithm - 在没有堆栈的情况下评估表达式树

algorithm - 最小排序子数组

C#高精度计时

c# - 无法将枚举扩展方法与泛型一起使用

wpf - 删除 WPF 中 ListView 上的鼠标悬停效果