Issue while iterating through User Controls in a Windows Form - c#

I am facing an issue while running through all the User Controls in my Windows form.
I am creating a Windows Form that has the following features:
The Main form has 3 User Controls embedded in it
The Main form also has a combo box. Selecting a particular value in the Combo box will bring the corresponding User Control to the front.
Each User Control has two Check boxes as well as two Combo boxes.
The User can summon each User Control through the Main Form's combo box and check the check boxes and/or modify the combo boxes inside each User Control
Once this is done, there is a button, which on being pressed, executes the following code. This code is supposed to check which check boxes have been checked from every User Control, and execute some functionality :
private void button1_Click(object sender, EventArgs e)
{
foreach (Control c in this.Controls)
{
if (c is UserControl)
{
foreach (Control ctl in c.Controls)
{
if (ctl is CheckBox && (ctl as CheckBox).Checked)
{
Indicator.Text = "It's in";
}
}
}
}
//Some other code after this
}
Here, I have included a Text Box called "Indicator" that shows whether the compiler has entered a particular "for" loop or "if" block. And I'm observing that the innermost "if" alone is not getting executed.
Could someone point out why exactly this is happening?

You need a recursive algorithm,
void ProcessControls(Control ctrlContainer)
{
foreach (Control ctrl in ctrlContainer.Controls)
{
if (ctrl is CheckBox && (ctrl as CheckBox).Checked)
{
Indicator.Text = "It's in";
}
if (ctrl.HasChildren)
ProcessControls(ctrl);
}
}

I do think you might be better off adding some functionality to your user control so it can describe the state of its own checkboxes rather than going digging inside it to find it and do logic. Generally in OO programming, when we encapsulate things within a class, we also provide general purpose accessors "visible to the outside" to describe the internal state of affairs, rather than letting external code interests go poking around inside class to find out what they want
At some point in time you've added these usercontrols to the form either directly in the designer, or programmatically. In the first case they will have their own name:
var u1 = usercontrol1.GetCheckboxStateArray();
var u2 = usercontrol2.GetCheckboxStateArray();
Etc
Or maybe you added them programmatically, in which case it would make sense to keep track of them in a list as you're adding them:
protected List<UserControl> _ucList = new List<UserControl>();
...
foreach(var result in somedatabasequery){
var uc = new UserControl(result.Whatever);
this.Controls.Add(uc);
_ucList.Add(uc);
}
Then this list can be iterated. Sure you could argue that "well .Controls is a collection too, so why add them to another list when they're already in an accessible collection" - for the reasons you're here; .Controls is a general purpose description of the hierarchy of all controls on a form, it contains stuff we don't want and is hard to iterate. This List is purely and simply all and only the stuff we're interested in
As an aside, the UI you have described is atypical. The more usual way of hiding and showing controls under the selection of something that holds a bit of text would be a TabControl. It might be easier to loop through too, if you will persist with this "search for UserControls in a collection of controls" method - tabcontrols have tabpages, tabpages would probably have a .Controls that just contains your UserControl. The tabpage intrinsically takes care of showing and hiding controls as pages are clicked on which could simplify your code

Thanks to everyone for the answers. As it happens, the issue was hiding in plain sight, right under my nose. In each of the User Controls, I had placed the Checkboxes and Combo Boxes inside a Group Box. It completely slipped my mind, so much so that I didn't even mention them in my question.
Thus, as #Caius had suggested in the comments, the code wasn't functioning because I had not addressed the Group Box Container holding these Controls. Once I removed the Group Boxes (used only for aesthetic purpose), the code started functioning properly.

Related

Check if user control is already open

Im using WinForm C#
Have MainForm there is one panel where. my Inventory and Sell user controls are opening in panel. panel1.Controls.Add(inventory);
How to check if userControls are open?
When i check it i want to add tabControl. But i dont know how to add in tabPage controls without closing user control. Thanks
I mean if user control is already added in panel1.Controls. If its added gave name of user control
– Acid
How could the user control possibly be added to panel1.Controls without you knowing it? And if you added it yourself, you should already know the name of the user control.
Thus, all you have to do is loop through the controls in panel1.Controls and see if you find your user control. For example:
foreach (Control ctrl in panel1.Controls)
{
if (ctrl.Name == myUserControl)
{
// Found the control!
// (do something here...)
}
}
Alternatively, if you for whatever reason don't know the name of the control, you could still find all the controls of type UserControl that have been added to the panel's Controls collection. Like so:
foreach (Control ctrl in panel1.Controls)
{
if (ctrl is UserControl)
{
// Found a UserControl!
// (do something here...)
}
}
Remember that the Tag property provided on every control gives you a way to uniquely identify it. You can check that property for matches, too, if you don't know the name.
Not sure what you mean by open, but you can handle the ControlAdded event on the Panel class to capture when a control is added...
panel1.ControlAdded += new ControlEventHandler(p_ControlAdded);

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.

Redraw Toolstrip based on selections

I have been asked to write c# winforms app that will give users the ability to select options from a checkbox list and have it automatically redraw/repaint a toolstrip with the selected items.
I am new to winforms so I am not sure how to approach it. Should I be using the BackgroundWorker Process? Invalidate()?
Just alittle confused.
Any assistence of pointing in the right direction would be appreciated.
You probably don't want a BackgroundWorker as that's run on a non-UI thread and would cause problems when you try to modify the toolstrip (you can only work with the UI on the thread the UI was created on). Handle the CheckedChanged events on the checkboxes and then add or remove items from the toolstrip. The repainting should be automatic.
You need to keep tooltips for all options some where (if Tag property of checkboxes is free the put it there). Then when an option is selected or deselected, you need to update tooltips.
Let's suppose you are adding all the checkboxes in a IList. then things will work as follows:
private IList<CheckBox> options= new List<CheckBox>();
private void UpdateTTip()
{
toolTip1.RemoveAll();
foreach (CheckBox c in options)
{
if (c.Checked)
toolTip1.SetToolTip(c, c.Tag.ToString());
}
}
Now you need to call this on checkedchanged event of options check boxes:
private void chk_CheckedChanged(object sender, EventArgs e)
{
UpdateTTip();
}
A toolstrip contains controls by itself - it does not just "paint" buttons you can press. In order to have the toolstrip display different buttons depending on different conditions, you can:
Clear the toolstrip items and re-create the ones that are needed in the current context in code when items are checked in the list you mentioned
Add all the items and design time (with property Visible = false) and only set the necessary ones to Visible = true upon selection in your check listbox
No need to do any painting :-)

C# Lock WinForm Controls

In a program I have written users can add controls to the form and move them around and set some properties in a pseudo design mode. I want to be able to lock all these controls into one location when they press a button to switch to "data mode". How can I do this? I wanted to do be able to loop through all the controls and use the Lock Property but I noticed it didn't show up in intellisense.
Thanks!
The Locked property is not a real property -- it is one which is added in by the Windows Forms designer (like the Generate Member and Modifiers "properties"). You would therefore need to simulate it yourself, either at the form level or (if required) at the control level (say with a dictionary of which controls are locked), and manually check it in the code you've written for moving controls around.
I am assuming by "pseudo-design mode" you do mean that your application is in a run-time state, and the end-user is experiencing a "virtual design mode" : please correct me if I am wrong.
But, I am assuming you are referring to the design-time 'Locked property of controls, and that you wish to "emulate" this at run-time ... correct ?
I'm also assuming you are attaching mouse up/down/move handlers to the controls you do allow to move around, probably by looping through all, or a subset of, the controls on the form (or a collection you are maintaining of controls allowed to be moved).
If my assumptions are correct, I would go for removing the event handlers that enable moving when you need to disable control movement, then restoring those event handlers when you need to allow controls to be moved again.
One main reason being that it is, imho, "best practice" to control event-handling rigorously (leaving event handlers "in-place" can interfere with object disposal ... although that may, in no way, apply to your scenario here).
One more idea : you have an "invisible" Panel docked 'fill to the Form : on this panel are all controls that can be moved : this may allow you to more easily "narrow your focus" on which controls you "spend" this extra code on. The drawbacks in using this approach are usually :
if you use hostingForm.ActiveControl to determine which control got the mousedown (and, thus, can then be moved) : you'll find some controls, like labels, and pictureboxes, do not become the activecontrol of the form when clicked, but most do.
you have a "z-order" thing to think about since a control not in your panel encapsulating the controls you wish to allow to move sent behind the pseudo-transparent panel will be hidden.
For these reasons, imho, I think disabling and re-enabling event handler attachments is best, most simple, and since it can be done when the controls are "down-cast" to their control "identity" :
private void enableControlsMove()
{
foreach (Control theControl in panel1.Controls)
{
Console.WriteLine(theControl.Name);
theControl.MouseDown += new MouseEventHandler(theControl_MouseDown);
theControl.MouseUp += new MouseEventHandler(theControl_MouseUp);
theControl.MouseMove += new MouseEventHandler(theControl_MouseMove);
}
}
private void disableControlsMove()
{
foreach (Control theControl in panel1.Controls)
{
Console.WriteLine(theControl.Name);
theControl.MouseDown -= theControl_MouseDown;
theControl.MouseUp -= theControl_MouseUp;
theControl.MouseMove -= theControl_MouseMove;
}
}
I use it this way.
best, Bill
Locking controls prevents them from
being dragged to a new size or
location on the design surface.
However, you can still change the size
or location of controls by means of
the Properties window or in code.
MSDN
I guess it's a visible-to-designer-only property. I think you'd have to implement your own freeze mechanism - a little flag to toggle between Design and Use modes.
Update: It seems that custom designer classes can add properties to controls based on whether they are in Design Mode or not.
More details available here if you intend to take the VS architectural hammer path. In any case, worth 10 mins of reading time.
Custom Design-time Control Features in Visual Studio .NET - Dino Esposito

Categories