在 Winforms C# 中旋转椭圆和奇数鼠标问题

Rotating an Oval and odd mouse problem in Winforms C#

我正在尝试做一些我认为非常简单的事情:围绕其中心旋转一个椭圆。所以我设置了一个简单的程序来尝试这样做。最后,我需要做的是单击椭圆的一部分,然后将鼠标移动到一个会导致椭圆旋转的方向。但是,目前,我只想右键单击一个窗体,在鼠标单击发生的中心出现一个椭圆形,然后再绘制旋转的椭圆形。它有点管用。我之所以这么说,是因为第一次单击时,椭圆形及其旋转的椭圆形出现在正确的位置,两个椭圆形的中心就在我的鼠标指针所在的位置。但是,如果我再次单击表单上的其他地方,椭圆会按预期运行(正常和旋转的椭圆出现,彼此居中),但椭圆出现在表单上完全随机的位置(意思是不在e.x 和 e.y 协调)

以下是代码的相关部分:

//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);

            }

谁能看出我第一次单击表格并移动鼠标后椭圆出现在随机位置的原因?

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

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

你也可以调用Invalidate();

触发重绘

您需要 class 来存储省略号:

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 中提供的对象。

你有两个截然不同的问题。一个关于画椭圆。我在本页的 中回答了这个问题。在这里我想回答一下如何用鼠标旋转一个椭圆

首先,我们必须检测鼠标是否击中了椭圆。因此,让我们将此方法添加到其他答案中的 Ellipse class。

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  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(); 之后)来减少闪烁。