C# WinForm: Accessing a certain Control in Controls - c#

I have 3 Controls inside a TabPage tabpage1; let's call them panel1, panel2 and datagridview1. I'm trying to make a general method for accessing panel2. How do I access this Panel in TabPage.Controls? I've found out that I can use something like tabpage1.Controls[1]. But how do I know the index of panel2? And how do I set its index?

I believe you can use the controls collection.
var ctrls = this.Controls.Find("ControlName", true);
if (ctrls != null)
if(ctrls.Length != 0)
{
Control ctrl = ctrls[0];
}
I am not good in C# but I believe it works and you can easy make an universal method from it. (this only for the ilustrational purposes - replace with the apriopriate object.

Related

How to dynamically remove a UserControl from the form in c #?

I have a UserControl that is dynamically added to a FlowLayoutPanel. In that same UserControl I have a button to remove itself if the user wants it, obviously at runtime. To eliminate I mean not only to eliminate that tight button, but also the full UserControl that contains the button.
The code of when the UserControl are added dynamically at the moment is as follows:
private void agregaUC() {
UserControl1 UC = new UserControl1();
aux += 1;
UC.Tag = aux.ToString();
flowLayoutPanel2.Controls.Add(UC);
}
The code to eliminate this is on the side of the form, that is, where the UserControl are being added. The button event to remove the UserControl is thrown by code through the operator + =, then there I write the suggestions that you give me.
EDIT: Based on the sample of code you've added, I've modified the below code to work better with what you are looking for. You need to find out how to access the Tag of the control you're trying to remove.
Since you don't have a reference, then you should make sure that the .Tag property can be found, because then you can do something like
foreach (Control c in flowLayoutPanel2.Controls) {
if (c.Tag == "Aux") {
flowLayoutPanel2.Controls.Remove(c);
c.Dispose();
break;
}
}
EDIT
Reading through all the comments everywhere, it seems like this is what's happening. There is a UserControl, inside that user control is a Button (Delete) and the button's Click event is subscribed to by the window, and it's in this event handler that we're trying to remove the UserControl from flowLayoutPanel2
Based on these assumptions, your function should look like this:
void UserControl_Delete_Click(object sender, EventArgs e)
{
Button Delete = (Button)sender;
UserControl UC = (UserControl)Delete.Parent;
flowLayoutControl2.Controls.Remove(UC);
UC.Dispose();
}
This is assuming a lot about the internal structure of everything, as I don't have the code to confirm this will work. It will get you a long ways down the path, though, and should only need a little tweaking based on the actual structure of the UserControl.
You can try something like that.
this.Parent.Controls.Remove(this);
Control.Parent Property.
Remark: Setting the Parent property value to null removes the control from the Control.ControlCollection of its current parent control.
So
this.Parent = null;
Edit
The code is intended to be called from within the user control itself.

Windows Form Control with Children

I have a panel in my Windows form that I would like to add other controls like text boxes into it and also iterate over all elements in the panel to retrieve all the data in it.
Is this possible to do with a panel control?
I experimented with a foreach loop such as
foreach(textbox tb in panel1)
{
}
but I get an error saying panel does not have a public definition for GetEnumerator.
What would be a better control/container to use, where I can add more controls to it and eventually access all the controls within it an their data?
Update
Just a heads up - I am having some problems with adding multiple text boxes in code.
I create a textbox object and then add it to the panel, but only one shows up.
I have read elsewhere on this site and others that adding a textbox with the same name might be causing the problem.
To solve this, I replaced the panel with a flow layout panel which works great.
Hopefully this helps others.
It's the correct control to use. Try iterating the Controls property of the panel object.
foreach(Control control in panel.Controls)
{
if(control is TextBox)
{
TextBox textBox = control as TextBox;
//etc.
}
}
The following example clears all textbox in any control
void ClearTextBoxes(Control parent)
{
foreach (Control child in parent.Controls)
{
TextBox textBox = child as TextBox;
if (textBox == null)
ClearTextBoxes(child);
else
textBox.Text = string.Empty;
}
}
Then whenever you want clear. you call
ClearTextBoxes(panel1);
You need to access Controls collection of the Panel, better if you do:
foreach(Textbox tb in panel1.Controls.OfType<TextBox>)
But the above would give you TextBoxes inside the panel, not inside other controls inside the panel, if you want to get get textboxes recursively then see this question
You're missing one little thing. Try:
foreach (Control c in panel1.Controls)
And then check the control type if you have more than one type of control in it.
The reason for the error is that panel1 is an object, not a collection of objects, so you have to refer specifically to the collection of objects that panel1 contains.
Something like this
foreach (Control c in panel1.Controls)
{
if(c.GetType() == typeof(TextBox))
{
//do stuff
}
}

foreach (Control ctrl in Frm.Controls) in which order it takes controls

I am having a peculiar problem with the order in which TextBox controls are added in to the form's Controls property.
Currently, I have the function:
public static bool IsValidate(System.Windows.Forms.Form Frm)
{
foreach (Control ctrl in Frm.Controls)
if (ctrl is TextBox)
// if (((TextBox)ctrl).AccessibleDescription == "Valid" && ((TextBox)ctrl).Text == string.Empty)
if (((TextBox)ctrl).AccessibleDescription == "Valid" && ((TextBox)ctrl).Text.Trim()== "")
{
MessageBox.Show(((TextBox)ctrl).AccessibleName + " Can't be Blank", Program.companyName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
((TextBox)ctrl).Focus();
return false;
}
return true;
}
But it's iterating through the textboxes randomly, even though I have set their tab indices.
So I develop the same form again and create the textboxes sequentially. But still, when I pass the form to this function, it's iterating through the textboxes randomly.
I want to know if there is any property of the controls that would allow me to manage their flow.
You can do its easily.
Please use following syntax and which sort controls as per your tabindex in your form
foreach (Control control in this.Controls.Cast<Control>()
.OrderBy(c => c.TabIndex))
{
}
It's much easier to sort controls manually than manage their order in Controls collection. Example (sorts by TabOrder):
private static int CompareTabIndex(TextBox c1, TextBox c2)
{
return c1.TabIndex.CompareTo(c2.TabIndex);
}
public static bool IsValid(Form form)
{
List<TextBox> textBoxes = new List<TextBox>();
foreach(Control ctl in form.Controls)
{
TextBox textBox = ctl as TextBox;
if(textBox != null) textBoxes.Add(textBox);
}
textBoxes.Sort(new Comparison<TextBox>(CompareTabIndex));
foreach(TextBox textBox in textBoxes)
{
if(textBox.AccessibleDescription == "Valid" && textBox.Text.Trim() == "")
{
MessageBox.Show(textBox.AccessibleName + " Can't be Blank",
Program.companyName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
textBox.Focus();
return false;
}
}
return true;
}
Is it really iterating over the controls "randomly"? (Implying that it is non-deterministic and the order is likely to change each time.) Or is it iterating over the controls in the same order each time, but not the order you expect? I suspect it's the latter, given that the C# language specification explicitly states the ordering of foreach (see first answer).
The tab order certainly won't affect the ordering of the controls. That's just for UI purposes. The actual order of the controls as array elements in the backing store is more likely controlled by the order in which they were created when building the form.
Can you elaborate more on that last part where you develop the form again "and take the text box sequentially"?
The controls are placed in order of the Z-order of the controls in the same parent container (top-most to bottom-most). To test try placing controls on a form and get the order. Apply "Send to Back" or "Bring to Front" for a few controls (at design time or runtime). The order of the foreach will change with the topmost control first and downwards.
The generated Designer code adds the controls based on the z-order. Lowest control first (top most control last). Hence it seems like it is based on the order in which it is added to the container.
I'm not sure if the implementation of BringToFront() and SendToBack() internally removes and adds controls in the required order. To me it makes sense to have it based on the z-order. And like mentioned above, we can always use our own ordering if required.
You can drop the controls into the form in the designer visually, and then open up the Form.Designer.cs source file and locate where the designer has typed in the code to add the controls to the Controls collection (i.e. the Controls.Add lines) and re-order those lines in the *.Designer.cs by hand. Once you've done that, the designer should leave your changes alone. I noticed that the designer writes them in reverse order. Your foreach should find them in the order that you arranged them.
i had this problem and i changed the order control name on Designer.cs ,
this.groupBox3.Controls.Add(this.txtPrice);
this.groupBox3.Controls.Add(this.txtDate);
See the Document Outline of the Form in view -> Other Windows -> Document Outline.
And then change the hierarchy of the controls as you need. the foreach looks there

Bring Winforms control to front

Are there any other methods of bringing a control to the front other than control.BringToFront()?
I have series of labels on a user control and when I try to bring one of them to front it is not working. I have even looped through all the controls and sent them all the back except for the one I am interested in and it doesn't change a thing.
Here is the method where a label is added to the user control
private void AddUserLabel()
{
var field = new UserLabel();
userContainer.Controls.Add(field);
SendLabelsToBack(); // Send All labels to back
userContainer.Controls[field.FieldName].BringToFront();
}
Here is the method that sends all of them to the back.
private void SendLabelsToBack()
{
foreach (var label in userContainer.Controls);
label.SendToBack();
}
Yeah, there's another way. The Controls.SetChildIndex() also changes Z-order. The one with index 0 is the one on top. Doesn't buy you anything though, BringToFront() uses this method.
Your SendLabelsToBack() method as given cannot work, it will also send the label to added to the back. But your next statement fixes that again.
Okay, that doesn't work, which means the BringToFront() method doesn't get executed. Look in the Output window for a "first chance exception" notification. As written, your SendLabelsToBack() will cause an exception if the user control contains any control other than a UserLabel. Also, set a breakpoint after the BringToFront() call and check the value of userContainer.Controls[0].Name when it breaks.
Controls' z-index is per-container.
If you call BringToFront on a control that is inside a container (such as a Panel), it will not bring the container to the front.
Therefore, the control will only go in front of other controls in that container.
To see what containers your controls are in, you can use the Document Outline pane in the View menu.
EDIT: Your userContainer control is probably behind a different control.
Have you tried Invalidate() after BringToFront()? BringToFront does not raise the Paint event
try this:
private void SendLabelsToBack()
{
foreach (var label in userContainer.Controls)
{
label.SendToBack();
label.Invalidate();
}
}
I think you just need to change your last line:
userContainer.Controls[field.FieldName].BringToFront();
to this:
userContainer.Controls[field.Name].BringToFront();
When you use a string as the indexer for the Controls collection, it goes by the Name property of the control (not the FieldName property).
Since you're just trying to bring the most recently-added control to the top, this would also work:
userContainer.Controls[userContainer.Controls.Count - 1].BringToFront();
From my experience looks like windows puts all controls belonging to one graphic container(pane, group box...etc) in a software collection. The collection is ordered by child index which is a property of every control in that container.
The trick is that children with the same index can and do exists. In this case windows will paint those children ordered relative to others but between them it will paint them in the reverse order they had been added to the container.
Long story short: for one container-you need to make sure controls have different indexes by changing ALL NOT just SOME of the indexes when you want to change the z-order. For example:
foreach (Control newControl in TopControl.Controls)
{
TopControl.Controls.SetChildIndex(newControl,indexlogic(newControl));
}
where indexLogic(newControl ) is your method of calculation of the index of particular control.

is there a way to add and remove controls from a form to another without the controls being deleted/GCed?

I have one form called:
MyControlContainerForm ccf
and a main form called:
SolidForm sf
and I am adding all the controls inside an instance of new MyControlContainerForm () to SolidForm, using:
sf.Controls.Add ( Control )
but when I remove them using:
sf.Controls.Remove ( Control )
they are gone from MyControlContainerForm instance as well.
Why? And how do I prevent this?
I want to be able to add MyControlContainerForm controls whenever I want, without initializing MyControlContainerForm every time, just once.
The reason this is happening is not that you're removing the controls from form2, but rather that you're adding them. Controls can't be shared between forms. If you look at the reflected code of the form2.Controls.Add() on the Control Collection enumerator, we can see what's happening here:
...
if (value.parent == this.owner)
{
value.SendToBack();
}
else
{
if (value.parent != null)
{
value.parent.Controls.Remove(value);
}
base.InnerList.Add(value);
...
As you can see here it check the parent of the incoming control, if it's not the owner of the collection, then it simply runs a value.parent.controls.Remove(value) to strip the control from it's originating form, so it can be added to the current one.
Controls are not intended to be on 2 Forms at the same time. Im surprised you got way with that, probably because you do not Show MyControlContainerForm .
Note that Control has a Parent property (= in who's Controls collection am I?), singular.
Edit:
In fact, when button1 is on panel1, it is part of panel1.Controls. But the statement
panel2.Controls.Add(button1);
removes button1 from panel1.Controls.
You can use a List<Control> as a store. That would also keep them alive just fine.

Categories