I have a WinForms app which is updating a TableLayoutPanel from a BackgroundWorker. Depending whether a device input is On (1) or off (0) the colour and some text is changed accordingly.
try
{
//Define a new TLP to hold the search tablelayoutpanel.
//search for the iterated card number.
//get the status label using GetControl from Position.
TableLayoutPanel TLP = new TableLayoutPanel();
string IO_Card_Name = "ED527_" + i.ToString();
TLP = TLP_IO_Info.Controls.Find(IO_Card_Name, true).FirstOrDefault() as TableLayoutPanel;
try { lbl = TLP.GetControlFromPosition(4, 0) as Label; } catch { lbl = null; };
//if card is found (because input is active, colour the TLP (card) according to its state.
if (TLP != null && lbl != null && INPUTS[i - 1] == 1)
{
TLP.BackColor = Color.Green;
foreach (Label l in TLP.Controls)
{
l.BackColor = Color.Green;
}
lbl.Invoke((MethodInvoker)delegate { lbl.Text = "ON"; });
}
else if (TLP != null && lbl != null && INPUTS[i - 1] == 0)
{
TLP.BackColor = Color.White;
foreach (Label l in TLP.Controls)
{
l.BackColor = Color.White;
}
lbl.Invoke((MethodInvoker)delegate { lbl.Text = "OFF"; });
}
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
};
TLP holds 5 labels in it. The update shows some noticeable lag when the line updates. Is there a way I can carry out something akin to SuspendLayout() / ResumeLayout on the main UI thread?
****EDIT to show the before and after - the IOLabel column updates slightly before the Status column.
Sounds you have a nested design. Each row is 5 Labels are hosted by different TableLayoutPanels and the TLP_IO_Info which is a TableLayoutPanel hosts the other TableLayoutPanels. In the DoWork event of the BackgroundWorker you have a for..loop to change the Backcolor of the inner controls according to the current state of the devices which you read it from the INPUT int array. Please correct me.
I'd like to suggest this:
foreach (var tlp in TLP_IO_Info.Controls.OfType<TableLayoutPanel>()
.Where(x => x.Name.StartsWith("ED527_")))
{
if (tlp.GetControlFromPosition(4, 0) is Label lbl)
{
var state = // get the state of the current device from INPUT array
var stateColor = state == 1 ? Color.Green : Color.White;
var stateText = state == 1 ? "ON" : "OFF";
this.Invoke(new Action(() =>
{
tlp.BackColor = stateColor;
tlp.Controls.OfType<Label>().ToList().ForEach(l => l.BackColor = stateColor);
lbl.Text = stateText;
}));
}
}
Or this to eliminate the redundant code:
var stateColors = new[] { Color.White, Color.Green };
var stateTexts = new[] { "OFF", "ON" };
foreach (var tlp in TLP_IO_Info.Controls.OfType<TableLayoutPanel>()
.Where(x => x.Name.StartsWith("ED527_")))
{
if (tlp.GetControlFromPosition(4, 0) is Label lbl)
{
var state = // get the state of the current device from INPUT array
this.Invoke(new Action(() =>
{
tlp.BackColor = stateColors[state];
tlp.Controls.OfType<Label>().ToList()
.ForEach(l => l.BackColor = stateColors[state]);
lbl.Text = stateTexts[state];
}));
}
}
Note that, I've removed the expensive try..catch blocks since this code won't throw any exceptions.
As for the INPUT array, I suggest that you replace it with a Dictionary<string, int> to store the current state of each device since (according to the link you've provided) each device has a unique IOLineNumber so you can easily set/get the current state of each one.
⍰ Maybe there is already something like this in the library?
Related
I'm developing a windows form application that shows in checkboxList all the columns I have in a datagridview. First I'm setting checked all the items that can be null in SQL. I want to delete all other that are not checked or make them readonly. I have tried to cast it as checkbox but it's not working. I have tried deleting an item from the checkboxList but I get the following error Items collection cannot be modified when the DataSource property is set.
Any ideas or hints?
foreach (DataGridViewColumn column in ((MainSoftwareForm)mainSoftware).dataGridViewImportParts.Columns)
{
var columns = ((MainSoftwareForm)mainSoftware).dataGridViewImportParts.Columns.Cast<DataGridViewColumn>()
.Select(x => new { x.Name, x.HeaderText }).ToList();
checkedListBoxIPColumns.DataSource = columns;
checkedListBoxIPColumns.ValueMember = "Name";
checkedListBoxIPColumns.DisplayMember = "HeaderText";
//Set initial check state based on columns visibility
for (int i = 0; i < checkedListBoxIPColumns.Items.Count; i++)
{
if(mOS_PackOSDataSet.PARTLIST.Columns[i].AllowDBNull==true)
{
dynamic item = checkedListBoxIPColumns.Items[i];
checkedListBoxIPColumns.SetItemChecked(i, true);
}
else
{
}
}
checkedListBoxIPColumns.ItemCheck += (obj, args) =>
{
dynamic item = checkedListBoxIPColumns.Items[args.Index];
var visible = args.NewValue == CheckState.Checked ? true : false;
((MainSoftwareForm)mainSoftware).dataGridViewImportParts.Columns[(string)item.Name].Visible = visible;
};
}
I'll be really happy to explain why, if you are going to give a negative vote :)
I've found a solution to my question. If they are not allowed to be null in SQL I set them unchecked all the time.
checkedListBoxPartList.ItemCheck += (obj, args) =>
{
if (mOS_PackOSDataSet.PARTLIST.Columns[args.Index].AllowDBNull == true)
{
dynamic testOne = checkedListBoxPartList.Items[args.Index];
var test = args.NewValue == CheckState.Checked ? true : false;
((MainSoftwareForm)mainSoftware).dataGridViewPartLists.Columns[(string)testOne.Name].Visible = test;
}
else
{
dynamic item = checkedListBoxPartList.Items[args.Index];
if (args.CurrentValue == CheckState.Unchecked) args.NewValue = CheckState.Unchecked;
var visibleIndeterminate = args.NewValue == CheckState.Unchecked ? false : false;
((MainSoftwareForm)mainSoftware).dataGridViewPartLists.Columns[(string)item.Name].Visible = visibleIndeterminate;
}
};
Hi Im trying to assign a label to a FlowLayoutPanel based on the Value of a cell
My Code:
foreach (DataGridViewRow row in datagridview.SelectedRows)
{
var Id = row.Cells["typeID"].Value.ToString();
int typeID;
if (!String.IsNullOrWhiteSpace(Id) && int.TryParse(Id, out PlayerID))
{
Label label1 = new Label();
label1.Text = row.Cells["PlayerType"].Value.ToString();
//Does not matter for type of player
//flpGoalie.Controls.Add(label1);
if(label1.Text == "Goalie")
{
flpGoalie.Controls.Add(label1);
}
}
This Line flpGoalie.Controls.Add(label1); , assigns any label to that FlowLayoutPanel.
What im trying to do is Seperate the PlayerType into different FLP based on the value of the cell.
Since there is a pattern between the FLP names and the Text in the label1, you can use Controls.Find to find a particular Control in your Form like this:
foreach (DataGridViewRow row in datagridview.Rows.Cast<DataGridViewRow>())
{
var Id = row.Cells["typeID"].Value.ToString();
int typeID;
if (!String.IsNullOrWhiteSpace(Id) && int.TryParse(Id, out PlayerID))
{
Label label1 = new Label();
label1.Text = row.Cells["PlayerType"].Value.ToString();
//Does not matter for type of player
//flpGoalie.Controls.Add(label1);
Control[] ctrls = Controls.Find("flp" + label1.Text, true);
if (ctrls != null && ctrls.Length > 0){
FlowLayoutPanel flp = ctrls[0] as FlowLayoutPanel;
if(flp != null)
flp.Controls.Add(label1);
}
}
C# TextBox
AutoCompleteCustomSource has a List<string>,
AutoCompleteMode = Suggest.
I can see the List when I type a Letter.
How to show entire list without Typing a Letter Programmatically? This must be done while the User presses the Down Arrow Key in the TextBox.
Is there any Win32 API Available?
My Solution
I refined a Better Solution.
Add a ListBox Control to the form and make it as Visible = false
int curSelIndex = -1;
The below given Code will be executed Form_Load Event.
txtEmpId.AutoCompleteCustomSource.AddRange(EmpIds.ToArray());
lstAutoComplete.Items.Clear();
lstAutoComplete.Items.AddRange(EmpIds.ToArray());
txtEmpId.KeyDown += (ks, ke) =>
{
if (!(ke.KeyCode == Keys.Down ||
ke.KeyCode == Keys.Up ||
ke.KeyCode == Keys.Enter))
{
lstAutoComplete.Visible = false;
return;
}
ke.Handled = true;
if (ke.KeyCode == Keys.Enter)
{
if (lstAutoComplete.Visible)
{
var str = lstAutoComplete.SelectedItem + "";
// Process the Selected Item and set to TextBox.
}
}
if (!lstAutoComplete.Visible && txtEmpId.Focused)
{
var loc = txtEmpId.Location;
loc.Y += txtEmpId.Height;
lstAutoComplete.Location = loc;
lstAutoComplete.Size = txtEmpId.Size;
lstAutoComplete.Height = 100;
lstAutoComplete.SelectedIndex = 0;
curSelIndex = 0;
lstAutoComplete.Visible = true;
}
else if(lstAutoComplete.Visible && txtEmpId.Focused)
{
if (ke.KeyCode == Keys.Down)
{
curSelIndex++;
if (curSelIndex >= lstAutoComplete.Items.Count)
curSelIndex = lstAutoComplete.Items.Count - 1;
if (lstAutoComplete.Items.Count > 0)
lstAutoComplete.SelectedIndex = curSelIndex;
}
else if (ke.KeyCode == Keys.Up)
{
curSelIndex--;
if (curSelIndex < 0)
curSelIndex = 0;
if (lstAutoComplete.Items.Count > 0)
lstAutoComplete.SelectedIndex = curSelIndex;
}
}
};
txtEmpId.Leave += (ls, le) => lstAutoComplete.Visible = false;
I didn't find any API for your problem, so I just make a my own suggestion box by using ListBox to show when the Down Arrow Key is pressed, when you do other operation, it disappeares. I hope it is useful to you. code sample is bellow:
//string datasource
List<string> strList = null;
//suggestion listbox
ListBox sugBox = null;
public FrmTextSuggest()
{
InitializeComponent();
//setting the textbox control
strList = new List<string>()
{
"USA",
"England",
"China",
"Japan",
"Korea",
"India",
"France",
"Canada"
};
var autoCollection = new AutoCompleteStringCollection();
autoCollection.AddRange(strList.ToArray());
this.txtCountry.AutoCompleteCustomSource = autoCollection;
this.txtCountry.AutoCompleteMode = AutoCompleteMode.Suggest;
this.txtCountry.AutoCompleteSource = AutoCompleteSource.CustomSource;
//register the Down Arrow Key event
this.txtCountry.KeyDown += new KeyEventHandler(txtCountry_KeyDown);
}
void txtCountry_KeyDown(object sender, KeyEventArgs e)
{
//show the your own suggestion box when pressing down arrow and the text box is empty
if (e.KeyCode == Keys.Down && txtCountry.Text.Trim().Equals(""))
{
sugBox = new ListBox();
//define the box
sugBox.Width = txtCountry.Width;
Point p = txtCountry.Location;
p.Y += txtCountry.Height;
sugBox.Location = p;
sugBox.Items.AddRange(strList.ToArray());
//copy the value to the textbox when selected index changed.
sugBox.SelectedIndexChanged += new EventHandler(sugBox_SelectedIndexChanged);
//show box
if (sugBox.Items.Count > 0)
{
sugBox.SelectedIndex = 0;
this.Controls.Add(sugBox);
sugBox.Focus();
}
}
//remove and hide your own suggestion box when other operation
else
{
if (sugBox != null && this.Controls.Contains(sugBox))
{
this.Controls.Remove(sugBox);
sugBox.Dispose();
sugBox = null;
}
}
}
void sugBox_SelectedIndexChanged(object sender, EventArgs e)
{
string selText = this.sugBox.SelectedItem.ToString();
if (!string.IsNullOrEmpty(selText))
{
this.txtCountry.Text = selText;
}
}
here is my result of test:
I need to clear 32 labels on my windows form application, there are other labels present but I do NOT want to clear these. Is there a more efficient and less coded way to do this? My code for this at the moment is as follows using a method: (snippet)
private void ClearFields()
{
label50.Text = string.Empty;
label51.Text = string.Empty;
label52.Text = string.Empty;
label53.Text = string.Empty;
label54.Text = string.Empty;
label55.Text = string.Empty;
// Down to label82
}
I have researched but it's always clearing ALL labels/textboxes.
You could add an object to the Tag of the Label you want to clear.
Label label50 = new Label();
bool deleteMe = true;
label50.Tag = deleteMe;
Then just iterate over your labels and clear all where the Tag is true:
foreach(Label lbl in myLabels)
{
if(lbl.Tag != null && lbl.Tag is bool && (bool)lbl.Tag == true)
{
lbl.Text = String.Empty;
}
}
Try following line:
foreach (Label _label in this.Controls.OfType<Label>().Where(a => a.Name != "Lable32").Select(a => a).ToArray())
_label.Text = string.Empty;
Add non removal lable in where condition if there are many. Here except lable32 all lable text will set to empty.
I would recomend you to place all the labels that need to be cleared in some kind of cointainer. That way you can do something of the sort:
foreach (var child in container.Children)
{
if (child is Label)
{
((Label)child).Text=String.Empty;
}
}
I am not very familiar with WinForms, but I think there are containers there.
Set the Tag property of the labels you do not want cleared to the string "DoNotClear" (using the Property Window or code) then use the following LINQ code:
foreach (var label in Controls.OfType<Label>().Where(l => l.Tag != "DoNotClear"))
label.Text = string.Empty;
Solution : you can use Controls.Find() method to find the controls from id label50 to label82 and assign String.Empty for each identified Label.
Try This:
private void button1_Click(object sender, EventArgs e)
{
for(int i=50;i<83;i++)
{
this.Controls.Find("label" + i,true)[0].Text = String.Empty;
}
}
If all your labels have names like label + id
var labelsToClear = from l in Controls.OfType<Label>()
let id = Int32.Parse(l.Name.Replace("label", ""))
where id >= 50 && id <= 82
select l;
foreach(var label in labelsToClear)
label.Text = String.Empty;
If labels can have different names, then you can filter out labels which match label + id pattern:
Controls.OfType<Label>().Where(l => Regex.IsMatch(l.Name, #"^(?:label)\d+$"))
I keep getting stuck on this part of my program.
whenver i call an listbox.selectitemchange event, i want the proper amount of trackbar and labels to be displayed.
now, it does not work properly.
Some of them get removed when the event is called, some of them don't.
foreach (Label label in Controls.OfType<Label>())
{
if (label.Tag != null && label.Tag.ToString() == "dispose")
{
label.Dispose();
}
}
foreach (TrackBar trackBar in Controls.OfType<TrackBar>())
{
if (trackBar.Tag != null && trackBar.Tag.ToString() == "dispose")
{
trackBar.Dispose();
}
}
for (int i = 0; i < calc; i++)
{
//string[] LineWidthSplitted = lines[lineWidth].Split(' ');
//Int32.TryParse(LineWidthSplitted[2], out WidthValue);
Label Label = new Label();
Label.Name = "TrackbarWidth" + LabelName++;
Label.Tag = "dispose";
Label.Text = "Board -" + LabelName + "- Height:";
Label.Location = new Point(10, 450 + (50 * LabelName));
Label.Size = new System.Drawing.Size(100, 25);
this.Controls.Add(Label);
TrackBar trackBar = new TrackBar();
trackBar.Name = "TrackbarWidth" + trackbarName++;
trackBar.Tag = "dispose";
trackBar.Maximum = 85;
trackBar.Minimum = 65;
trackBar.SmallChange = 5;
trackBar.TickFrequency = 5;
trackBar.Value = 65;
trackBar.Location = new Point(150, 450 + (50 * trackbarName));
trackBar.Size = new System.Drawing.Size(100, 25);
this.Controls.Add(trackBar);
lineWidth += 4;
}
while, when i remove the foreach for the trackbar, all labels get properly displayed.
they all get deleted, en recreated for the pricese amount needed to be created, no exceptions.
Any reason why?
thank you.
Don't use "Dispose" on the labels right away. First remove them. Note that you can't modify the Controls collection inside the foreach so you have to do something like this:
List<Label> itemsToRemove = new List<Label>();
foreach (Label label in Controls.OfType<Label>())
{
if (label.Tag != null && label.Tag.ToString() == "dispose")
{
itemsToRemove.Add(label);
}
}
foreach (Label label in itemsToRemove)
{
Controls.Remove(label);
label.Dispose();
}
If you want to remove all different kinds of controls in one swoop:
List<Control> itemsToRemove = new List<Control>();
foreach (Control ctrl in Controls)
{
if (ctrl.Tag != null && ctrl.Tag.ToString() == "dispose")
{
itemsToRemove.Add(ctrl);
}
}
foreach (Control ctrl in itemsToRemove)
{
Controls.Remove(ctrl);
ctrl.Dispose();
}
I can't test this now, but I think you should also remove the controls from the Form Controls collection where you have added them. By the way, in your case I think you could avoid the OfType extension and use the old fashioned for..loop that will allow to execute just one loop....
for(int x = this.Controls.Count - 1; x >= 0; x--))
{
Control ctr = this.Controls[x];
if (ctr Is Label && ctr.Tag != null && ctr.Tag.ToString() == "dispose")
{
this.Controls.Remove(ctr);
ctr.Dispose();
}
if(ctr Is TrackBar && ctr.Tag != null && ctr.Tag.ToString() == "dispose")
{
this.Controls.Remove(ctr);
ctr.Dispose();
}
}
Notice how removing elements from a collection with a for..loop should be done in reverse order, from the end to start of the collection