在面板框中选择绘制的图形 [英] Select drawn figure within panel box
问题描述
我正在处理一个'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 PictureBox
is 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屋!