在面板框中选择绘制的图形

时间:2015-09-05 00:14:36

标签: c# winforms graphics

我正在研究'用例 - 图表形式',用户可以在其中选择元素和模式

enter image description here

只是一个简单的表格。它工作正常,为每个actor元素和每个用例元素创建一个类。在创建之后,两者都被添加到列表中。

但不知怎的,我只是无法弄清楚如何选择一个已创建的元素,并在用它做一些事情之后。

我做的课程:

    class Actor
{
    private static int _id;
    private Panel _panel;
    public string Name { get; private set; }
    public int X { get; private set; }
    public int Y { get; private set; }


    public Actor(Panel panel, string name, int x, int y)
    {
        _id++;
        _panel = panel;
        Name = name;
        X = x;
        Y = y;
    }

    public void DrawActor()
    {
        // draw Actor
        var graphics = _panel.CreateGraphics();
        var pen = new Pen(Color.Black, 2);
        graphics.DrawEllipse(pen, X - 10, Y - 30, 20, 20);
        graphics.DrawLine(pen, X, Y - 10, X, Y + 20);
        graphics.DrawLine(pen, X - 15, Y, X + 15, Y);
        graphics.DrawLine(pen, X, Y + 20, X - 15, Y + 35);
        graphics.DrawLine(pen, X, Y + 20, X + 15, Y + 35);


        // rectangle around actor
        graphics.DrawRectangle(pen, (X - 20), (Y - 30), 40, 80);



        // setup font
        var stringFont = new Font("Arial", 10);

        // measure string
        var textWith = graphics.MeasureString(Name, stringFont).Width;


        // label
        var label = new Label();
        var actorText = (_id < 10 ? "0" : "") + _id.ToString() + "-" + Name;
        label.Text = actorText;
        label.Location = new Point(X - (Convert.ToInt32(textWith)/2), Y + 40);
        label.AutoSize = true;
        label.BorderStyle = BorderStyle.FixedSingle;
        _panel.Controls.Add(label);

    }
    class UseCase
{
    private static int _id;
    private Panel _panel;
    private string _name;
    private int _x;
    private int _y;

    public UseCase(Panel panel, string name, int x, int y)
    {
        _id++;
        _panel = panel;
        _name = name;
        _x = x;
        _y = y;
    }

    public void DrawUseCase()
    {
        var graphics = _panel.CreateGraphics();
        var pen = new Pen(Color.Black, 2);
        graphics.DrawEllipse(pen, _x , _y , 120, 50);

        // setup font
        var stringFont = new Font("Arial", 10);

        // measure string
        var textWith = graphics.MeasureString(_name, stringFont).Width;

        // label
        var label = new Label();
        var useCaseText = (_id < 10 ? "0" : "") + _id.ToString() + "-" + _name;
        label.Text = useCaseText;
        label.Location = new Point(_x - (Convert.ToInt32(textWith) / 2) + 60, _y + 20);
        label.AutoSize = true;
        label.BorderStyle = BorderStyle.FixedSingle;
        _panel.Controls.Add(label);

    }
}

Github存储库: https://github.com/JimVercoelen/use-case-helper

谢谢

1 个答案:

答案 0 :(得分:3)

您的代码有几个问题,一旦您学会如何在winforms中正确正确,所有问题都会消失!

有许多帖子描述了它,但您需要了解的是,您确实有这两个选项:

  • 绘制到控件的表面上。这就是你所做的,但你做错了。

  • 绘制到控件中显示的Bitmap,例如Picturbox&#39; s Image或{{1 } {&#39; s Panel

选项二最适合慢慢加起来并且不需要一直纠正的图形。

选项一最适合交互式图形,用户可以移动很多东西或更改或删除它们。

您还可以通过在BackgroundImage中缓存绘制的图形来混合选项。

由于您开始绘制到曲面上,让我们看看您应该如何正确地进行绘制:

黄金法则:所有绘图都需要在对照的Bitmap事件中完成,或者从那里开始,只使用Paint事件&#39; {{ 1}}对象!

相反,您使用Paint创建了e.Graphics对象。这几乎总是错误

上述规则的一个结果是Graphics事件需要能够绘制到目前为止用户创建的所有对象。因此,您需要拥有类级别列表来保存必要的数据:control.CreateGraphicsPaint。然后它可以做一个

List<ActorClass>

是的,这完全重绘一切看起来像是浪费但它不会成为一个问题,直到你需要绘制数百个对象。

但如果你不这样做,你画的东西都不存在。

通过运行现有代码并执行List<UseCaseClass>序列来测试。噗,所有的图画都没了..

现在回到原来的问题:如何选择例如演员?

这真的很简单,因为你可以迭代foreach(ActorClass actor in ActorList) actor.drawActor(e.Graphics) 事件中的Minimize/Maximize(不要使用ActorList事件,因为它缺少必要的参数):

MouseClick

这是一个简单的实现;您可能希望针对重叠对象的情况对其进行优化..

现在你可以做一些事情,比如改变矩形的颜色或在Click变量中添加对象的引用。

每当您对要绘制的事物列表进行任何更改时,例如添加或删除对象或移动它或更改任何(可见)属性,都应通过调用{{foreach (ActorClass actor in ActorList) if (actor.rectangle.Contains e.Location) { // do stuff break; } 事件触发更新。 1}}。

顺便说一下:您在标题中询问了currentActor,但在代码中仅使用了Paint。建议使用Invalidate,因为它是 doublebuffered ,并且还结合了两个位图,以便您同时使用缓存PictureBox和{{1}也许是一篇好文章..

据我所知,到目前为止您的代码缺少必要的类。当你编写它们时,添加一个Draw例程和对你添加的Panel的引用,或者只是使用PictureBox自己绘制文本..

<强>更新

在查看您的项目之后,这里会进行最小化更改以使绘图工作:

Image

从不尝试缓存BackgroundImage对象!

Label

同样的;将真正的DrawString对象传递给绘图例程!

// private Graphics graphics; // delete!!

我们不是直接调用例程,而是触发Graphics事件,然后可以将一个好的private void pl_Diagram_Paint(object sender, PaintEventArgs e) { pen = new Pen(Color.Black, 1); DrawElements(e.Graphics); // pass out the 'good' object //graphics = pl_Diagram.CreateGraphics(); // delete! } 对象传递给它。

Graphics

我们收到Graphics对象并将其传递给..

    // actor
    if (rb_Actor.Checked)
    {
        if (e.X <= 150)
        {
            var actor = new Actor(name, e.X, e.Y);
            _actors.Add(actor);
            pl_Diagram.Invalidate();  // trigger the paint event
            //DrawElements();
        }
    }

    // use case
    if (rb_Use_Cases.Checked)
    {
        var useCase = new UseCase(name, e.X, e.Y);
        _useCases.Add(useCase);
        pl_Diagram.Invalidate();  // trigger the paint event
        //DrawElements();
    }

Paint

经过这些改动后,绘图仍然存在。

仍建议您使用Graphics替换public void DrawElements(Graphics graphics) { foreach (var actor in _actors) { DrawActor(graphics, actor); } foreach (var useCase in _useCases) { DrawUseCase(graphics, useCase); } } ,以避免在重绘过程​​中出现闪烁现象。 (或者用双缓冲的Panel子类替换。)