Way to format Form with dynamic text fields c# - c#

Right, so I have 13 textboxes with corresponding labels that are assigned after a user decides the name from a different form (instantiated by the 'Add field...' button). The issue arises when the user wishes to delete a textbox with previously entered data, as this results in an empty space where the textbox and label originally were as visualized by the following image:
My question is: how do I make it so that when a user chooses to delete a textbox, the textbox-label pair(s) that follow it replace the deleted textbox AND shift the remaining textboxes accordingly.
Textbox-label pairs in designer:
I've thought about this problem intensively over the past few days, and have concluded that with my current knowledge of C# I am limited to solving this issue with a horrendously tedious amount of if-statements (talking hundreds - thousands here). Any and all help would be appreciated!
Current code on the X-button for first textbox-label pair:
private void xButton1_Click(object sender, EventArgs e)
{
label14.Text = "";
textBox1.Text = "";
if (label14.Text.Equals(""))
{
label14.Visible = false;
textBox1.Visible = false;
xButton.Visible = false;
label14.Text = "";
textBox1.Text = "";
}
if (!textBox2.Text.Equals(""))
{
label14.Text = label15.Text;
textBox1.Text = textBox2.Text;
}
if (!textBox2.Text.Equals("") && (textBox3.Text.Equals("")))
{
label15.Visible = false;
textBox2.Text = "";
textBox2.Visible = false;
xButton2.Visible = false;
}
}

One simple thing you could do is give all your "dynamic" controls (label, textbox, button) a similar value in their Tag property (in my example, I used the string "dynamic" for all the control Tags. This enables you to query for them easily.
Next, you could follow the logic that, anytime you delete some controls, you move all controls below the deleted ones up a distance equal to the height of the control being deleted plus whatever padding you have between the controls.
For example, when a user clicks the X button, since you know the value of the Bottom of the control that's being deleted, you could find all controls that had a matching Tag property whose Top is greater than the x button Bottom, and you can move them up.
Here's an example (this assumes that all your X buttons are mapped to this same click event):
private void buttonX_Click(object sender, EventArgs e)
{
// This is represents the distance between the bottom
// of one control to the top of the next control
// Normally it would be defined globally, and used when you
// lay out your controls.
const int controlPadding = 6;
var xButton = sender as Button;
if (xButton == null) return;
var minTopValue = xButton.Bottom;
var distanceToMoveUp = xButton.Height + controlPadding;
// Find all controls that have the Tag and are at the same height as the button
var controlsToDelete = Controls.Cast<Control>().Where(control =>
control.Tag != null &&
control.Tag.ToString() == "dynamic" &&
control.Top == xButton.Top)
.ToList();
// Delete the controls
controlsToDelete.ForEach(Controls.Remove);
// Get all controls with the same tag that are below the deleted controls
var controlsToMove = Controls.Cast<Control>().Where(control =>
control.Tag != null &&
control.Tag.ToString() == "dynamic" &&
control.Top > minTopValue);
// Move each control up the specified amount
foreach (var controlToMove in controlsToMove)
{
controlToMove.Top -= distanceToMoveUp;
}
}

Related

How can I uncheck radiobuttons in C# while still allowing only the first radiobutton to be tabbable?

Not sure if the title makes much sense, so here is the full context:
I'm coding in C#.
I've made an app with several UserControls, each one with many textboxes and radiobuttons.
All radiobuttons are placed in a panel in a set of 2, looking like this:
[ <label> O <radiobutton1text> O <radiobutton2text> ]
(while the first radiobutton have TabStop = true, and the second's TabStop = false)
When tabbing to such panel, only radiobutton1text is focused, and when hitting the LeftArrow key the radiobutton2text is selected. That's the desired outcome.
In order to make a UserControl load faster the second (and above) time, I'm not closing it but rather replacing it with a different UserControl each time the contents need to change.
But this rises an issue: When UserControl X is open, then on top of it I open UserControl Y and then back to X, the textboxes and radiobuttons still have the contents from the first session of when I had UserControl X open for the first time. (I need the contents of textboxes and radiobuttons to be reset after replacing a UserControl).
So I made a function that loops through all controls and empties their contents.
The problem is, when I uncheck the radiobuttons (and restore their TabStop state to true) in this function, the second radiobutton is tabbable after I check either one of them and then invoke the function, whereas it wasn't before going through this function.
The function:
public void BackToMain(object sender, EventArgs e)
{
// Go through all controls and empty each TextBox, RichTextBox, RadioButton or ComboBox.
int parentControlsCount = Controls.Count - 1;
for (int i = parentControlsCount; i >= 0; i--)
{
if (Controls[i].HasChildren == true)
{
int childrenControlsCount = Controls[i].Controls.Count - 1;
for (int j = childrenControlsCount; j >= 0; j--)
{
var controlType = Controls[i].Controls[j].GetType().ToString();
switch (controlType)
{
case "System.Windows.Forms.TextBox":
case "System.Windows.Forms.RichTextBox":
Controls[i].Controls[j].Text = null;
break;
case "System.Windows.Forms.RadioButton":
// Restore both properties to default value
((RadioButton)Controls[i].Controls[j]).Checked = false;
if (j == 1)
((RadioButton)Controls[i].Controls[j]).TabStop = true;
else if (j == 2)
((RadioButton)Controls[i].Controls[j]).TabStop = false;
break;
case "System.Windows.Forms.ComboBox":
((ComboBox)Controls[i].Controls[j]).SelectedIndex = -1;
break;
}
}
}
}
}
What am I doing wrong?
I ended up applying this gross hack- a function on every second radiobutton's CheckedChange:
private void DisableTabStopOnCheckedChange(object sender, EventArgs e)
{
// Assume the following STR:
// 1. In any radiobutton panel, select any radiobutton (without ever invoking BackToMain function in the first post);
// 2. Invoke the BackToMain function;
// 3. In the same radiobutton panel as in step #1, click the second radiobutton.
// Normally, without this function, if the user will now cycle through the controls using the Tab key, both the first and second radiobuttons will be tabbable,
// and that's because in the BackToMain function we reset their Checked and TabStop properies, and that's something that should be handled automatically by the control itself.
// Doing it manually means that for the *first time* selecting the second radiobutton, the first one's TabStop state won't update, which means both radiobuttons
// will have the TabStop state set to true, causing both to be tabbable.
// This is a gross hack to fix this by disabling TabStop on the first radio button if the second one is checked and the first one's TabStop state
// is true (this should happen only after BackToMain has been invoked).
if (((RadioButton)sender).Checked)
{
var firstRadioButton = ((RadioButton)sender).Parent.Controls[1];
if (((RadioButton)firstRadioButton).TabStop == true)
{
((RadioButton)firstRadioButton).TabStop = false;
}
}
}
Not a pretty solution, I know. But it works.

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).

Changing The Property Of An Object Using Variables? C#/Visual Studio

Ok so I have 100 buttons and I need to change there colors based on conditions in a while loop. They are named button1, button2, button3 ,etc. during the first time around the loop (iteration?) I need to edit button1, the next time button2 the third time button3, etc.
I thought I could just make a string that equaled "button", add the number of times around the loop to it and change the color like that.
String ButtonNumber = "button" + i; where i = number of times around loop
When I try to edit the color using ButtonNumber.BackColor = Color.Red; it won't let me because it's not treating ButtonNumber like a button, but like a string. How do I accomplish this? Thanks! (this is my first time programing pretty much)
Consider using Controls.Find to find a control by name, and then you can change it's properties:
for (int i = 1; i <= 100; i++)
{
var buttonName = string.Format("button{0}", i);
var foundControl = Controls.Find(buttonName, true).FirstOrDefault();
if (foundControl != null)
{
// You can now set any common control property using the found control
foundControl.BackColor = Color.Red;
// If you need to set button-specific properties (i.e. properties
// that are not common to all controls), then cast it to a button:
var buttonControl = foundControl as Button;
if (buttonControl != null)
{
buttonControl.AutoEllipsis = true;
}
}
}

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;
}
}

Can I display links in a ListView's detail mode?

I'm displaying a set of search results in a ListView. The first column holds the search term, and the second shows the number of matches.
There are tens of thousands of rows, so the ListView is in virtual mode.
I'd like to change this so that the second column shows the matches as hyperlinks, in the same way as a LinkLabel shows links; when the user clicks on the link, I'd like to receive an event that will let me open up the match elsewhere in our application.
Is this possible, and if so, how?
EDIT: I don't think I've been sufficiently clear - I want multiple hyperlinks in a single column, just as it is possible to have multiple hyperlinks in a single LinkLabel.
You can easily fake it. Ensure that the list view items you add have UseItemStyleForSubItems = false so that you can set the sub-item's ForeColor to blue. Implement the MouseMove event so you can underline the "link" and change the cursor. For example:
ListViewItem.ListViewSubItem mSelected;
private void listView1_MouseMove(object sender, MouseEventArgs e) {
var info = listView1.HitTest(e.Location);
if (info.SubItem == mSelected) return;
if (mSelected != null) mSelected.Font = listView1.Font;
mSelected = null;
listView1.Cursor = Cursors.Default;
if (info.SubItem != null && info.Item.SubItems[1] == info.SubItem) {
info.SubItem.Font = new Font(info.SubItem.Font, FontStyle.Underline);
listView1.Cursor = Cursors.Hand;
mSelected = info.SubItem;
}
}
Note that this snippet checks if the 2nd column is hovered, tweak as needed.
Use ObjectListView -- an open source wrapper around a standard ListView. It supports links directly:
This recipe documents the (very simple) process and how you can customise it.
The other answers here are great, but if you don't want to have to hack some code together, look at the DataGridView control which has support for LinkLabel equivalent columns.
Using this control, you get all the functionality of the details view in a ListView, but with more customisation per row.
You can by inheriting the ListView control override the method OnDrawSubItem.
Here is a VERY simple example of how you might do:
public class MyListView : ListView
{
private Brush m_brush;
private Pen m_pen;
public MyListView()
{
this.OwnerDraw = true;
m_brush = new SolidBrush(Color.Blue);
m_pen = new Pen(m_brush)
}
protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
if (e.ColumnIndex != 1) {
e.DrawDefault = true;
return;
}
// Draw the item's background.
e.DrawBackground();
var textSize = e.Graphics.MeasureString(e.SubItem.Text, e.SubItem.Font);
var textY = e.Bounds.Y + ((e.Bounds.Height - textSize.Height) / 2);
int textX = e.SubItem.Bounds.Location.X;
var lineY = textY + textSize.Height;
// Do the drawing of the underlined text.
e.Graphics.DrawString(e.SubItem.Text, e.SubItem.Font, m_brush, textX, textY);
e.Graphics.DrawLine(m_pen, textX, lineY, textX + textSize.Width, lineY);
}
}
You can set HotTracking to true so that when the user hovers mouse over the item it appears as link.

Categories