Linking Buttons Together - c#

I am trying to create some kind of linkage between some buttons on the form so that when i click a button it highlights all the previous buttons to it [some kind of volume controller]
Imagine it as a volume controller. All these colored buttons will be gray and what i want to achieve is as you click on a button it colorizes all the buttons before it; However IDK what is the best way to make a behavior like this without involving tons of useless codes...

First you will need to add all the buttons to an array then handle it from there
Code
//Create an array of buttons and hook up the Click event of each of them
private Button[] VolumeButtons { get; set; }
public Main()
{
InitializeComponent();
//Assuming that you have 21 buttons as it appears in your picture...
VolumeButtons = new Button[21];
VolumeButtons[0] = button1;
VolumeButtons[1] = button2;
VolumeButtons[2] = button3;
VolumeButtons[3] = button4;
VolumeButtons[4] = button5;
VolumeButtons[5] = button6;
VolumeButtons[6] = button7;
VolumeButtons[7] = button8;
VolumeButtons[8] = button9;
VolumeButtons[9] = button10;
VolumeButtons[10] = button11;
VolumeButtons[11] = button12;
VolumeButtons[12] = button13;
VolumeButtons[13] = button14;
VolumeButtons[14] = button15;
VolumeButtons[15] = button16;
VolumeButtons[16] = button17;
VolumeButtons[17] = button18;
VolumeButtons[18] = button19;
VolumeButtons[19] = button20;
VolumeButtons[20] = button21;
foreach (var volumeButton in VolumeButtons)
volumeButton.Click += VolumeButton_Click;
}
void VolumeButton_Click(object sender, EventArgs e)
{
//Find the index of the clicked button
int index = Array.FindIndex(VolumeButtons, 0, VolumeButtons.Length, button => button == ((Button)sender));
//Set the color of all the previous buttons to Aqua, and all the forward buttons to gray [ you may play with it to match your colors then ]
for (int i = 0; i < VolumeButtons.Length; i++)
VolumeButtons[i].BackColor = i <= index ? Color.Aqua : Color.Gray;
}

Put the buttons in an array
Create a click event that looks up the index n of the button (sender) in the array and sets the style of each button 0 - n appropriately.
Wire up each button to the click event
Do as much as you can on your own and come back with specific questions as necessary.

Related

Expander menu in C #

I am trying to reproduce the operation of the Control Expander WPF, or as shown in the menu of Outlook, Vertical Web Menu etc., since in WindowsForms this control does not exist. Here I leave the sample code: Menu_Expader.zip link GoogleDrive.
I have managed to do it using the following controls:
Panels
FlowLayoutPanel
1 Time Control
Button Vectors
Labels Vectors ...
This works perfectly, but it happens that to each panel I must establish a
Maximum Size and Minimum Size therefore every time I add an item inside I must modify the size of the panel where I add it, and the item are very close to each other is a bit annoying for the user's vision.
Example this is what I currently have:
EDIT
Code Sample:
// The state of an expanding or collapsing panel.
private enum ExpandState
{
Expanded,
Expanding,
Collapsing,
Collapsed,
}
// The expanding panels' current states.
private ExpandState[] ExpandStates;
// The Panels to expand and collapse.
private Panel[] ExpandPanels;
// The expand/collapse buttons.
private Button[] ExpandButtons;
// Initialize.
private void Form1_Load(object sender, EventArgs e)
{
// Initialize the arrays.
ExpandStates = new ExpandState[]
{
ExpandState.Expanded,
ExpandState.Expanded,
ExpandState.Expanded,
};
ExpandPanels = new Panel[]
{
panModule1,
panModule2,
panModule3,
};
ExpandButtons = new Button[]
{
btnExpand1,
btnExpand2,
btnExpand3,
};
// Set expander button Tag properties to give indexes
// into these arrays and display expanded images.
for (int i = 0; i < ExpandButtons.Length; i++)
{
ExpandButtons[i].Tag = i;
ExpandButtons[i].Image = Properties.Resources.expander_down;
}
}
// Start expanding.
private void btnExpander_Click(object sender, EventArgs e)
{
// Get the button.
Button btn = sender as Button;
int index = (int)btn.Tag;
// Get this panel's current expand
// state and set its new state.
ExpandState old_state = ExpandStates[index];
if ((old_state == ExpandState.Collapsed) ||
(old_state == ExpandState.Collapsing))
{
// Was collapsed/collapsing. Start expanding.
ExpandStates[index] = ExpandState.Expanding;
ExpandButtons[index].Image = Properties.Resources.expander_up;
}
else
{
// Was expanded/expanding. Start collapsing.
ExpandStates[index] = ExpandState.Collapsing;
ExpandButtons[index].Image = Properties.Resources.expander_down;
}
// Make sure the timer is enabled.
tmrExpand.Enabled = true;
}
// The number of pixels expanded per timer Tick.
private const int ExpansionPerTick = 7;
// Expand or collapse any panels that need it.
private void tmrExpand_Tick(object sender, EventArgs e)
{
// Determines whether we need more adjustments.
bool not_done = false;
for (int i = 0; i < ExpandPanels.Length; i++)
{
// See if this panel needs adjustment.
if (ExpandStates[i] == ExpandState.Expanding)
{
// Expand.
Panel pan = ExpandPanels[i];
int new_height = pan.Height + ExpansionPerTick;
if (new_height >= pan.MaximumSize.Height)
{
// This one is done.
new_height = pan.MaximumSize.Height;
}
else
{
// This one is not done.
not_done = true;
}
// Set the new height.
pan.Height = new_height;
}
else if (ExpandStates[i] == ExpandState.Collapsing)
{
// Collapse.
Panel pan = ExpandPanels[i];
int new_height = pan.Height - ExpansionPerTick;
if (new_height <= pan.MinimumSize.Height)
{
// This one is done.
new_height = pan.MinimumSize.Height;
}
else
{
// This one is not done.
not_done = true;
}
// Set the new height.
pan.Height = new_height;
}
}
// If we are done, disable the timer.
tmrExpand.Enabled = not_done;
}
I want to get a result similar to this - Bootstrap Menu Accordion:
 
Imitate that operation panels expand according to the quantity of item that it contains as long as it does not protrude from the screen, in which case it will show the scroll bar. I know there are software that provide custom controls like DVexpress, DotNetBar Suite among others, but they are Licensed Software I do not want to use it illegally pirate. Can you help me optimize it or create it in another way?
Environment: Visual Studio 2010 & .NET NetFramework 4.
The original question I made it in StackOverFlow in Spanish.
Modulo (Module)
Menu Principal (Main menu)
Mantenimientos (Maintenance)
Procesos (Processes)
Consultas (Queries)
Reportes (Reports)
Note: If someone speaks Spanish and English and can do a better translation, please edit the question. (Excuse the advertising on the image, I recorded the screen with a software trial version).

Dinamically generate a button to open file dialog and select a file using WPF controls and C#

Basically I'm making a program that allows you to add to a stackpanel another stackpanel with several horizontally aligned textboxes with the press of a button. So far, everything is working as intented. Here's my code so far ,Stacker is the name of the parent stackpanel and it starts off empty:
private void Add_Click(object sender, RoutedEventArgs e)
{
Stacker.Children.Add(NewXD(Stacker.Children.Count + 1));
}
public System.Windows.Controls.StackPanel NewXD(int XD)
{
System.Windows.Controls.StackPanel NewP = new StackPanel();
NewP.Orientation = Orientation.Horizontal;
System.Windows.Controls.TextBox HAHA = new TextBox();
HAHA.Name = "question" + XD.ToString();
//HAHA.Text = HAHA.Height.ToString()+" "+HAHA.Width.ToString();
HAHA.Height = Double.NaN;
HAHA.Width = 120;
HAHA.FontSize=20;
NewP.Children.Add(HAHA);
for (int i = 1; i < 6; i++)
{
System.Windows.Controls.TextBox newBox = new TextBox();
newBox.Name = "answer"+XD.ToString()+"_"+i.ToString();
newBox.Height = Double.NaN;
newBox.Width = 120;
NewP.Children.Add(newBox);
}
System.Windows.Controls.ComboBox correct = new ComboBox();
correct.Name = "correct" + XD.ToString();
for (int i = 1; i < 6; i++)
{
System.Windows.Controls.ComboBoxItem newItem = new ComboBoxItem();
newItem.Name = "ans" + XD.ToString() + "_" + i.ToString();
newItem.Content = i.ToString();
correct.Items.Add(newItem);
}
NewP.Children.Add(correct);
return NewP;
}
I apologize for the lack of seriousness in some of my code.
Now, what I need to do is for the child stackpanels to also contain independent file pickers that work like the one sampled in this thread: Open file dialog and select a file using WPF controls and C#
What I don't know how to perform is that each of these generated buttons have the same basic funcion but are linked with each of their corresponding textbox.
Thanks in advance :)
Edit: As I was writing this it occured to me that perhaps I could use the help of the child stackpanel's array-like properties to choose the corresponding textbox, because the file selector's textbox and button will always be the last two items in the stackpanel, but I'm not very sure how to perform this.
For functionality you can create an EventHandler that will be assigned to each button. Your event handler will then open File Dialog...
Buttons have Tag property which you could use to identify your TextBoxes, or you could derive from Button class and add AssociatedTextBox property for example.

Referencing a dynamically created control in a new TabPage

newbie programmer here after hours of searching has left me stumped.
I'm having trouble with referencing a control inside a tab created at RunTime with a button press. Basically what I have is a tabletop RPG calculator, using a Windows Form, that has a tabControl holding tab pages, with each tab page holding user-inputted stats for that individual enemy to be used in calculations.
The problem is that I want the user to be able to click a button to generate a new enemy tab page. Here is my code for generating an enemy tab page with a TextBox.
int enemyNumber = 0;
// Creates a new Enemy Tab
private void button2_Click_1(object sender, EventArgs e)
{
// Create a new TabPage
var newTabPage = new TabPage()
{
Text = "Enemy " + enemyNumber,
};
// Add Enemy Name Box
var newEnemyNameBox = new TextBox()
{
Name = "enemyNameBox" + enemyNumber,
Text = "",
Location = new Point(127, 11),
Size = new Size(133, 20)
};
// Add the controls to the new Enemy tab
newTabPage.Controls.Add(newEnemyNameBox);
// Add the TabPage to the TabControl
tabControl1.TabPages.Add(newTabPage);
// Increases the enemy's "reference number" by 1
// So that enemy tabs will be generated in order enemyTab0, enemyTab1, etc.
enemyNumber += 1;
}
This all works nicely. Unfortunately, after this point things have gotten ugly. I need to reference that TextBox named "enemyNameBox" + enemyNumber, and I'm not sure how to do so.
What I did was create "archVariables" to store the values from whatever enemy tab is selected, then use the appropriate archVariable in the program's calculations. IE: archEnemyName. The idea is that whatever tab the user is currently selected on (determined via SelectedIndex) the TextBox from that page will be used for the program's output.
Here are the two things I've tried after researching the matter:
// Attempt 1
private void defendCalcButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < tabControl1.SelectedIndex; i++)
{
archEnemyNameBox = ((TextBox)Controls["enemyNameBox" + i]).Text;
}
}
This code simply throws a NullReferenceException when I press the button. So after researching more I tried this:
// Attempt 2
private void defendCalcButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < tabControl1.SelectedIndex; i++)
{
TextBox tb2 = new TextBox();
tb2 = ((TextBox)(enemyTab.Controls.Find("enemyNameBox" + i, true)));
archEnemyNameBox = tb2.Text;
}
}
This time I got an Error: Cannot convert type 'System.Windows.Forms.Control[]' to 'System.Windows.Forms.TextBox'
I feel like the second method I have here is probably closer to the correct way to do this, but apparently I'm still not getting it right. I've learned a lot by searching the information on stackoverflow and msdn.microsoft but nothing has gotten me past this problem.
Any help would be appreciated.
basically the problem with your second attemp is that enemyTab.Controls.Find("enemyNameBox" + i, true) returns an array of Controls Control[] and you're trying to convert that to a Control here is the problem, you should get the first control in that array and then convert it to a Control so it should be like this:
private void defendCalcButton_Click(object sender, EventArgs e)
{
for (int i = 0; i < tabControl1.SelectedIndex; i++)
{
TextBox tb2 = new TextBox();
tb2 = ((TextBox)(enemyTab.Controls.Find("enemyNameBox" + i, true)[0]));
archEnemyNameBox = tb2.Text;
}
}
but it is not the BestWay to do so it seems that everytime a user adds a new tabPage it will have the same Controls right? so why not create an userControl with any Control you have on your TabPage? so when you press the user press to add a new tab your code should be like so:
private void CreateNewEnemyTab()
{
var newTabPage = new TabPage()
{
Text = "Enemy " + enemyNumber,
};
EnemyTabUserControl enemyTab = new EnemyTabUserControl(enemyNumber);
here the EnemyTabUserControl should have all the components you need;
newTabPage.Controls.Add(enemyTab);
tabControl1.TabPages.Add(newTabPage);
}
and the code to bring the TextBox from the current tab could be as follow (you are going to need to reference LINQ)
using System.Linq;
//First Lets create this property, it should return the selected EnemyTabUserControl inside the tabControl
public EnemyTabUserControl CurrentTab {
get {
return tabControl1.SelectedTab.Controls.OfType<EnemyTabUserControl>().First();
}
}
// then if we make the textbox you want to reference from outside the code we can do this
CurrentTab.NameOfTheTextBox;
Patrick has solved your fundamental problem, but I don't think you need the loop in there at all. Here I've broken the steps out so you can see what needs to happen a little better:
private void defendCalcButton_Click(object sender, EventArgs e)
{
Control[] matches = this.Controls.Find("enemyNameBox" + tabControl1.SelectedIndex.ToString(), true);
if (matches.Length > 0 && matches[0] is TextBox)
{
TextBox tb = (TextBox)matches[0];
archEnemyNameBox = tb.Text;
}
}

C# Dynamically added button click is throwing argument out of bounds exception

Basically I am trying to create an attachment window utilizing keeping everything in lists for easy use later. So, every time the form loads it goes through everything in the list and creates both labels and buttons for them. There is no errors until I click my button. If I click any of the X buttons, I get an argument out of bounds exception on the click += line. What's interesting is why its being called? The click isn't supposed to add another event handler to itself. Its also interesting that on click the indicie is one greater than the total count so how its even executing that line is beside me considering it should never iterate higher that its max count. Any help would be greatly appreciated.
ArrayList attachmentFiles;
ArrayList attachmentNames;
public Attachments(ArrayList attachments, ArrayList attachmentFileNames)
{
InitializeComponent();
attachmentFiles = attachments;
attachmentNames = attachmentFileNames;
}
private void Attachments_Load(object sender, EventArgs e)
{
ScrollBar vScrollBar1 = new VScrollBar();
ScrollBar hScrollBar1 = new HScrollBar();
vScrollBar1.Dock = DockStyle.Right;
hScrollBar1.Dock = DockStyle.Bottom;
vScrollBar1.Scroll += (sender2, e2) => { pnl_Attachments.VerticalScroll.Value = vScrollBar1.Value; };
hScrollBar1.Scroll += (sender3, e4) => { pnl_Attachments.HorizontalScroll.Value = hScrollBar1.Value; };
pnl_Attachments.Controls.Add(hScrollBar1);
pnl_Attachments.Controls.Add(vScrollBar1);
Label fileName;
for (int i = 0; i < attachmentNames.Count; i++)
{
fileName = new Label();
fileName.AutoSize = true;
fileName.Text = attachmentNames[i].ToString();
fileName.Top = (i + 1) * 22;
pnl_Attachments.Controls.Add(fileName);
Button btn_RemoveAttachment = new Button();
btn_RemoveAttachment.Text = "X";
btn_RemoveAttachment.Tag = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => removeAttachment(s, e3, attachmentFiles[i].ToString(), attachmentNames[i].ToString()));
btn_RemoveAttachment.Top = (i + 1) * 22;
btn_RemoveAttachment.Left = fileName.Right + 22;
pnl_Attachments.Controls.Add(btn_RemoveAttachment);
}
}
private void removeAttachment(object sender, EventArgs e, string file, string fileName)
{
attachmentNames.Remove(fileName);
attachmentFiles.Remove(file);
pnl_Attachments.Controls.Clear();
this.Close();
}
In my test, attachmentFiles had a count of 3 and attachmentNames had a count of 3. On form load, there are no issues. But, on button click I get an exception because somehow its trying to add another click listener to a button with i = 3 (a.k.a a 4th element)
The problem is not with the event subscription, but with the event handler execution.
You are running into this problem because a closure is created for the event handler, but the value i is modified by the for loop. The last value of i will be 1 + attachmentNames.Count and this will be the value used by each invocation of the event handler.
For more detail as to why this happens you can check out the question and answer here: Access to Modified Closure.
To resolve the problem, you can assign i to another variable:
var currentAttachmentIndex = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => {
removeAttachment(s,
e3,
attachmentFiles[currentAttachmentIndex].ToString(),
attachmentNames[currentAttachmentIndex].ToString())
});
Or you can use the value you already captured in the Tag property of the btn_RemoveAttachment control.
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => {
var senderButton = (Button)s;
var currentAttachmentIndex = (int)senderButton.Tag;
removeAttachment(s,
e3,
attachmentFiles[currentAttachmentIndex].ToString(),
attachmentNames[currentAttachmentIndex].ToString())
});
Keep in mind, though, if you are removing items from the List, the indexes will not be valid. Understanding how closures work, however, should help you solve that problem if it arises (it looks like you close the form anyway after the first removal).
Presumably, the attachmentFiles[i] is what is causing the out of bounds exception, perhaps attachmentFiles has fewer elements than attachmentNames?
Why not set a breakpoint on that line and check what is causing the out of bounds exception?
I get an argument out of bounds exception on the click += line. What's interesting is why its being called? The click isn't supposed to add another event handler to itself
It looks like the exception is not thrown at the event subscription (+=) but at the lambda function declared in that same line
Its also interesting that on click the indicie is one greater than the total count so how its even executing that line is beside me considering it should never iterate higher that its max count. Any help would be greatly appreciated
The value of i is fixed when you assign the lambda to the event. The indexes at the attachmentFiles change as you remove elements, but the indexes used by the lambda to access it don't. Let's try an example.
let's assume we have an array with 4 attchements (index:attachment))
[0:att0, 1:att1, 2:att2, 3:att3]
And 4 buttons that execute this lambdas
[removeAt(0), removeAt(1), removeAt(2), removeAt(3)]
We click the second button and it correctly removes the second attachment from array, now we have:
[0:att0, 1:att2, 2:att3]
[removeAt(0), removeAt(1), removeAt(2), removeAt(3)]
Now we click the fourth button. It tries to remove the attachment with index 3 and the out of bounds exception is thrown because that index doesn't exist anymore (and even if it existed, it might not point to the right attachment!)
Another approach would be to modify your 'removeAttachment' method, and use that as your event handler for all buttons.
An example of this would be:
for (int i = 0; i < attachmentNames.Count; i++)
{
var lbl_FileName = new Label
{
AutoSize = true,
Name = "Label" + i, // Give this a name so we can find it later
Text = attachmentNames[i],
Top = (i + 1) * 22
};
var btn_RemoveAttachment = new Button
{
Text = "X",
Tag = i,
Top = (i + 1) * 22,
Left = lbl_FileName.Right + 22
};
btn_RemoveAttachment.Click += removeAttachment;
pnl_Attachments.Controls.Add(lbl_FileName);
pnl_Attachments.Controls.Add(btn_RemoveAttachment);
}
Then you can modify your removeAttachment method to be an EventHandler, and to detect the button and associated label using the sender As Button and Button.Tag property:
private void removeAttachment(object sender, EventArgs e)
{
// Get associated Label and Button controls
var thisButton = sender as Button;
var index = Convert.ToInt32(thisButton.Tag);
var thisLabel = (Label) Controls.Find("NameLabel" + index, true).First();
// Remove the files
int itemIndex = attachmentNames.IndexOf(thisLabel.Text);
attachmentNames.RemoveAt(itemIndex);
attachmentFiles.RemoveAt(itemIndex);
// Disable controls and strikethrough the text
thisButton.Enabled = false;
thisLabel.Font = new Font(thisLabel.Font, FontStyle.Strikeout);
thisLabel.Enabled = false;
}

Centering text on a button in a WinForms application

I have a simple Windows Forms application with a tabControl. I have 3 panels on the tabControl, each having 5 buttons. The text on first set of buttons is hard-coded, but the next set populates when you click one from the first group, and then the same thing happens again for the last group when you click one of the buttons from the second group. In the [Design] view I manually set the TextAlign property of each button to MiddleCenter. However, when I run the application the text on the middle set of buttons is never centered. It is always TopLeft aligned. I've tried changing the font size and even explicitly setting the TextAlign property every time I set button text programmatically, as follows:
private void setButtons(List<string> labels, Button[] buttons)
{
for (int i = 0; i < buttons.Count(); i++)
{
if (i < labels.Count)
{
buttons[i].Text = labels.ElementAt(i);
buttons[i].TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
buttons[i].Enabled = true;
}
else
{
buttons[i].Text = "";
buttons[i].Enabled = false;
}
}
}
This image shows the result:
Does anyone have any ideas for what I'm missing?
Trim text which you are assign to button. Also you can refer label by index, without calling ElementAt
private void setButtons(List<string> labels, Button[] buttons)
{
for (int i = 0; i < buttons.Count(); i++)
{
Button button = buttons[i];
if (i < labels.Count)
{
button.Text = labels[i].Trim(); // trim text here
// button.TextAlign = ContentAlignment.MiddleCenter;
button.Enabled = true;
}
else
{
button.Text = "";
button.Enabled = false;
}
}
}
You can set the UseCompatibleTextRendering property to true, then use the TextAlign property.
The strings in the SQL table that were assigned to the middle column were actually nchar(50), not nvarchar(50), which explains the problem. I added .Trim() to the Text assignment and it looks great now.
You can use the TextAlign from the Properties Menu and set it to MiddleCenter ...
If this does not work then the text you have for your button is larger than the actual button itself... to which you should either rescale your Font Size to a lower base size or a percent size of the actual button by using
btnFunction.Font = new Font(btnFunction.Font.Name, Convert.ToInt32(btnFunction.Height * 0.3333333333333333));
This would cause the button's font to be one third of the height of the button....

Categories