I want to create on runtime labels/shapes and after that to connect shapes with line like how you do it in Visio.
With this code I am creating the "block":
private void createBlock() {
try {
Label label = new Label();
label.AutoSize = false;
label.Location = Control.MousePosition;
label.Size = new Size(89, 36);
label.BackColor = Color.DarkOliveGreen;
label.ForeColor = Color.White;
label.FlatStyle = FlatStyle.Flat;
label.TextAlign = ContentAlignment.MiddleCenter;
label.Text = "New Block";
label.ContextMenuStrip = contextBlock;
canvas.Controls.Add(label);
MoveBlock(label);
} catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
With this I am moving the objects in the form:
private void MoveBlock(Label block, Label endBlock=null){
block.MouseDown += (ss, ee) => {
if (ee.Button == System.Windows.Forms.MouseButtons.Left) fPoint = Control.MousePosition;
};
block.MouseMove += (ss, ee) => {
if (ee.Button == System.Windows.Forms.MouseButtons.Left) {
Point temp = Control.MousePosition;
Point res = new Point(fPoint.X - temp.X, fPoint.Y - temp.Y);
block.Location = new Point(block.Location.X - res.X, block.Location.Y - res.Y);
fPoint = temp;
}
};
}
How do I actually do that? At least how to search for it? What is the best way to do this?
Here is a minimal example how you can start by changing and expanding your code a little:
first we create a class level variable to hold the list of connected blocks:
List<Tuple<Label, Label>> lines = new List<Tuple<Label, Label>>();
You may want to use a class of your own instead of the cheap Tuples
to hold more info about the lines, like color, pen style, width etc..
Next we make the createBlock
method return the new Label
, so we can use it directly..
private Label createBlock() <---- for convenience!
{
try
{
Label label = new Label();
label.AutoSize = false;
label.Location = Control.MousePosition;
label.Size = new Size(89, 36);
label.BackColor = Color.DarkOliveGreen;
label.ForeColor = Color.White;
label.FlatStyle = FlatStyle.Flat;
label.TextAlign = ContentAlignment.MiddleCenter;
label.Text = "New Block";
label.ContextMenuStrip = contextBlock;
canvas.Controls.Add(label);
MoveBlock(label);
return label; <---- for convenience!
} catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return null;
}
Here is how I use it in my Form Load
event:
private void Form1_Load(object sender, EventArgs e)
{
Label l1 = createBlock();
Label l2 = createBlock();
lines.Add(new Tuple<Label, Label>(l1,l2));
}
It is probably a good idea to put each block into a List<Label>
or (List<Block>
once you have upgraded them to a class Block
) to access them later, so you don't have to wade through the canvas.Controls
collection..
To make it show the lines, we need to trigger the Paint
event each time a line is added or moved:
private void MoveBlock(Label block, Label endBlock = null)
{
block.MouseDown += (ss, ee) =>
{
if (ee.Button == System.Windows.Forms.MouseButtons.Left)
fPoint = Control.MousePosition;
};
block.MouseMove += (ss, ee) =>
{
if (ee.Button == System.Windows.Forms.MouseButtons.Left)
{
Point temp = Control.MousePosition;
Point res = new Point(fPoint.X - temp.X, fPoint.Y - temp.Y);
block.Location = new Point(block.Location.X - res.X,
block.Location.Y - res.Y);
fPoint = temp;
canvas.Invalidate(); // <------- draw the new lines
}
};
}
I hope your canvas is double buffered (or that canvas is a PictureBox
)!
Here is a simple implementation of drawing the lines:
private void canvas_Paint(object sender, PaintEventArgs e)
{
foreach (Tuple<Label, Label> t in lines)
{
Point p1 = new Point(t.Item1.Left + t.Item1.Width / 2,
t.Item1.Top + t.Item1.Height / 2);
Point p2 = new Point(t.Item2.Left + t.Item2.Width / 2,
t.Item2.Top + t.Item2.Height / 2);
e.Graphics.DrawLine(Pens.Black, p1, p2);
}
}
There are many things to improve but it works and is rather simple. The lines are behind the Labels and as long you don't overcrowd the canvas they look rather natural..
There are many things to learn about Winforms Graphics but they are well beyond the scope of this post..