I use this solution(in code below) to add multiply buttons on panel. It works ok but it takes too long, when it tries to add a lot of buttons (for an example 40). I want to ask, if any one knows of a better solution for this situation? I was thinking of creating all possible buttons at program start-up, but in this case will start-up take too long, especially if there will be really lot of buttons (this scenario is possible)?
while (data.Read())
{
btnName = Convert.ToString(data["Name"]);
btnColor = (color == string.Empty) ? Convert.ToString(data["Color"]) : color;
categoryId = Convert.ToInt16(data["CategoryId"]);
//both category and article table's contains this data!
if (categoryId == articleCatId || cl == typeof(Category))
{
Button newbtn = new Button();
newbtn.TextAlign = ContentAlignment.MiddleCenter;
newbtn.Click += (sender, e) => method(sender, e);
newbtn.Text = btnName;
newbtn.Name = "button-" + btnName;
newbtn.Height = size;
newbtn.Width = size;
newbtn.Font = new Font("Microsoft Sans Serif", fontH);
newbtn.Location = new Point(paddingL, paddingT);
newbtn.BackColor = ColorTranslator.FromHtml(btnColor);
location.Controls.Add(newbtn);
num += 1;
if ((num - 1) / inline == 1) { paddingT += size; paddingL = 2; num = 1; }
else { paddingL = paddingL + size; }
}
}
You probably cannot reduce the number of buttons you need to create, so you then have some options to speed it up a little bit:
Add the buttons to an object that is not visible. Only when you're done adding buttons, you make the object visible.
Call SuspendLayout on the parent control to stop it from trying to layout itself. Then call ResumeLayout when you're done adding buttons.
Use a more lightweight control than a button, that is more appropriate for the task. For example a Listbox, Combobox, or several checkboxes or option buttons styled as normal buttons.
Write your own lightweight Button control that does exactly what you want but no more.
Related
I am creating dynamically buttons in windows forms by using the code below. I want to have a context menu with different options pop up when a button is being right clicked. I made the context menu strp and it shows up but i cant figure out how to make the different options perform the functions i have written for them. Any idea how i can connect the items to their functions ?
void AddButton(Shape s){
Button b = new Button();
b.Text = s.name;
b.Width = sceneItemsList.Width-6;
b.TabStop = false;
b.FlatStyle = FlatStyle.Flat;
b.BackColor = Color.LightGray;
b.FlatAppearance.BorderColor = Color.Black;
ContextMenuStrip cm = new ContextMenuStrip();
cm.Items.Add("Deselect");
cm.Items.Add("Edit");
if (s is GroupShape)
{
cm.Items.Add("Ungroup");
}
cm.Items.Add("Delete");
b.ContextMenuStrip = cm;
}
Figured it out! This is my code if anyone else is struggling with this. Basically you make a loop that goes through all the opptions and adds a click eventhadler
cm.Items[0].Click += (sender, e) => {
Console.WriteLine("ok");
};
I have a Windows Forms Application that I need to assign an image to all 100 buttons, the problem is, that I need to do it randomly every time... Is there a faster way of doing this?
My first idea was to use the basic method of assigning that image to a variable and then assigning the image to the button:
Bitmap P_Farm = new Bitmap(#"IMAGE PATH.jpeg");
this.button1.Image = P_Farm;
But the problem with that is that I will need to do this for all 100 buttons.
this.button1.Image = P_Farm; // "P_Farm is just the path to the image"
this.button2.Image = P_Farm;
this.button3.Image = P_Farm;
this.button4.Image = P_Farm;
I want to keep my code as dry as possible.
The reason I can't just do it through the "Image" option in the "Properties" window is because eventually I will have a random image for every button on every load of the app. So first it will be
this.button1.Image = Z_Farm;
this.button2.Image = C_Farm;
this.button3.Image = P_Farm;
this.button4.Image = P_Farm;
then
this.button1.Image = P_Farm;
this.button2.Image = P_Farm;
this.button3.Image = Z_Farm;
this.button4.Image = Z_Farm;
I was wondering if it was possible to do something like reading every line in a text file but instead of the line changing with each try, the button changes
int i = 0;
while (true) // Something like this loop but changing not the line, but the button
{
this.button[i].image = P_Farm; // this obviously doesn't work
I++;
}
Hopefully this makes sense
Thanks a lot!
You can also loop through all the controls in your form, find the ones that are buttons and change their image that way. Of course you don't want to change them all. What I usually do is set a number to the Tag property:
foreach (Control control in Controls)
{
if (control is Button theButton && (int)theButton.Tag == 5)
{
theButton.Image = P_Farm;
}
}
This will not work if you have panels with buttons that you want to change too. You will have to write a recursive function that involves all the possible containers in your form such as panels.
If you want to change all the buttons in a container like a panel you would only change your foreach line to something like foreach (Control control in panel.Controls).
In the future, when you decide that not all buttons will have the same image, you could set an image based on the tag property like this:
foreach (Control control in Controls)
{
if (control is Button theButton && (int)theButton.Tag >= 5)
{
switch ((int) theButton.Tag)
{
case 100:
theButton.Image = P_Farm;
break;
case 101:
theButton.Image = Z_Farm;
break;
}
}
}
Because we are assuming that all buttons have an int in their tag property, you should add a number to all buttons, including those that should not change like your Cancel and Ok buttons. Something like a zero to exclude them from the image assignement.
I'm sure that there are better ways. I haven't done WinForms in a while.
I have a form that contains a TableLayoutPanel with various controls and labels in it. One of them is a custom control that inherits from ComboBox that has extra auto-complete behavior (auto-completes on any text rather than just left to right). I didn't write the code for this control, so I'm not super familiar with how it works, but essentially upon clicking on the Combobox, it adds a ListBox below the ComboBox, within the same Panel of the TableLayoutPanel, that covers the normal drop down.
Unfortunately, the TableLayoutPanel prevents the ListBox from being fully visible when added, and only one item is shown. The goal is to get it to look like a normal ComboBox which would drop down to cover any controls below it.
Is there any way to allow a control that is in a TableLayoutPanel to overlap the TableLayoutPanel to get this to work as I want? I want to avoid any controls moving around due to the TableLayoutPanel growing to accommodate the ListBox.
Relevant code from the control:
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.ToLower().Contains(SearchText.ToLower()))
{
listBoxChild.Items.Add(Item);
listBoxChild.SelectedIndex = 0;
}
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Anchor = ((System.Windows.Forms.AnchorStyles)
((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
TheControlToMove.BringToFront();
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
Images:
Desired behavior
Current behavior
Going on the suggestion from TaW, I came up with a tentative solution. This form isn't re-sizable but does auto-size so that it looks ok if the user changes their DPI in Windows.
To resolve this, I moved the control out of the TableLayoutPanel to an arbitrary position in the Parent of the TableLayoutPanel. On form loading, I summed the coordinates of the TableLayoutPanel and an empty panel in the cell that I wanted the control to be located on top of. This worked for my needs but it feels like a kludge.
The better solution is probably to use Control.PointToScreen and Control.PointToClient methods, however I wasn't able to get these methods to give me the correct coordinates.
I'm currently trying to add some custom controls to a panel in winforms.
Every control will be docked and build something like a "list".
Now i'm trying to implement a feature to select/deselect every control.
Its working fine, my problem is that it seems to be very slow sometimes.
Currently its about ~50 custom controls in the panel.
modtable.Click += (s, e) =>
{
foreach (Control m in pnl_ucMods.Controls)
{
if(m is ModTableEntry)
{
if(m != modtable)
{
((ModTableEntry)m).BackColor = SystemColors.Control;
}
else if (m == modtable && m.BackColor == SystemColors.Control)
m.BackColor = SystemColors.ActiveCaption;
else
m.BackColor = SystemColors.Control;
}
}
};
Whenever i click on one of the controls it will change the backcolor, on a second click it will change it back but thats only working if i wait like a second before i click again. If i click to fast, nothing happens and i have to click again.
I understand that winforms is not designed to have tons of controls and i understand that foreach will need some time to loop through all the controls, but maybe someone here has a small idea how to improve the code and maybe solve this problem.
tl;dr
Click on one of the custom controls in the panel will change its backcolor. (Selected)
Every other control will change the backcolor too (deselected)
If the clicked control is already selected, it will deselect.
EDIT:
A small example to test that problem.
Just create a new project, add the code and call it.
private void addPanels()
{
Panel newPanel = new Panel();
newPanel.AutoScroll = true;
newPanel.Dock = DockStyle.Fill;
this.Controls.Add(newPanel);
for (int i = 0; i < 50; i++)
{
Panel childPanel = new Panel();
childPanel.Size = new Size(100, 30);
childPanel.Dock = DockStyle.Top;
childPanel.Click += (s, e) =>
{
foreach (Control p in newPanel.Controls)
{
if (p is Panel)
{
if (p != childPanel)
((Panel)p).BackColor = SystemColors.Control;
else if (p == childPanel && p.BackColor == SystemColors.Control)
p.BackColor = SystemColors.ActiveCaption;
else
p.BackColor = SystemColors.Control;
}
}
};
newPanel.Controls.Add(childPanel);
}
}
Use MouseDown event instead of Click event.
When you click twice too fast, it would be a DoubleClick event and no other Click event will raise.
With your permission, Reza.
I am making an application in winforms which shows a blueprint in a picturebox, and I need to place parts on it programmatically. These parts needs to be clickable (thus they should be a user control), and then fire the corresponding click event (clicking on a part should display information unique to that part). I could say that I want to place custom buttons on my picture. Now, of course, I need only one click event, and change the displayed information according to selection, though I don't know how to "link" this event to each created button.
I have a list of parts right next to the picturebox, and selecting a part should make the associated control to appear on the form (and deselecting it should remove it, or at least make it hidden). At first, I thought I will create one control during design, and make it appear/disappear and relocate it with each selection. The problem is, that the user should be able to select multiple parts, and the program should show all selected parts on the blueprint.
As each blueprint is different, the number of parts cannot be defined in advance. Is it possible, to create multiple instances of the same control on the run? Or is there a workaround?
If you use controls for your picture elements( you do not determine anything from coordinates of mouse click) and each picture element is associated with only one menu control, then I can propose you to use the Tag property to associate the corresponding menu controls:
public Form1()
{
InitializeComponent();
this.CreatePictureRelatedControls();
}
private void CreatePictureRelatedControls()
{
Int32 xPictureControls = 50,
yPictureControls = 50,
xAssociatedControls = 200,
yAssociatedControls = 50,
yMargin = 10;
Int32 controlWidth = 125,
controlHeight = 20;
Int32 controlCount = 3;
// ---------Associated controls-----------------
var associatedControls = new Button[controlCount];
// Loop - creating associated controls
for (int i = 0; i < associatedControls.Length; i++)
{
var associatedButton = new Button()
{
Left = xAssociatedControls,
Top = yAssociatedControls + (i * (controlWidth + yMargin)),
Width = controlWidth,
Height = controlHeight,
Text = String.Format("associated control {0}", i),
Visible = false
};
// Event handler for associated button
associatedButton.Click += (sender, eventArgs) =>
{
MessageBox.Show(((Control)sender).Text, "Associated control clicked");
};
associatedControls[i] = associatedButton;
}
// ----------------- Picture controls ---------------
var pictureControls = new Button[controlCount];
// Loop - creating picture controls
for (int i = 0; i < pictureControls.Length; i++)
{
var pictureButton = new Button()
{
Left = xPictureControls,
Top = yPictureControls + (i * (controlWidth + yMargin)),
Width = controlWidth,
Height = controlHeight,
Text = String.Format("picture part button {0}", i),
// Use of tag property to associate the controls
Tag = associatedControls[i],
Visible = true
};
// Event hadler for picture button
pictureButton.Click += (sender, eventArgs) =>
{
Control senderControl = (Control)sender;
Control associatedControl = (Control)senderControl.Tag;
associatedControl.Visible = !associatedControl.Visible;
};
pictureControls[i] = pictureButton;
}
this.Controls.AddRange(associatedControls);
this.Controls.AddRange(pictureControls);
}
P.S. If you need to associate multiple controls then you can just set Tag property to some collection:
button.Tag = new Control[] {associated[1], associated[3]};