I am dynimically populating a panel with a table and adding some rows, columns, images and labes into the table based on the number of attached monitors. Each time the 'Monitors' panel is opened up, the dynimically added controls are removed and then re-added again in case any settings have been changed since it was last opened. When the panel is loaded for the first time, this is how it appears:
This is just how I want it to display. Both monitors detected and correct resolutions specified. Now, when the the Monitors panel is navigated away from, then switched back to (triggering the table contents to be removed and re-added), it displays with an extra row and old label like so:
No matter how many times the panel is navigated away from and back to, it always displays correctly the first time it's opened, then incorrectly any time after. Here is my code for populating the panel:
public void monitorPanel_Paint()
{
// Remove existing monitor pictures
foreach (Control item in monitorLayoutPanel.Controls.OfType<PictureBox>())
{
monitorLayoutPanel.Controls.Remove(item);
item.Dispose();
}
// Remove existing monitor labels
foreach (Control item in monitorLayoutPanel.Controls.OfType<Label>())
{
monitorLayoutPanel.Controls.Remove(item);
item.Dispose();
}
// Get number of attached monitors
int screens = Screen.AllScreens.Count();
// Auto add a table to nest the monitor images and labels
this.monitorLayoutPanel.Refresh();
this.monitorLayoutPanel.ColumnStyles.Clear();
this.monitorLayoutPanel.RowStyles.Clear();
this.monitorLayoutPanel.ColumnCount = screens;
this.monitorLayoutPanel.RowCount = 2;
this.monitorLayoutPanel.AutoSize = true;
int z = 0;
foreach (var screen in Screen.AllScreens.OrderBy(i => i.Bounds.X))
{
var percent = 100f / screens;
this.monitorLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, percent));
PictureBox monitor = new PictureBox
{
Name = "MonitorPic" + z,
Size = new Size(95, 75),
BackgroundImageLayout = ImageLayout.Stretch,
BackgroundImage = Properties.Resources.display_enabled,
Anchor = System.Windows.Forms.AnchorStyles.None,
};
Label resolution = new Label
{
Name = "MonitorLabel" + z,
TextAlign = ContentAlignment.MiddleCenter,
Font = new Font("Segoe UI", 10),
ForeColor = Color.Black,
BackColor = Color.Transparent,
Text = screen.Bounds.Width + "x" + screen.Bounds.Height,
Anchor = System.Windows.Forms.AnchorStyles.None,
};
this.monitorLayoutPanel.Controls.Add(monitor, z, 0);
this.monitorLayoutPanel.Controls.Add(resolution, z, 1);
z++;
}
}
I am surprised it isn't throwing an exception when you remove the controls from the Controls collection while enumerating it. It's worth checking if that is causing the enumeration to get clobbered and causing you to skip removing some of the controls. If that is the case, the solutions is capture a List<Control> of controls to remove while enumerating, then remove them all after enumerating it:
List<Control> controlsToRemove = new List<Control>();
foreach (Control item in monitorLayoutPanel.Controls.OfType<PictureBox>()) {
controlsToRemove.Add(item);
}
foreach (Control item in monitorLayoutPanel.Controls.OfType<Label>()) {
controlsToRemove.Add(item);
}
foreach (Control item in controlsToRemove) {
monitorLayoutPanel.Controls.Remove(item);
item.Dispose();
}
Related
I have a TableLayoutPanel on a form which has 8 columns and 8 rows. I'm trying to populate this table from a list of a user controls which each contain three labels. When I run the app, I can see it has been populated, but looking at the actual form, it is empty.
I've tried replacing the control with just a label, and they show up fine, it's just when I try to use the user control that it doesn't work.
This code runs in the Load method and generates a list of GridSquareModel, which is used to populate the user control. Each model has an X and Y position which relates to their cell on the table panel.
var startup = new StartupForm();
if (startup.ShowDialog() == DialogResult.OK)
{
var grid = new List<GridSquareModel>();
for (int i = 0; i < 64; i++)
{
Position pos = new Position();
pos.X = i % 8;
pos.Y = i / 8;
grid.Add(new GridSquareModel(pos));
}
RenderGrid(grid);
}
else
{
Application.Exit();
}
This method generates a GridSquareControl for every GridSquareModel in the list. The X and Y value in the position determines which cell they are added to.
private void RenderGrid(List<GridSquareModel> grid)
{
UIGrid.Clear();
GridTable.Controls.Clear();
foreach (var item in grid)
{
var control = new GridSquareControl(item)
{
Dock = DockStyle.Fill
};
GridTable.Controls.Add(control, item.Position.X, item.Position.Y);
//GridTable.Controls.Add(new Label
//{
// Text = "TEST"
//}, item.Position.X + 1, item.Position.Y + 1);
}
}
I have commented my attempt to replace the GridSquareModel with a Label, which worked. This should produce a table containing 64 controls but once it is loaded the table appears empty.
I'm an idiot. I created a constructor for GridSquareControl but it did not include the InitializeComponent method, so obviously it was not rendered. All is working as expected now.
I created a template panel to go by when my form loads that holds a record. When adding a new record I have a method that duplicates that template panel and then adds it to my list of panels for each record. Somehow controls are getting deleted from my template panel when I am duplicating it and I have no idea how this is happening. The portion of code doing this is listed below
Panel pn = new Panel()
{
Width = _PNTemp.Width,
Height = _PNTemp.Height,
Left = 0,
Top = 0,
BackColor = _PNTemp.BackColor,
ForeColor = _PNTemp.ForeColor,
AutoScroll = true,
Name = _PNTemp.Name,
Tag = _PrgPanels.Count.ToString()
};
MessageBox.Show(_PNTemp.Controls.Count.ToString());
foreach (Control c in _PNTemp.Controls)
{
pn.Controls.Add(c);
MessageBox.Show(_PNTemp.Controls.Count.ToString());
}
MessageBox.Show(_PNTemp.Controls.Count.ToString());
_PrgPanels.Add(pn);
I put the messagebox.show() in at 3 points to narrow down where it is happening. The first one shows the correct number of controls, the second and third shows a 1/2 the total amount of controls. why is this?
This is because each control can be added to only one parent control. All controls in your template panel are already a child of the template panel. When you try to add these controls to a new panel, the controls will get removed from the template panel.
As per the docs:
A Control can only be assigned to one Control.ControlCollection at a
time. If the Control is already a child of another control it is
removed from that control before it is added to another control.
Which means that you need to create new controls instead of adding those in the template.
An alternative approach is to create a method that returns the template panel. When you need the template panel, just call the method and a new panel will be created:
public static Panel CreateTemplatePanel() {
Panel pn = new Panel();
// set properties, add controls...
return pn;
}
A control can only be on one panel at once. I've added comments inline in your code to help explain whats happening.
Panel pn = new Panel()
{
Width = _PNTemp.Width,
Height = _PNTemp.Height,
Left = 0,
Top = 0,
BackColor = _PNTemp.BackColor,
ForeColor = _PNTemp.ForeColor,
AutoScroll = true,
Name = _PNTemp.Name,
Tag = _PrgPanels.Count.ToString()
};
MessageBox.Show(_PNTemp.Controls.Count.ToString());
//all the controls are still inside _PNTemp
foreach (Control c in _PNTemp.Controls)
{
pn.Controls.Add(c);
MessageBox.Show(_PNTemp.Controls.Count.ToString());
//Each time this runs you remove a control from _PNTemp to pn.
}
//All the controls moved from _PnTemp to pn
MessageBox.Show(_PNTemp.Controls.Count.ToString());
_PrgPanels.Add(pn);
I have a custom Tabs Control I created. It works as a coloured Label for the tab itself and as a Panel to hold the contents. My application reads UI parameters from config files. Take this line as an example from the controls config:
RTFBOX=(ID - rtf1) (BOUNDS - 0,0,100,100) (MULTILINE - enable) (FILE - email_rules.rtf)
This line tells the application to create an instance of my custom RichTextBoxPlus class and the important thing to take from this is that it is set up to read rich text from the FILE parameter. If I don't add this RichTextBoxPlus to another Control it shows it's rich text formatting absolutely fine.
I have another config that reads actions at runtime, this can be simple stuff like telling a Button created with the controls config that when it is clicked, it should fire off an email using content from a TextBox control. I have an action that pairs controls to each tab in the Tabs custom control. For example:
ADDTOTABS=(OBJECT - tabsControl1) (CONTROLS - panel1, panel2)
This finds tabsControl1 and adds panel1 to the 1st tab and panel2 to the 2nd tab. In this example, Panels are being added to each tab instead of individual controls as the Panels could hold multiple controls, handled at runtime through the ADDTOPANEL action.
ADDTOPANEL=(OBJECT - rtf1) (TARGET - panel1) (TRIGGER - onload)
So the rtf1 instance of RichTextBoxPlus is added to panel1 which is then added to the respective Panel of the Tabs control's 1st tab.
What I have found is rtf1 displays with rich text formatting absolutely fine if added to panel1 but not adding panel1 to Tabs.
The ADDTOTABS action executes this method:
private void TabContents_Action(Tabs tabpanel, string[] ctrls)
{
string[] tabs = tabpanel.GetTabNames();
for(int i = 0; i < tabs.Length; i++)
{
Control control = this.Controls.Find(ctrls[i], true).FirstOrDefault();
tabpanel.SetTaggedObject(control, tabs[i]);
}
tabpanel.SetTabActive(tabs[0]);
}
The SetTaggedObject method of the Tabs class finds the Panel control that corresponds with the tab name provided:
public void SetTaggedObject(Control ctrl, string tab)
{
Control container = this.Controls.Find(tab, false).FirstOrDefault();
container.Controls.Add(ctrl);
}
Doesn't seem to be anything untoward about this method.
The SetTabActive method of the Tabs class has a little more bulk. This handles changing the appearance of all tabs so that inactive tabs look different to the active tab. It is also hides and shows the panels for each tab based on whether the tab is active.
public void SetTabActive(string tab)
{
LabelPlus activeTab = this.tabs.Find(x => x.Name.Equals(tab));
List<LabelPlus> inactiveTabs = new List<LabelPlus>(this.tabs.FindAll(x => !x.Name.Equals(tab)));
activeTab.BackColor = this.ActiveColor;
activeTab.ForeColor = this.ActiveForeColor;
string panelName = tab.Remove(tab.Length - this.tabSuffix.Length);
Panel activeTabPanel = (Panel)this.Controls.Find(panelName, true).FirstOrDefault();
activeTabPanel.Bounds = new Rectangle(
new Point(this.tabStart, this.originalLocation.Y + this.TabTotalHeight), this.Size);
ControlCollection activeTabCtrls = activeTabPanel.Controls;
foreach(LabelPlus inactiveTab in inactiveTabs)
{
inactiveTab.BackColor = this.InactiveColor;
inactiveTab.ForeColor = this.InactiveForeColor;
string inactivePanelName = inactiveTab.Name.Remove(inactiveTab.Name.Length - this.tabSuffix.Length);
Panel inactiveTabPanel = (Panel)this.Controls.Find(inactivePanelName, true).FirstOrDefault();//
inactiveTabPanel.Bounds = new Rectangle(
new Point(this.tabStart, this.originalLocation.Y + this.TabTotalHeight), this.Size);
ControlCollection inactiveTabControls = inactiveTabPanel.Controls
foreach (Control ctrl in inactiveTabControls) { ctrl.Location = new Point(0, ctrl.Location.Y); ctrl.Hide(); }
}
foreach (Control ctrl in activeTabCtrls)
{ ctrl.Location = new Point(0, ctrl.Location.Y); ctrl.Show(); }
}
Not sure but I'd say the issue must be in this method. Any thoughts?
I add labels to my form programmatically, but they disappear, except last one. I'm sure that given location to them is appropriate. But when the second label appears, first disappears, or when third label appears, second disappears.
Here is my code:
Label[] lenlab = new Label[255];
Label lab = new Label();
lab.Font = new Font("Microsoft Sans Serif", 10, FontStyle.Bold);
lab.ForeColor = Color.White;
lab.BackColor = Color.Transparent;
lab.AutoSize = true;
lenlab[1] = lab;
lenlab[1].Location = new Point(50, panel1.Location.Y + panel1.Height + 20);
lenlab[1].Text = c[1];
this.Controls.Add(lenlab[1]);
for (int i = 2; i < c.Count; i++)
{
lenlab[i] = lab;
lenlab[i].Location = new Point(lenlab[i - 1].Location.X + lenlab[i -1].Width + 40, lenlab[i - 1].Location.Y);
lenlab[i].Text = " + " + c[i];
this.Controls.Add(lenlab[i]);
}
This line is causing every position in your array to have a reference to the same Label you created originally, outside the loop, which means all you're doing is changing the position and text of the same Label inside your loop.
lenlab[i] = lab;
The behavior you're seeing is due to the fact that you can only add a particular control to this.Controls once, so the effect is that you see the same label changing position.
Here's the portion of the Add() method that checks whether the control you're adding already has a parent, and if it does, then it removes it from it's current parent before adding it to the new one. So every time you call this.Controls.Add() with the same Label, it removes it from the Form and then adds it again.
// Remove the new control from its old parent (if any)
if (value.parent != null) {
value.parent.Controls.Remove(value);
}
Instead, create a new Label inside your for loop:
lenlab[i] = new Label();
There are controls that can help you layout controls without the need to calculate a new position each time. In particular, read up on the FlowLayoutPanel and TableLayoutPanel classes.
What you are doing there is basically create one Label, change it several times, and attach it several times to the page. What you end up having on the page is the last version of the Label being added once, which is the expected behavior.
If you want to add several labels, you need to new each of them.
I'm auto generating checkboxes and putting them into a panel,but when I do that, they all appear at the same point. I can manually move them apart, but this could cause problems down the line. Is there a way to get them to automatically align under each other.
Here's my code:
foreach (Category i in DesktopApp.getBaseCat())
{
checkBox = new CatBox();
checkBox.setCat(i);
checkBox.Text = i.ToString(); // puts the name of the category in the text field
checkBox.Click += new EventHandler(simpleCatBox_Click);
this.categoryPanel.Controls.Add(checkBox); // adds it to the panel
step++; // increments step
}
Give the checkbox a margin (10px in this example)
checkBox.Margin = new Thinkness(10);
Or, if you want different margins
checkBox.Margin = new Thickness(left,top,right,bottom);