WPF C# Canvas Drawing Line connecting buttons - c#

i am testing out how to draw a line that connects the button to another button upon clicking the the button, i got confuse with the coordinates and the settop setleft, how do they actually work in simple terms. i know that we have to set the X2 Y2 ( subtract of start point with end point ) of the line, but i really confuse at what to subtract and how do i do this .
this is so far what i have tried:
int k = 20;
for (int i = 0; i < 4; i++)
{
Button btn = new Button();
btn.Content = i.ToString();
btn.Height = 20;
btn.Width = 20;
Canvas.SetTop(btn,k); // 20
Canvas.SetLeft(btn, 20); // 10
Canvas1.Children.Add(btn);
btn.PreviewMouseDown += (source, e) =>
{
// No idea how to set X2 , Y2 for the line's end point.
Line line = new Line();
//line.X2 = ;
//line.Y2 = ;
line.Stroke = Brushes.Red;
line.StrokeThickness = 4;
Canvas.SetLeft(line,40); // Suppose this is where the line should start
Canvas.SetTop(line ,40); // for button " 0 " .
Canvas1.Children.Add(line);
};
k += 20;
}
for (int i = 0; i < 4; i++)
{
Button btn2 = new Button();
btn2.Content = i.ToString();
btn2.Height = 20;
btn2.Width = 20;
Canvas.SetTop(btn2, k); // 20
Canvas.SetRight(btn2, 20); // 10
Canvas1.Children.Add(btn2);
btn2.PreviewMouseDown += (source, e) =>
{
//Draw Line to connect here.
};
k += 20;
}
i am trying to draw a line from btn to btn2 .
And also , how do i adjust the buttons to be in the same level, for now the right buttons ( btn2 ) is abit lower than the left buttons ( btn) and i want to draw a line to connect the right buttons to the left buttons upon clicking button 0 , so 0 will draw a line to 0 .

Whilst you can do it by moving the Canvas around, you might be better off using the positions of the Buttons themselves on the Canvas.
Essentially, you need to have two sets of coordinates, your LineStart and LineEnd, which in this case will be obtained from the two different Buttons you're clicking.
You could use Point location = ((Button)source).TransformToAncestor(Canvas1).Transform(new Point(0, 0)) to obtain the location of the button being clicked, relative to the parent Canvas.
You would then need to do a similar thing to find the location of the original object that you had clicked (which you don't seem to be storing). You could then simply draw a line between the two calculated Points by setting the X1, X2, Y1, Y2 values.
Adapting your example, you could do something like this (just to demonstrate):
public partial class MainWindow : Window
{
public Button LastClicked { get; set; }
public MainWindow()
{
InitializeComponent();
InitButtons();
}
public void InitButtons()
{
int k = 20;
for (int i = 0; i < 4; i++)
{
Button btn = new Button();
btn.Content = i.ToString();
btn.Height = 20;
btn.Width = 20;
Canvas.SetTop(btn, k); // 20
Canvas.SetLeft(btn, 20); // 10
Canvas1.Children.Add(btn);
btn.PreviewMouseDown += (source, e) =>
{
if (LastClicked != null)
{
// Get button locations.
Point LastClickedLocation = LastClicked.TransformToAncestor(Canvas1).Transform(new Point(0, 0));
Point ThisClickedLocation = ((Button)source).TransformToAncestor(Canvas1).Transform(new Point(0, 0));
// Stop same side lines.
if (LastClickedLocation.X != ThisClickedLocation.X)
{
Line line = new Line();
line.X1 = LastClickedLocation.X;
line.Y1 = LastClickedLocation.Y + btn.Height / 2; // Button Middle.
line.X2 = ThisClickedLocation.X + btn.Width; // Adjust Left side.
line.Y2 = ThisClickedLocation.Y + btn.Height / 2; // Button Middle.
line.Stroke = Brushes.Red;
line.StrokeThickness = 4;
Canvas1.Children.Add(line);
}
}
LastClicked = (Button)source;
};
k += 20;
}
k = 20; // Reset k, this is why your buttons weren't aligned.
for (int i = 0; i < 4; i++)
{
Button btn2 = new Button();
btn2.Content = i.ToString();
btn2.Height = 20;
btn2.Width = 20;
Canvas.SetTop(btn2, k); // 20
Canvas.SetRight(btn2, 20); // 10
Canvas1.Children.Add(btn2);
btn2.PreviewMouseDown += (source, e) =>
{
if (LastClicked != null)
{
// Get button locations.
Point LastClickedLocation = LastClicked.TransformToAncestor(Canvas1).Transform(new Point(0, 0));
Point ThisClickedLocation = ((Button)source).TransformToAncestor(Canvas1).Transform(new Point(0, 0));
// Stop same side lines.
if (LastClickedLocation.X != ThisClickedLocation.X)
{
Line line = new Line();
line.X1 = LastClickedLocation.X + btn2.Width; // Adjust Left side.
line.Y1 = LastClickedLocation.Y + btn2.Height / 2; // Button Middle.
line.X2 = ThisClickedLocation.X;
line.Y2 = ThisClickedLocation.Y + btn2.Height / 2; // Button Middle.
line.Stroke = Brushes.Red;
line.StrokeThickness = 4;
Canvas1.Children.Add(line);
}
}
LastClicked = (Button)source;
};
k += 20;
}
}
}
I wouldn't recommend using this exact code, as it's not particularly bright about handling Button clicks and deciding when to draw lines, but it should demonstrate the drawing and obtaining coordinates that you're after.

Related

Get the number of a certain Button in stack panel

Hey I would like to create a stack panel with multiple buttons in it and when I press one, it tells me which button it is. Like when I press the second button from the top, I get the number 2 back and so on.
The buttons are created with this loop:
for (int i = 0; i < LBresponse.Items.Count; i++)
{
System.Windows.Controls.Button BTclear = new Button();
BTclear.Content = "Clear";
BTclear.Width = 50;
BTclear.Height = 20;
BTclear.HorizontalAlignment = HorizontalAlignment.Right;
BTclear.Click += Button_Click;
BTclear.IsEnabled = true;
STPresponse.Children.Add(BTclear);
}
I could do it with the location of the button but I hope there is a better solution.
One Idea would be to put the number into the context when the button is created but this would not be pretty.
You could store the number or index somewhere, for example in the Tag property:
for (int i = 0; i<LBresponse.Items.Count; i++)
{
System.Windows.Controls.Button BTclear = new Button();
BTclear.Tag = i;
BTclear.Content = "Clear";
BTclear.Width = 50;
BTclear.Height = 20;
BTclear.HorizontalAlignment = HorizontalAlignment.Right;
BTclear.Click += (ss, ee) =>
{
MessageBox.Show(((Button)ss).Tag.ToString());
};
BTclear.IsEnabled = true;
STPresponse.Children.Add(BTclear);
}

Putting picturebox on picturebox

I have a problem adding picture boxes using Controls class on another picture box that already exists.
when i load the form i don't see any changes because the "coded" pictures are under the main picture (Arena)
Here is my code:
void drawSpikes()
{
PictureBox[] spikes = new PictureBox[Arena.Height / 25 * 2];
int position = 0;
byte wall = 1;
byte spike_count = 0;
for (int i = 0; i < Arena.Height / 25 * 2; i++)
{
spikes[i] = new PictureBox();
}
foreach (var Spike in spikes)
{
if (spike_count == 18) wall = 2;
Spike.Size = new Size(25, 25);
if (wall == 1)
{
Spike.Location = new Point(21, position);
Spike.BackColor = Color.Yellow;
}
if (wall == 2)
{
Spike.Location = new Point(position, 250);
Spike.BackColor = Color.Yellow;
}
if (position == 450) position = 0;
position += 25;
spike_count += 1;
Controls.Add(Spike);
}
}
How can i fix it ?
sorry for the function disconnection (i am new in stack overflow).
You can use the BringToFront() function after adding the picture box to the controls collection:
Controls.Add(Spike);
Spike.BringToFront();

How to get position of a button in an array on mouse clicked

I have the code that creates a grid of buttons from an array. I need to get their possitions in array on mouse clicked event. Any ideas or links to some other post/articles would be very helpful.
The code for creating the grid:
// Creating buttons array for 5x5 grid
Button[] tiles25 = new Button[25];
// Generating 5x5 button grid
void Spawn5x5Grid()
{
// position of the firts tile
int x = 35, y = 55;
// current tile index
int count = 0;
for (int i = 1; i < 6; i++)
{
for (int j = 1; j < 6; j++)
{
// Adding button to the array
tiles25[count] = new Button()
{
Size = new Size(24, 24),
Location = new Point(x, y)
};
// Adding buttons from array to the form
Controls.Add(tiles25[count]);
count++;
x = x + 24;
}
x = 35;
y = y + 24;
}
lblSize.Text = "5 x 5";
currentGrid = Grids.grid5x5;
}
I suggest scanning tiles25 array in the Click event handler
...
Controls.Add(tiles25[count]);
tiles25[count].Click += (o, ee) => {
Button button = o as Button;
int index = Array.IndexOf(tiles25, button);
//TODO: Put relevant code here: "button" clicked which is at "index" position
};
count++;
x = x + 24;
...
Register an event handler for each of your button click events and then extract the Location property from the sender object:
// Generating 5x5 button grid
void Spawn5x5Grid()
{
// position of the firts tile
int x = 35, y = 55;
// current tile index
int count = 0;
for (int i = 1; i < 6; i++)
{
for (int j = 1; j < 6; j++)
{
// Adding button to the array
tiles25[count] = new Button()
{
Size = new Size(24, 24),
Location = new Point(x, y)
};
// Adding buttons from array to the form
Controls.Add(tiles25[count]);
tiles25[count].Click += Tiles25_Click;
count++;
x = x + 24;
}
x = 35;
y = y + 24;
}
lblSize.Text = "5 x 5";
currentGrid = Grids.grid5x5;
}
private void Tiles25_Click(object sender, EventArgs e)
{
var bt = sender as Button;
MessageBox.Show("X = " + bt.Location.X + "; Y = " + bt.Location.Y);
}
You need to set up an event handler for when the button is clicked. Right now, all you're doing is creating the buttons and adding them to the list of controls at a given position. Now, all you have to add is the event handler for the click event!
//...
// Adding button to the array
tiles25[count] = new Button()
{
Size = new Size(24, 24),
Location = new Point(x, y),
};
tiles25[count] += new EventHandler(this.Tile_Click);
//...
void Tile_Click(Object sender, EventArgs e)
{
Button clickedButton = (Button)sender;
//...
}
Then inside of the Tile_Click() event handler, you can use whatever code necessary to get the position with the clickedButton object.
If I'm not wrong VoidWalker, you are trying to get the position(index) of the item in the source array and not the actual position of the button on screen. If the former case is true read on, for the latter we have some good answers above.
What you need to do is to mark each button with an identifier that would be used to infer the position. A simple yet damn efficient approach.
At the time of creating the button:
// Adding button to the array
tiles25[count] = new Button()
{
Size = new Size(24, 24),
Location = new Point(x, y)
};
// Add the current index to the name field of the Button
tiles25[count].Name = "Grid5-Btn" + count.ToString();
// Adding buttons from array to the form
Controls.Add(tiles25[count]);
Then on button click you can simply do
void Tile_Click(Object sender, EventArgs e)
{
Button clickedButton = (Button)sender;
var index = int(clickedButton.Name.Split("Grid5-Btn")[0]);
//...
}
This way you can add multiple pieces of informatio such as the hierarchy of grids on the page. You can exactly pinpoint which element to access without running any loops, which would be the case with Array.IndexOf

Performance issue when adding controls to a panel in C#

I am creating a panel and then adding some labels/buttons to it to form a grid. The issue is that if I add more than say 25x25 items to the panel, there is a terrible performance hit. I can resize the form ok but when I scroll the panel to see all the labels the program lags, the labels/buttons tear or flicker, and sometimes it can make the program unresponsive. I have tried adding the controls to a "DoubleBufferedPanel" that I created. This seems to have no effect. What else could I do? Sorry for such a large code listing. I didn't want to waste anyone's time.
namespace GridTest
{
public partial class Form1 : Form
{
private const int COUNT = 50;
private const int SIZE = 50;
private Button[,] buttons = new Button[COUNT, COUNT];
private GridPanel pnlGrid;
public Form1()
{
InitializeComponent();
pnlGrid = new GridPanel();
pnlGrid.AutoScroll = true;
pnlGrid.Dock = DockStyle.Fill;
pnlGrid.BackColor = Color.Black;
this.Controls.Add(pnlGrid);
}
private void Form1_Load(object sender, EventArgs e)
{
int x = 0;
int y = 0;
int offset = 1;
for (int i = 0; i < COUNT; i++)
{
for (int j = 0; j < COUNT; j++)
{
buttons[i, j] = new Button();
buttons[i, j].Size = new Size(SIZE, SIZE);
buttons[i, j].Location = new Point(x, y);
buttons[i, j].BackColor = Color.White;
pnlGrid.Controls.Add(buttons[i, j]);
x = x + SIZE + offset;
}
x = 0;
y = y + SIZE + offset;
}
}
}
}
Also, the GridPanel class:
namespace GridTest
{
public class GridPanel : Panel
{
public GridPanel()
: base()
{
this.DoubleBuffered = true;
this.ResizeRedraw = false;
}
}
}
If you must add controls at run time, based on some dynamic or changing value, you might want to consider creating an image on the fly, and capturing mouse click events on its picturebox. This would be much quicker and only have one control to draw rather than hundreds. You would lose some button functionality such as the click animation and other automatic properties and events; but you could recreate most of those in the generation of the image.
This is a technique I use to offer users the ability to turn on and off individual devices among a pool of thousands, when the location in a 2-dimensional space matters. If the arrangement of the buttons is unimportant, you might be better offering a list of items in a listview or combobox, or as other answers suggest, a datagridview with button columns.
EDIT:
An example showing how to add a graphic with virtual buttons. Very basic implementation, but hopefully you will get the idea:
First, some initial variables as preferences:
int GraphicWidth = 300;
int GraphicHeight = 100;
int ButtonWidth = 60;
int ButtonHeight = 20;
Font ButtonFont = new Font("Arial", 10F);
Pen ButtonBorderColor = new Pen(Color.Black);
Brush ButtonTextColor = new SolidBrush(Color.Black);
Generating the image:
Bitmap ControlImage = new Bitmap(GraphicWidth, GraphicHeight);
using (Graphics g = Graphics.FromImage(ControlImage))
{
g.Clear(Color.White);
for (int x = 0; x < GraphicWidth; x += ButtonWidth)
for (int y = 0; y < GraphicHeight; y += ButtonHeight)
{
g.DrawRectangle(ButtonBorderColor, x, y, ButtonWidth, ButtonHeight);
string ButtonLabel = ((GraphicWidth / ButtonWidth) * (y / ButtonHeight) + x / ButtonWidth).ToString();
SizeF ButtonLabelSize = g.MeasureString(ButtonLabel, ButtonFont);
g.DrawString(ButtonLabel, ButtonFont, ButtonTextColor, x + (ButtonWidth/2) - (ButtonLabelSize.Width / 2), y + (ButtonHeight/2)-(ButtonLabelSize.Height / 2));
}
}
pictureBox1.Image = ControlImage;
And responding to the Click event of the pictureBox:
// Determine which "button" was clicked
MouseEventArgs em = (MouseEventArgs)e;
Point ClickLocation = new Point(em.X, em.Y);
int ButtonNumber = (GraphicWidth / ButtonWidth) * (ClickLocation.Y / ButtonHeight) + (ClickLocation.X / ButtonWidth);
MessageBox.Show(ButtonNumber.ToString());
I think you won't be able to prevent flickering having 625 buttons (btw, windows) on the panel. If such layout is mandatory for you, try to use the DataGridView, bind it to a fake data source containing the required number of columns and rows and create DataGridViewButtonColumns in it. This should work much more better then your current result ...

why doesnt my panel show all my buttons in my c# application?

my panel in my windows form application doesn't include all the buttons i asked it to. It shows only 1 button, Here is the code
private void AddAlphaButtons()
{
char alphaStart = Char.Parse("A");
char alphaEnd = Char.Parse("Z");
for (char i = alphaStart; i <= alphaEnd; i++)
{
string anchorLetter = i.ToString();
Button Buttonx = new Button();
Buttonx.Name = "button " + anchorLetter;
Buttonx.Text = anchorLetter;
Buttonx.BackColor = Color.DarkSlateBlue;
Buttonx.ForeColor = Color.GreenYellow;
Buttonx.Width = 30;
Buttonx.Height = 30;
this.panelButtons.Controls.Add(Buttonx);
//Buttonx.Click += new System.EventHandler(this.MyButton_Click);
}
}
Aren't they all going to be on the same position?
Try setting Buttonx.Location = new Point(100, 200);
(but with different points for different buttons)
You could use a FlowLayoutPanel, which would take care of the layout for you, or you need to track the locations yourself, or which could look something like this:
private void AddAlphaButtons()
{
char alphaStart = Char.Parse("A");
char alphaEnd = Char.Parse("Z");
int x = 0; // used for location info
int y = 0; // used for location info
for (char i = alphaStart; i <= alphaEnd; i++)
{
string anchorLetter = i.ToString();
Button Buttonx = new Button();
Buttonx.Name = "button " + anchorLetter;
Buttonx.Text = anchorLetter;
Buttonx.BackColor = Color.DarkSlateBlue;
Buttonx.ForeColor = Color.GreenYellow;
Buttonx.Width = 30;
Buttonx.Height = 30;
// set button location
Buttonx.Location = new Point(x, y);
x+=30;
if(x > panel1.Width - 30)
{
x = 30;
y+=30;
}
this.panelButtons.Controls.Add(Buttonx);
//Buttonx.Click += new System.EventHandler(this.MyButton_Click);
}
}

Categories

Resources