在面板框中选择绘制的图形 [英] Select drawn figure within panel box

查看:123
本文介绍了在面板框中选择绘制的图形的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在处理一个'use-case-diagram-form',其中用户可以选择一个元素和一个方法



只是一个简单的表单。它工作正常,为每个actor元素和每个use-case元素创建一个类。



但不知何故,我无法弄清楚如何选择一个已创建的元素,然后对它进行处理。

>

我所做的课程:

  class Actor 
{
private static int _id;
私人面板_panel;
public string Name {get;私人设置; }
public int X {get;私人设置; }
public int Y {get;私人设置; }

$ b $ public Actor(面板,字符串名,int x,int y)
{
_id ++;
_panel = panel;
名称=名称;
X = x;
Y = y;
}

public void DrawActor()
{
//绘制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);


//角色周围的矩形
graphics.DrawRectangle(pen,(X - 20),(Y - 30),40,80);



//设置字体
var stringFont = new字体(Arial,10);

//测量字符串
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;
私人面板_panel;
private string _name;
private int _x;
private int _y;

public UseCase(面板面板,字符串名称,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);

//设置字体
var stringFont = new字体(Arial,10);

//测量字符串
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);


$ b $ / code $ / pre
$ b $ p Github存储库
https://github.com/JimVercoelen/use-case-helper



谢谢 一旦你学会了如何在winforms中正确地绘制



有很多文章描述它,但是你需要明白你真的有这两个选项:




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

  • code>,它显示在控件中,如 Picturbox 图像 Panel BackgroundImage




选项2最适合缓慢叠加的图形,并且不需要一直纠正。

选项一适用于交互式图形,其中用户将移动很多东西或改变或删除它们。



您也可以通过在位图中缓存绘制的图形来混合选项

自从你开始绘制表面之后,让我们看看你应该如何正确地做到这一点:

$ b $黄金法则:所有绘图都需要在控件的 Paint 事件中完成,或者始终只使用 Paint 事件的 e.Graphics object!



相反,您使用 control.CreateGraphics Graphics 对象C $ C>。这几乎总是错的



上述规则的一个结果是, Paint 事件需要能够绘制用户迄今为止创建的所有对象。因此,您需要具有类级别列表来保存必要的数据: List< ActorClass> List< UseCaseClass> 。然后,它可以做一个

  foreach(ActorList中的ActorClass actor)actor.drawActor(e.Graphics)



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



但如果您不这样做,您绘制的任何东西都将持续存在。



通过运行您现在的代码并执行 Minimize / Maximize 序列来测试。 Poof,所有图纸都没有了。



现在回到您的原始问题:如何选择一个例如演员?

这真的很简单,你可以迭代 ActorList MouseClick 事件(请勿使用点击事件,因为它缺少必要的参数):

  foreach(ActorList中的ActorClass actor)
if(actor.rectangle.Contains e.Location)
{
// do stuff
break;
}

这是一个简单的实现;你可能想要改善它的情况下重叠的对象..



现在你可以做的事情可能会改变矩形的颜色或添加对象的引用一个 currentActor 变量。



每当您对要绘制的东西列表进行更改时,例如添加或删除一个对象或移动它或更改任何(可见)属性,您应通过调用 Invalidate 通过 Paint 事件触发更新。 。

顺便说一句:您在标题中询问了 PictureBox ,但只使用了一个面板在代码中。建议使用 PictureBox ,因为它是双缓冲,并且还将两个位图组合使用,以便使用缓存 Image 和一个 BackgroundImage 也许是一个不错的论文..



就目前为止,我可以看到你的代码缺乏必要的类。当你编写它们时,添加一个Draw例程和一个对你添加的 Label 的引用,或者简单地使用 DrawString 来绘制文本自己..



更新:



查看您的项目后, 最小化更改,以使绘图工作:

  // private graphics graphics; //删除! 

永远尝试缓存图形<

  private void pl_Diagram_Paint(object sender,PaintEventArgs e)
{
pen = new Pen(Color.Black,1);
DrawElements(e.Graphics); //传出'good'对象
// graphics = pl_Diagram.CreateGraphics(); //删除!
}

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

  //演员
if(rb_Actor.Checked)
{
if(eX <= 150)
{
var actor = new Actor(name,eX,eY);
_actors.Add(actor);
pl_Diagram.Invalidate(); //触发绘画事件
// DrawElements();



//用例
if(rb_Use_Cases.Checked)
{
var useCase = new UseCase(name,eX ,eY);
_useCases.Add(useCase);
pl_Diagram.Invalidate(); //触发绘画事件
// DrawElements();

$ / code>

不是直接调用例程,而是触发 Paint 事件,然后它可以将一个好的 Graphics 对象传递给它。

  public void DrawElements(Graphics graphics)
{
foreach(_actors中的var actor)
{
DrawActor(graphics,actor);


foreach(var useCase in _useCases)
{
DrawUseCase(graphics,useCase);
}
}

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

  private void DrawActor(Graphics graphics,Actor actor)

  graphics.DrawEllipse(pen,(useCase.X  -  60 ),(useCase.Y-30),120,60); 

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



为了避免重绘过程中的闪烁,仍建议使用 Picturebox 替换面板。 (或者用双缓冲面板子类替换..)

I am working on an 'use-case-diagram-form' where an user can select an element and a modus

Just a simple form. It al works fine, made a class for each actor element and each use-case element. Both are added in a list after beeing created.

But somehow I just can't figure out how to select a created element and after do something with it.

classes i made:

    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 repository: https://github.com/JimVercoelen/use-case-helper

Thanks

解决方案

Your code has several issues, all of which will go away once you learn how to draw properly in winforms!

There are many posts describing it but what you need to understand that you really have these two options:

  • Draw onto the surface of the control. This is what you do, but you do it all wrong.

  • Draw into a Bitmap which is displayed in the control, like the Picturbox's Image or a Panel's BackgroundImage.

Option two is best for graphics that slowly add up and won't need to be corrected all the time.

Option one is best for interactive graphics, where the user will move things around a lot or change or delete them.

You can also mix the options by caching drawn graphics in a Bitmap when they get too numerous.

Since you started with drawing onto the surface let's see how you should do it correctly:

The Golden Rule: All drawing needs to be done in the control's Paint event or be triggered from there always using only the Paint event's e.Graphics object!

Instead you have created a Graphics object by using control.CreateGraphics. This is almost always wrong.

One consequence of the above rule is that the Paint event needs to be able to draw all objects the user has created so far. So you will need to have class level lists to hold the necessary data: List<ActorClass> and List<UseCaseClass>. Then it can do maybe a

foreach(ActorClass actor in ActorList) actor.drawActor(e.Graphics)

etc.

Yes this fully repainting everything looks like a waste but it won't be a problem until you need to draw several hundreds of object.

But if you don't do it this way, nothing you draw persists.

Test it by running your present code and doing a Minimize/Maximize sequence. Poof, all drawings are gone..

Now back to your original question: How to select an e.g. Actor?

This really gets simple as you can can iterate over the ActorList in the MouseClick event (do not use the Click event, as it lacks the necessary parameters):

foreach (ActorClass actor in ActorList) 
   if (actor.rectangle.Contains e.Location)
      {
         // do stuff
         break;
      }

This is a simple implementation; you may want to refine it for the case of overlapping objects..

Now you could do things like maybe change the color of the rectangle or add a reference to the object in a currentActor variable.

Whenever you have made any changes to your lists of things to draw, like adding or deleting a object or moving it or changing any (visible) properties you should trigger an update via the Paint event by calling Invalidate.

Btw: You asked about a PictureBox in the title but use only a Panel in the code. Using a PictureBoxis recommended as it is doublebuffered and also combines two Bitmaps to let you use both a caching Image and a BackgroundImage with maybe a nice paper..

As far as I can see your code so far lacks the necessary classes. When you write them add a Draw routine and either a reference to the Label you add or simply use DrawString to draw the text yourself..

Update:

After looking at your project, here a the minimal changes to make the drawing work:

// private Graphics graphics; // delete!!

Never try to cache a Graphics object!

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

The same; pass the real Graphics object into the drawing routine instead!

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

Instead of calling the routine directly we trigger the Paint event, which then can pass a good Graphics object to it.

public void DrawElements(Graphics graphics)
{
    foreach (var actor in _actors)
    {
        DrawActor(graphics, actor);
    }

    foreach (var useCase in _useCases)
    {
        DrawUseCase(graphics, useCase);
    }
}

We receive the Graphics object and pass it on..

    private void DrawActor(Graphics  graphics, Actor actor)

and

    graphics.DrawEllipse(pen, (useCase.X - 60), (useCase.Y - 30), 120, 60);

After these few changes the drawing persists.

Replacing the Panel by a Picturebox is still recommended to avoid flicker during the redraw. (Or replace by a double-buffered Panel subclass..)

这篇关于在面板框中选择绘制的图形的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆