【问题标题】:Rotating an Oval and odd mouse problem in Winforms C#在 Winforms C# 中旋转椭圆和奇怪的鼠标问题
【发布时间】:2020-03-28 10:45:11
【问题描述】:

我正在尝试做一些我认为非常简单的事情:围绕其中心旋转一个椭圆。所以我设置了一个简单的程序来尝试这样做。最后,我需要做的是单击椭圆的一部分,然后将鼠标移动到会导致椭圆旋转的方向。但是,目前,我要做的就是右键单击一个表单,在鼠标单击的中心出现一个椭圆形,然后接下来绘制旋转的椭圆形。它有点工作。我之所以这么说是因为我第一次单击时,椭圆形及其旋转的椭圆形恰好出现在正确的位置,两个椭圆形的中心就在我的鼠标指针所在的位置。但是,如果我再次单击表单上的其他位置,椭圆会按预期运行(正常和旋转的椭圆出现,彼此居中),但椭圆出现在表单上完全随机的位置(意味着不在ex和ey co-ords)

以下是代码的相关部分:

//this is declared at the top of the form
        float theAngle = 0;
        Matrix result = new Matrix();
        Point center = new Point(0,0);

//this is declared in the form constructor
ovalGraphic = this.CreateGraphics();

//This is declared in the event handler for the mouseclick
           if (e.Button == MouseButtons.Right)
            {

                ovalGraphic.DrawEllipse(pen2, e.X-50, e.Y-15, 100, 30);
                xUp_lbl.Text = e.X.ToString();
                yUp_lbl.Text = e.Y.ToString();

                center.X = e.X;
                center.Y = e.Y;
                result.RotateAt(theAngle+=10, center);
                ovalGraphic.Transform = result;
                ovalGraphic.DrawEllipse(pen3, e.X - 50, e.Y - 15, 100, 30);

            }

在我第一次单击表单并移动鼠标后,任何人都可以看到椭圆出现在随机位置的任何原因吗?

【问题讨论】:

  • 您犯的大错误是试图在Click 处理程序中绘图。那是行不通的。仅在 Paint 处理程序中绘制。 this.CreateGraphics() 仅对测量屏幕分辨率等有用;它不会绘制到屏幕或持续存在。您应该只在 Click 处理程序中使用 Invalidate
  • 另外,将矩阵也移动到Paint 事件中。另外,您需要处理 Matrix 对象。目前尚不清楚如何计算旋转角度。添加10 不符合您的要求。
  • 嗯,这实际上只是原型代码,它必须进入其他代码,这是......真正的混乱。但是,我不太擅长使用 Windows 图形,所以我还有一些额外的问题:1)我将使用 Invalidate 做什么? 2)我没有 Paint 事件,所以没有“处理程序”。而且不会有,因为我插入的代码没有 Paint 事件处理程序。
  • 在 MouseDown 或 MouseUp 事件中调用 Invalidate()。由于您有这些事件的处理程序,您还可以向 Paint 事件添加一个处理程序,它将提供您需要的 e.Graphics 对象 (PaintEventArgs e)。
  • @Cynon:随机位置不是随机的。由于您缓存了图形对象,因此它是多次应用旋转变换的结果。您没有清除第一个转换,因此第二个转换与第一个“堆叠”。

标签: c# winforms transform


【解决方案1】:

这不是 Windows 窗体绘制的工作方式。形式在绘画时自行决定。调整大小或移动表单或移除顶部的另一个窗口时,可能会发生这种情况。

在表单上绘制的图形是 volatile 的,即当表单重绘自身时,它会通过用背景颜色填充自身来清除内容。在此阶段所有图纸都丢失了,必须重新绘制。

你也可以通过调用Invalidate();来触发重绘

你需要一个类来存储省略号:

public class Ellipse
{
    public Rectangle Rectangle { get; set; }
    public float Angle { get; set; }

    public PointF Center => new PointF(
        Rectangle.Left + 0.5f * Rectangle.Width,
        Rectangle.Top + 0.5f * Rectangle.Height);
}

在表单的顶部声明(字段通常以下划线开头):

private readonly List<Ellipse> _ellipses = new List<Ellipse>();
private float _theAngle = 0.0f;

鼠标点击:

private void Form1_MouseClick(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Right) {
        var ellipse = new Ellipse {
            Rectangle = new Rectangle(e.X - 50, e.Y - 15, 100, 30),
            Angle = _theAngle
        };
        _ellipses.Add(ellipse);
        _theAngle += 30;  // Just for test purpose.
        Invalidate(); // Redraw!
    }
}

那么你必须覆盖OnPaint:

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    foreach (Ellipse ellipse in _ellipses) {
        var matrix = new Matrix();
        matrix.RotateAt(ellipse.Angle, ellipse.Center);
        e.Graphics.Transform = matrix;
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; // Creates smooth lines.
        e.Graphics.DrawEllipse(Pens.Red, ellipse.Rectangle);
    }
}

永远不要自己创建Graphics 对象,而是使用PaintEventArgs e 中提供的对象。

【讨论】:

  • 这个错误经常犯,有没有一个规范的“只在Paint事件中抽奖”的答案?
  • 这里的部分问题是我正在修改现有代码(我没有编写),尽管有一堆使用图片框完成的绘图,但还是有代码中的零 OnPaint 事件。
  • @DourHighArch,我认为每一个可能的问题都至少被问过并回答了上千次。但它们也有非常具体的要求,例如(在这种特定情况下)存储角度等。
  • 您不需要添加绘画事件。只需覆盖已经存在的 Paint 事件处理程序 (OnPaint)。如果您改为在控件上绘图,请派生您自己的控件并覆盖OnPaint,或者使用现有的控件并将绘制事件处理程序添加到Paint 事件。
  • @OlivierJacot-Descombes 非常感谢。你的代码向我解释了很多。我不是新手 C# 编码器,但我从来不需要对图形做任何事情。所以这帮助了很多。假设这里的人如果看到我试图修改的代码就会心脏病发作。 (我被禁止实际修复代码中的东西——比如代码隐藏中有 3-4k 行代码)
【解决方案2】:

您有两个非常不同的问题。一是关于画椭圆。我在此页面上的previous answer 中回答了这个问题。这里我想回答一下如何用鼠标旋转椭圆。

首先,我们必须检测鼠标是否击中椭圆。因此,让我们将此方法添加到另一个答案中的Ellipse 类中。

public bool IsHit(Point point)
{
    // Let's change the coordinates of the point to let the ellipse
    // appear as horizontal and as centered around the origin.
    PointF p = RotatePoint(point, Center, -Angle);
    PointF center = Center;
    p.X -= center.X;
    p.Y -= center.Y;

    // Let's make the ellipse appear as a circle seen from the point.
    p.Y *= (float)Rectangle.Width / Rectangle.Height;
    float radius = 0.5f * Rectangle.Width;

    // We hit if we are inside an ellipse larger by tolerance 
    // but not inside one smaller by tolerance.
    const float tolerance = 3.0f;
    float R = radius + tolerance;
    float r = radius - tolerance;
    float px2 = p.X * p.X;
    float py2 = p.Y * p.Y;
    return px2 + py2 <= R * R && px2 + py2 >= r * r;
}

它使用这个辅助方法

// Adapted from this answer https://stackoverflow.com/a/13695630/880990 by Fraser.
private static PointF RotatePoint(Point pointToRotate, PointF centerPoint, double angleInDegrees)
{
    double angleInRadians = angleInDegrees * (Math.PI / 180);
    double cosTheta = Math.Cos(angleInRadians);
    double sinTheta = Math.Sin(angleInRadians);
    return new PointF {
        X =
            (float)
            (cosTheta * (pointToRotate.X - centerPoint.X) -
            sinTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.X),
        Y =
            (float)
            (sinTheta * (pointToRotate.X - centerPoint.X) +
            cosTheta * (pointToRotate.Y - centerPoint.Y) + centerPoint.Y)
    };
}

我们还添加了这个方法,它告诉我们在女巫角度(相对于椭圆的中心)我们击中了椭圆:

public double HitAngle(Point point)
{
    PointF center = Center;
    return Math.Atan2(point.Y - center.Y, point.X - center.X);
}

现在让我们回到表格。我们还需要表单级别的两个字段:

private Ellipse _hitEllipse;
private double _hitAngle;

在 MouseDown 中,我们检测鼠标是否接触到椭圆。如果是,我们通过在两个新字段中设置初始参数来启动轮换:

private void Form1_MouseDown(object sender, MouseEventArgs e)
{
    foreach (Ellipse ellipse in _ellipses) {
        if (ellipse.IsHit(e.Location)) {
            _hitEllipse = ellipse;
            _hitAngle = ellipse.HitAngle(e.Location);
            Invalidate();
            break;
        }
    }
}

在 MouseMove 中我们执行旋转:

private void Form1_MouseMove(object sender, MouseEventArgs e)
{
    if (_hitEllipse != null) {
        double newHitAngle = _hitEllipse.HitAngle(e.Location);
        double delta = newHitAngle - _hitAngle;
        if (Math.Abs(delta) > 0.0001) {
            _hitEllipse.Angle += (float)(delta * 180.0 / Math.PI);
            _hitAngle = newHitAngle;
            Invalidate();
        }
    }
}

最后,在 MouseUp 中我们停止旋转:

private void Form1_MouseUp(object sender, MouseEventArgs e)
{
    _hitEllipse = null; // Stop rotating the ellipse.
    Invalidate();
}

在现有的OpPaint 方法中,我们用不同的颜色绘制椭圆,这取决于我们是否旋转它。我们在 MouseDown 和 MouseUp 中添加了额外的 Invalidate() 调用,以使更改立即生效。

OnPaint 中,让我们将DrawEllipse 的行替换为这两行:

Pen pen = ellipse == _hitEllipse ? Pens.Red : Pens.Blue;
e.Graphics.DrawEllipse(pen, ellipse.Rectangle);

我们可以通过在表单构造函数中调用DoubleBuffered = true;(在InitializeComponent(); 之后)来减少闪烁。

【讨论】:

  • 这是非常棒的东西。我需要做一些工作来指定您可以单击以进行旋转的区域,并且由于某种原因,当 MouseUp 事件发生时,会创建一个新的椭圆形,但我非常感谢您的帮助和输入!
  • 不要忘记鼠标点击时的if (e.Button == MouseButtons.Right) {。 (我的第一个版本没有)。就像现在一样,您可以单击椭圆线的任何部分来旋转它。但是,您可以替换我的IsHit 代码并测试鼠标是否在椭圆尖端的小圆圈或正方形内。所涉及的计算会更容易。
猜你喜欢
  • 2020-12-04
  • 2017-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多