Optimize c# foreach complexity - c#

Alright so I`m doing a particular case here and I need to pass through a treeview with many foreaches.
After realising my code is not working properly in memory wise I decided to rewrite the code to LINQ. I have managed to get rid of the first foreaches but at the end, I cannot get rid of the signalUI foreach because it needs the Typeof(SignalUI) properties. These being said I don`t know how to do the LINQ for 2 lists at the same time. I have tried working with JOINS but had no results.
private void signalUITextBox_textchanged(object sender, EventArgs e)
{
for (int j = 0; j < _numberOfSelectedSlaves; j++)
if (_currentNode.Parent.Parent.Text == mas.Slaves[j].Name)
{
int counter = 0;
foreach (FrameUI frame in mas.Slaves[j].FrameList)
{
int signalcounter = 0;
if(_currentNode.Text == mas.Slaves[j].FrameList[counter].FrameName)
foreach (SignalUI signalui in frame.SignalList)
{
foreach (Control i in this.Controls)
{
if (i is TextBox)
{
var txtbox = (TextBox)sender;
if (txtbox.Name == i.Name)
{
foreach (var k in typeof(SignalUI).GetProperties())
if (signalui.SignalName + k.Name == txtbox.Name)
{
mas.Slaves[j].FrameList[counter].SignalList[signalcounter].SetProperty(k.Name, Convert.ToString(txtbox.Text));
}
}
}
}
signalcounter++;
}
counter++;
}
}
}
private void signalUCheckBox_textchanged(object sender, EventArgs e)
{
var SlaveQuery = mas.Slaves.Where(slave => slave.Name == _currentNode.Parent.Parent.Text).First();
var FrameQuery = SlaveQuery.FrameList.Where(frame => frame.FrameName == _currentNode.Text).First();
var chkbox = (CheckBox)sender;
var ControlQuery = this.Controls.OfType<CheckBox>().Where(control => control.Name == chkbox.Name).First();
}
EDIT: Thanks for help. Following below answers i managed to remove all the nested loops
private void signalUITextBox_textchanged(object sender, EventArgs e)
{
int j;
int signalcounter = 0;
int counter = 0;
var txtbox = (TextBox)sender;
var properties = typeof(SignalUI).GetProperties();
var SlaveQuery = mas.Slaves.Where(slave => slave.Name == _currentNode.Parent.Parent.Text).First();
var FrameQuery = SlaveQuery.FrameList.Where(frame => frame.FrameName == _currentNode.Text).First();
for (j = 0; j < _numberOfSelectedSlaves; j++)
if (_currentNode.Parent.Parent.Text == mas.Slaves[j].Name)
break;
foreach (FrameUI frame in mas.Slaves[j].FrameList)
{
if (_currentNode.Text == mas.Slaves[j].FrameList[counter].FrameName)
break;
counter++;
}
foreach (SignalUI signalui in FrameQuery.SignalList)
{
var prop = properties.FirstOrDefault(p => signalui.SignalName + p.Name == txtbox.Name);
if (prop != null)
{
mas.Slaves[j].FrameList[counter].SignalList[signalcounter].SetProperty(prop.Name, Convert.ToString(txtbox.Text));
}
signalcounter++;
}
}

As a general rule: Pull repeated calculations that always yield the same result within each loop iteration out of the body of the loop and to them once. The structure of your code will simplify, declutter, and you effectively reduce its inherent complexity.
The outline of your code will look like this:
private void signalUITextBox_textchanged(object sender, EventArgs e)
{
// build a list of textboxes, but…
// in fact, you don't need this at all if you care only of 'sender' as a TextBox
var textBoxes = this.Controls.OfType<TextBox>().ToList();
// hence:
if (!(sender is TextBox)) return;
var txtbox = (textBoxe) sender;
// build a list of properties (hint: reduce the list further, if you can)
var properties = typeof(SignalUI).GetProperties();
// in addition, a dictionary may come handy: .ToDictionary(p => p.Name);
// the rest of the code seems cluttered and unnecessarily complex, too,
// but hard to refactor without detailed knowledge of your data structures
for (int j = 0; j < _numberOfSelectedSlaves; j++)
{
if (_currentNode.Parent.Parent.Text == mas.Slaves[j].Name)
{
int counter = 0;
foreach (FrameUI frame in mas.Slaves[j].FrameList)
{
int signalcounter = 0;
if (_currentNode.Text == mas.Slaves[j].FrameList[counter].FrameName)
foreach (SignalUI signalui in frame.SignalList)
{
// find the matching property
var prop = properties.FirstOrDefault(p => signalui.SignalName + p.Name == txtbox.Name);
if (prop != null)
{
mas.Slaves[j].FrameList[counter].SignalList[signalcounter].SetProperty(prop.Name, Convert.ToString(txtbox.Text));
}
signalcounter++;
}
counter++;
}
}
}
}
Three nested loops less.

Related

Get specific listview rows based on condition

I have a listview with diffrent entries (see figure (A). I would like to extract some specific rows based on a condition. So far, i have this code:
private void Button2_Click(object sender, EventArgs e)
{
ArrayList listing = new ArrayList();
for (int i = 0; i < listView2.Items.Count; i++)
{
string columnOne = listView2.Items[i].Text;
string columnTwo = listView2.Items[i].SubItems[1].Text;
int numb = int.Parse(listView2.Items[i].SubItems[2].Text);
string columnThree = listView2.Items[i].SubItems[3].Text;
if(numb >= 2)
{
listing.Add($"{columnOne},{columnTwo},{numb},{columnThree}");
}
}
foreach (string item in listing)
{
listView2.Items.Clear();
ListViewItem listItem = new ListViewItem();
var separ = item.Split(',');
listItem.Text = separ[0].Trim();
listItem.SubItems.Add(separ[1]);
listItem.SubItems.Add(separ[2]);
listItem.SubItems.Add(separ[3]);
listView2.Items.Add(listItem);
}
}
I get figure (B), but normally i should get figure (C). How can this be achieved?
you shouldn't clear listview in foreach loop. do it once:
listView2.Items.Clear();
foreach (string item in listing)
{
// listView2.Items.Clear();
ListViewItem listItem = new ListViewItem();
var separ = item.Split(',');
listItem.Text = separ[0].Trim();
listItem.SubItems.Add(separ[1]);
listItem.SubItems.Add(separ[2]);
listItem.SubItems.Add(separ[3]);
listView2.Items.Add(listItem);
}
Removing the non matching items from the list makes more sense here. For your problem, execute a backward loop, try to convert the text of the third subitem to integer value using the int.TryParse method, and remove the ListViewItem if the value is less than 2.
private void button2_Click(object sender, EventArgs e)
{
for (var i = listView2.Items.Count - 1; i >= 0; i--)
{
if (int.TryParse(listView2.Items[i].SubItems[2].Text, out var num) && num < 2)
{
listView2.Items.RemoveAt(i);
}
}
}
Yet, if you want to get a list of matching items:
// +
using System.Collections.Generic;
private void button2_Click(object sender, EventArgs e)
{
var items = new List<ListViewItem>();
for (var i = 0; i < listView2.Items.Count; i++)
{
if (int.TryParse(listView2.Items[i].SubItems[2].Text, out var num) && num >= 2)
{
items.Add(listView2.Items[i]);
}
}
// ...
}
Or LINQ way:
// +
using System.Linq;
private void button2_Click(object sender, EventArgs e)
{
var items = listView2.Items.Cast<ListViewItem>()
.Where(x => int.TryParse(x.SubItems[2].Text, out var num) && num >= 2)
.ToList();
// ...
}
As a side note, using the ArrayList class is not recommended, use the List<T> class instead.

datagrid get selected rows and cells values in wpf c#

I would like to redo the code of my old windows forms application on wpf and I have a problem with referencing datagridview.
This is the void look of my old application:
private void button2_Click(object sender, EventArgs e)
{
if (DGV1.Rows.Count > 0 && DGV1.SelectedRows != null)
{
bool wart = true;
for (int i = 0; i < listBox2.Items.Count; i++)
{
listBox2.SelectedIndex = i;
int w1 = Int32.Parse(listBox2.SelectedItem.ToString());
int w2 = Int32.Parse(DGV1.SelectedRows[0].Cells[0].Value.ToString());
if (w1 == w2)
{
wart = false;
break;
}
}
if (wart)
{
listBox2.Items.Add(DGV1.SelectedRows[0].Cells[0].Value);
}
}
}
This is the void look of my new application:
private void Button1_Click(object sender, RoutedEventArgs e)
{
IList rows = dataGrid1.SelectedItems;
if(dataGrid1.SelectedItem != null)
{
bool wart = true;
for (int i =0; i < listBox1.Items.Count; i++)
{
listBox1.SelectedIndex = i;
object item = dataGrid1.SelectedItem;
int w1 = Int32.Parse(listBox1.SelectedItem.ToString());
int w2 = Int32.Parse(dataGrid1.SelectedCells[0].Column.GetCellContent(item).ToString()); <--- !!
if(w1 == w2)
{
wart = false;
break;
}
}
if(wart)
{
listBox1.Items.Add(dataGrid1.SelectedCells[0]); <-- !!
}
}
}
The application spills out at the second if, where it displays:
And it should be:
Please Help :-)
It should probably be like this:
listBox1.Items.Add(dataGrid1.CurrentRow.Cells[0].Value);
This code is from WinForms, but I assume the coding for wpf may not be different, since both are in c#.
dataGrid1.SelectedItem isn't just some object, it has concrete type and properties like Id, Tytul, Kategorie, Text
you need to make a cast to that concrete type and access property instead of trying to get the value from low-level UI elements like DataGridCellInfo:
var item = (MyConcreteClass)dataGrid1.SelectedItem;
int w2 = item.Id;

Simulate CTRL down until I desire in c#?

I'm trying to simulate a user pressing ctrl down, the main goal would be in a datagridview when I select something programarly (initially) I dont want the user to then change that selection if not just add on to it or subtract, just as if you were to hold ctrl + left mouse click. I have no idea where to even begin. I tried to create a selection change event conbined with logicals but that will cause an infinite loop since we would be selecting one by a user then the code change other and other etc infinitely triggering that event. Please help, I'm sort of new to coding. I also don't know how to determine whether a ctrl key has been pressed, is pressed and being held.
private void selecttionh(object sender, EventArgs e)
{
if (stage == "4A" || stage == "3B" && ModifierKeys.HasFlag(Keys.Control))
{
int nothing = 0;
btnclickercl bt = new btnclickercl();
bt.dataGridView1_SelectionChanged(sender, e, dataGridViewReslist, dataGridViewnewres, nothing);
}
if (stage == "4A" || stage == "3B" && (ModifierKeys & Keys.Control) != Keys.Control)
{
MessageBox.Show("Please Press and hold " + "'ctrl'" + " to continue");
dataGridViewReslist.ClearSelection();
for (int i = 0; i < ResRoomSelections.Count; i++)
{
dataGridViewReslist.Rows[ResRoomSelections[i][0]].Cells[ResRoomSelections[i][1]].Selected = true;
dataGridViewReslist.Rows[ResRoomSelections[i][0]].Cells[(ResRoomSelections[i][1]) + 1].Selected = true;
}
}
else
{
dataGridViewReslist.ClearSelection();
for (int i = 0; i < ResRoomSelections.Count; i++)
{
dataGridViewReslist.Rows[ResRoomSelections[i][0]].Cells[ResRoomSelections[i][1]].Selected = true;
dataGridViewReslist.Rows[ResRoomSelections[i][0]].Cells[(ResRoomSelections[i][1]) + 1].Selected = true;
}
}
}
The way to make this happen is to store the selection state separately, update it when the user clicks a cell, then re-apply it. This prevents the selection from being lost every time they click. If you hook the proper event handlers (mouseup, not click) you can do this without the screen flickering and otherwise being a mess to look at.
Everything you need is in this class, including an extension method SetupToggledSelectionMode(), which is your entry point.
static public class Example
{
static private bool[][] GetSelectionState(DataGridView input)
{
int rowCount = input.Rows.Count;
int columnCount = input.Columns.Count;
var result = new bool[rowCount][];
for (var r = 0; r < rowCount; r++)
{
result[r] = new bool[columnCount];
for (var c = 0; c < columnCount; c++)
{
var cell = input.Rows[r].Cells[c];
result[r][c] = cell.Selected;
}
}
return result;
}
static private void SetSelectionState(DataGridView input, bool[][] selectionState)
{
for (int r = 0; r <= selectionState.GetUpperBound(0); r++)
{
for (int c = 0; c <= selectionState[r].GetUpperBound(0); c++)
{
input.Rows[r].Cells[c].Selected = selectionState[r][c];
}
}
}
static public void SetupToggledSelectionMode(this DataGridView input)
{
bool[][] selectionState = GetSelectionState(input); //This will be stored in a closure due to the lambda expressions below
input.CellMouseUp += (object sender, DataGridViewCellMouseEventArgs e) =>
{
selectionState[e.RowIndex][e.ColumnIndex] = !selectionState[e.RowIndex][e.ColumnIndex];
SetSelectionState(input, selectionState);
};
input.SelectionChanged += (object sender, EventArgs e) =>
{
if (selectionState != null)
{
SetSelectionState(input, selectionState);
}
};
}
}
To use, populate your gridview, set up the initial selection programmatically, and call it like this:
myDataGrid.DataSource = myData;
myDataGrid.Refresh();
myDataGrid.SelectAll();
myDataGrid.SetupToggledSelectionMode();
The SetupToggledSelectionMode() method will register the necessary event handlers and store the selection state of the grid in a closed variable accessible to both handlers. So you won't have to declare anything additional; just call the method.
Thank you for this, this really helped me. all I did was to make it more efficient.Since it would call the Selection change every step of the way, so I got rid of that event completely and only kept the CellMouseup Event.
static private bool[][] GetSelectionState(DataGridView input)
{
int rowCount = input.Rows.Count;
int columnCount = input.Columns.Count;
var result = new List<int[]>();
for (var r = 0; r < rowCount; r++)
{
for (var c = 0; c < columnCount; c++)
{
if(input.Rows[r].Cells[c].Selected==true)
{
result.add(new int[]{r,c});//will keep only the integer of selected items
}
}
}
return result;//this for me was a recycled variable it can be used or recycled from somewhere else
}
private void SetSelectionState(DataGridView input,result)
{
for (int i=0;i<result.Count;i++)
{
input.Rows[result[i][0]].Cells[result[i][1]].Selected = true;
}
}
public void SetupToggledSelectionMode(DataGridView input,result)
{
for (int i=0;i<result.Count;i++)
{
if(result[i].SequenceEqual(new int[] { e.RowIndex, e.ColumnIndex }))
{
result.RemoveAt(i);
continueer = 1;
break;
}
}
if (continueer == 0)
{
ResRoomSelections.Add(new int[] { e.RowIndex, e.ColumnIndex });
}
SetSelectionState(input);
//whatever else you need to do
}
I know there is still a better way to search List but I could not get a lamda search to work so I just used brute force
-Thank you John Wu for all

C# reference array assignment issue

I'm having a little trouble reading values in from a database and assigning them to an array. It seem to work in my unit tests, but in practice some values are missing.
Here's my database code:
private void GetParameterValuesFromDatabase()
{
this.parameterValues = (from DataRow r in this.database.RunCommand("select * from KST_PARAM_VALUES v join DM_PARM_NAME p on v.PARM_NAME_KEY = p.PARM_NAME_KEY").Rows
where (int)r["SCENARIO_KEY"] == this.scenario.ScenarioKey
select new DatabaseParameter
{
ParameterValuesKey = r.Field<int>(0),
ProfileType = r.Field<string>(1),
ScenarioKey = r.Field<int>(2),
StressEditorKey = r.Field<int>(3),
StressClassKey = r.Field<int>(4),
PeriodKey = r.Field<int>(5),
ParameterNameKey = r.Field<int>(6),
ParameterValue = r.Field<double>(7),
ActiveStress = (r.Field<string>(8) == "Y") ? true : false,
ParameterKey = (int)r["PARM_NUMBER"]
}).ToDictionary(r => r.ParameterValuesKey, r => r);
}
Not having any issues with this part of my code, just showing for completeness.
private void LoadParameters()
{
this.GetParameterValuesFromDatabase();
// TODO: Assuming 9 periods for now, change to allow for variable periods
for (int i = 1; i <= MaxNumberOfStressPeriods; i++)
{
this.parametersByPeriod.Add(i, this.parameterValues.Where(t => t.Value.PeriodKey == i).ToDictionary(t => t.Key, t => t.Value));
}
Log.Instance.LogMessage(LogLevel.Debug, "Created parameter dictionaries from database");
// For every stress editor in the dictionary of stress editors
foreach (KeyValuePair<int, ClassList> ed in this.stressParams)
{
// For every type of class selector
foreach (ClassSelector c in Enum.GetValues(typeof(ClassSelector)))
{
// For each of the classes within each class list within the editor
for (int i = 0; i < ed.Value.ClassLists[c].Count; i++)
{
string className = ed.Value.ClassLists[c][i].Name;
// For each double array in each class
foreach (KeyValuePair<int, double[]> t in ed.Value.ClassLists[c][i].ClassVariables.EditorParameters)
{
double[] values = this.GetParameterValues(t.Key, ed.Key, className);
BasicStressEditorVariables.AddParameters(values, ed.Value, className, t.Key);
}
}
}
}
}
}
Above shows the overall LoadParameters() method.
Below we have some code that selects 9 values from the dictionary constructed from the database, ready to be added to the array.
private double[] GetParameterValues(int paramKey, int editorKey, string className)
{
double[] values = new double[9];
for (int i = 1; i <= MaxNumberOfStressPeriods; i++)
{
Dictionary<int, DatabaseParameter> temp = this.parametersByPeriod[i];
foreach (KeyValuePair<int, DatabaseParameter> d in temp)
{
if (d.Value.ParameterKey == paramKey && d.Value.PeriodKey == i && d.Value.StressEditorKey == editorKey && d.Value.ProfileType == className)
{
values[i - 1] = d.Value.ParameterValue;
}
}
}
return values;
}
Below shows getting the destination array from the dictionary, as indexes cannot be passed by reference
public static void AddParameters(double[] values, ClassList editor, string className, int paramKey)
{
// TODO: Maybe search all lists to eliminate the need for the class selector as a parameter
// TODO: Will throw an exception when nothing is found. Handle it
ParameterClass p = null;
foreach (ClassSelector c in Enum.GetValues(typeof(ClassSelector)))
{
p = editor.ClassLists[c].FirstOrDefault(f => f.Name == className);
if (p != null)
{
break;
}
}
// TODO: Notify that could not be found
if (p == null)
{
Log.Instance.LogMessage(LogLevel.Error, $"Unable to find class {className}");
return;
}
double[] dest = p.ClassVariables.editorParameters[paramKey];
AddParameterValues(values, ref dest);
}
And here's the AddParameterValues() method:
private static void AddParameterValues(double[] values, ref double[] destination)
{
if (values.Length != destination.Length)
{
return;
}
for (int i = 0; i < values.Length; i++)
{
destination[i] = values[i];
}
}
Debugging shows that some values are being loaded into the destination array, but some aren't. Could anyone tell me why this is? Or if not, point me toward some material?
Thank you for your time
I'm not that C# specialist but looking to following code as a C programmer
private double[] GetParameterValues(int paramKey, int editorKey, string className)
{
double[] values = new double[9];
//...
return values;
}
I would assume that the lifetime of values is only within the function GetParameterValues and the function GetParameterValues delivers the caller with reference to a dead variable.
What if you change the prototype to something like
private void GetParameterValues(ref double[] values, int paramKey, int editorKey, string className)

Click on list object and display other multiple list objects in another listbox C#

I am nearly to the answer but annoyingly, not quite.
So far my code is:
private void lstIndividuals_SelectedIndexChanged(object sender, EventArgs e)
{
var individual = lstIndividuals.SelectedItem as Individual;
var tempSimilarFilesToFile1 = new HashSet<Individual>();
int Counter = 0;
foreach (KeyValuePair<int, Individual> kvpInd in _Individuals1)
{
tempSimilarFilesToFile1 = new HashSet<Individual>();
foreach (KeyValuePair<int, Individual> kvpInd2 in _Individuals2)
{
if (kvpInd.Value.name.name.ToLower() == kvpInd2.Value.name.name.ToLower())
{
Counter++;
similarInds.Add(kvpInd.Value);
if (Counter >= 1)
{
tempSimilarFilesToFile1.Add(kvpInd2.Value);
}
}
}
lstIndividuals2.DataSource = tempSimilarFilesToFile1.ToList();
lstIndividuals2.DisplayMember = "DisplayName";
lstIndividuals2.ValueMember = "id";
}
As you can probably see, the lstIndividuals2 listbox items are zooming through really fast. I would just like to click on an item in lstIndividuals
Then I would like that to display similar records found (anything that abides by the rule kvpInd.value.name.name == kvpInd2.value.name.name)
All similar items, I would like to be stored in tempSimilarFilesToFile1 and that to be the datasource for the lstIndividual2
I apologise if I have explained badly.
Thank you.
You init the tempSimilarFilesToFile1 in the outer loop every time, so you actually get a list contains the items in _Individuals2 which are the same as the final item in _Individuals1. Just try to comment the init statement in the outer loop and see if that helps.
private void lstIndividuals_SelectedIndexChanged(object sender, EventArgs e)
{
var individual = lstIndividuals.SelectedItem as Individual;
var tempSimilarFilesToFile1 = new HashSet<Individual>();
int Counter = 0;
foreach (KeyValuePair<int, Individual> kvpInd in _Individuals1)
{
// comment the statement below
//tempSimilarFilesToFile1 = new HashSet<Individual>();
foreach (KeyValuePair<int, Individual> kvpInd2 in _Individuals2)
{
if (kvpInd.Value.name.name.ToLower() == kvpInd2.Value.name.name.ToLower())
{
Counter++;
similarInds.Add(kvpInd.Value);
if (Counter >= 1)
{
tempSimilarFilesToFile1.Add(kvpInd2.Value);
}
}
}
lstIndividuals2.DataSource = tempSimilarFilesToFile1.ToList();
lstIndividuals2.DisplayMember = "DisplayName";
lstIndividuals2.ValueMember = "id";
}

Categories

Resources