C# Search datagridview for duplicates - c#

Using ms visual studio and csharp .net4.
This is the code i have to check for duplicates
public void CheckForDuplicate()
{
DataGridViewRowCollection coll = ParetoGrid.Rows;
DataGridViewRowCollection colls = ParetoGrid.Rows;
List<string> listParts = new List<string>();
int count = 0;
foreach (DataGridViewRow item in coll)//379
{
foreach (DataGridViewRow items in colls)//143641
{
if (items.Cells[5].Value == item.Cells[5].Value)
{
if (items.Cells[2].Value != item.Cells[2].Value)
{
listParts.Add(items.Cells["Keycode"].Value.ToString());
count++;
dupi = true;
//txtDupe.Text = items.Cells["Keycode"].Value.ToString();
//this.Refresh();
}
}
}
}
MyErrorGrid.DataSource = listParts;
}
This is the check before it allows the user to save.
private void butSave_Click(object sender, EventArgs e)
{
CheckForDuplicate();
if (dupi == true)
{
txtDupe.Clear();
dupi = false;
}
else
{
SaveMyWorkI();
dupi = false;
}
}
This is the data that it is looking at:
Now, I know the logic must be flawed since it saving regardless.
I'm basically searching through each cell on pareto1 to see if the user has made any duplicates, if so it will not save and instead displays the part number etc in another datagridview....well that's the plan.
So could someone look through this and tell me
1) Where in my logic is this failing, also what about if the checks are correct?
2) Will the list work adding the info, if so is a simple bind to a datagrid view enough to display the results?
3) If this is just a really bad way of searching through could someone provide code that reflects what I am Trying to achieve.
Many thanks for your future comments.
UPDATE:: Ok thanks for the help, my algorithm now works, but my very last problem is displaying the part number that is duplicated on the pareto column, instead it displays the length.
public void CheckForDuplicate()
{
DataGridViewRowCollection coll = ParetoGrid.Rows;
DataGridViewRowCollection colls = ParetoGrid.Rows;
List<string> listParts = new List<string>();
int count = 0;
foreach (DataGridViewRow item in coll)//379
{
foreach (DataGridViewRow items in colls)//143641
{
count++;
if ((items.Cells[5].Value != null))
{
if ((items.Cells[5].Value != null) && (items.Cells[5].Value.Equals(item.Cells[5].Value)))
{
if ((items.Cells[2].Value != null) && !(items.Cells[2].Value.Equals(item.Cells[2].Value)))
{
listParts.Add(items.Cells["Keycode"].Value.ToString());
dupi = true;
}
}
}
}
}
MyErrorGrid.DataSource = listParts;
var message = string.Join(Environment.NewLine, listParts);
//MyErrorGrid.DataSource = message;
MessageBox.Show(message);
}
Even though the message box correctly displays the results? is it something im missing out when binding to my datagrid?

Here is a simple example showing how to perform validation during dataentry. There are various ways you can customise how the errors appear (including some sort of custom dialog to resolve errors) that might give you a better solution.
public partial class Form1 : Form
{
BindingSource bs;
DataTable dt; public Form1()
{
InitializeComponent();
BindingList<BindingClass> data = new BindingList<BindingClass>
{
new BindingClass{ Name = "one" },
new BindingClass { Name = "two"}
};
dataGridView1.DataSource = data;
dataGridView1.CellValidating += new DataGridViewCellValidatingEventHandler(dataGridView1_CellValidating);
}
void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (row.Index != e.RowIndex & !row.IsNewRow)
{
if (row.Cells[0].Value.ToString() == e.FormattedValue.ToString())
{
dataGridView1.Rows[e.RowIndex].ErrorText =
"Duplicate value not allowed";
e.Cancel = true;
return;
}
}
}
dataGridView1.Rows[e.RowIndex].ErrorText = string.Empty;
}
}
public class BindingClass
{
public string Name { get; set; }
}
}
Naturally this won't always fit your requirements of what users like to work with but I thought it could help to see another option.

You are doing comparisons with == and != .
items.Cells[5].Value exposes an object.
In your case this is most likely doing an equality check based on reference equality, which is probably not what your want. Try using something like items.Cells[5].Value.Equals(item.Cells[5].Value)
Please also consider solving such problems on the simplest abstractions available. E.g. if you had the grid bound to a collection of objects, then you could perform the cleanup operation against that collection of objects, disregarding any UI you bolt on top of it.
You can also consider using the Distinct extension method from the LINQ namespace and provide it an IEqualityComparer* to make sure the most efficient code for removing duplicates available in the .NET Framework is used by you.
*) IEqualityComparer is an abstraction that allows you to define in one place when you consider two objects to be equal. Distinct provides an overload where you can specify such a comparer.

See if this can work for you
var dup = dataGridView1.Rows.Cast<DataGridViewRow>().Distinct().Where(g => g.Index != 0);
Excluding row with index 0. It is header row.

Related

How do I make multiple if conditions, that returns true, execute in C#?

I have a checkedListBox where the user can select whatever they want to update. I want them to be able to freely update 1 up to 5 characteristics of a machine. So when they only want to update 1 thing, they do not have to provide the other 4 characteristics. Also, when they want to update 5 characteristics, they
can do it in one go. For that purpose I have the following if statements:
if (clbCharacteristicsToUpdate.CheckedItems.Count != 0)
{
if (clbCharacteristicsToUpdate.GetSelected(0))
{
currentITime = Convert.ToDouble(tbCurrentITime.Text);
MessageBox.Show(currentITime.ToString());
dh.UpdateCurrentITime(machineNr, currentITime);
}
if (clbCharacteristicsToUpdate.GetSelected(1))
{
cycleTime = Convert.ToDouble(tbCycleTime.Text);
MessageBox.Show(cycleTime.ToString());
dh.UpdateCycleTime(machineNr, cycleTime);
}
if (clbCharacteristicsToUpdate.GetSelected(2))
{
nrOfLinesPerCm = Convert.ToInt32(tbNrOfLinesPerCm.Text);
MessageBox.Show(nrOfLinesPerCm.ToString());
dh.UpdateNrOfLinesPerCm(machineNr, nrOfLinesPerCm);
}
if (clbCharacteristicsToUpdate.GetSelected(3))
{
heightOfLamallae = Convert.ToDouble(tbHeightOfLamallae.Text);
MessageBox.Show(heightOfLamallae.ToString());
dh.UpdateHeightOfLamallae(machineNr, heightOfLamallae);
}
if (clbCharacteristicsToUpdate.GetSelected(4))
{
if (rbLTB.Checked)
{
machineType = 2;
MessageBox.Show(machineType.ToString());
}
else if (rbSTB.Checked)
{
machineType = 1;
MessageBox.Show(machineType.ToString());
}
if(!rbLTB.Checked && !rbSTB.Checked)
{
MessageBox.Show("Select a machine type to update!");
return;
}
dh.UpdateType(machineNr, machineType);
}
}
My problem is that, when I choose and update 1 thing it works perfectly. But when I choose multiple ones, it only executes the last if statement that returns true. I thought about using if-else but then only the first one that returns true will be executed. I also thought about having if statements for each possibility. But since I have 5 characteristics I can update, this would make 25 possibilities and I do not want to have 25 if statements. Thanks in advance!
GetSelected does not check whether the item has been checked, but that it is actually selected.
For example, in the below image "Item 2" will will return true for GetSelected. It is selected, not checked.
Instead you could do something like checking the clbCharacteristicsToUpdate.CheckedItems property to get the items that are actually checked.
The GetSelected method actually comes from the ListBox class, which CheckedListBox inherits from.
Try this:
This is a helper method I created on a button.
private void button1_Click(object sender, EventArgs e)
{
CheckList();
}
This method looks if items are checked and iterates over the collection of checked ones, calling the last metod.
private void CheckList()
{
if (clbCharacteristicsToUpdate.SelectedItems.Count != 0)
{
foreach (int indexChecked in clbCharacteristicsToUpdate.CheckedIndices)
{
CheckSelectedItem(indexChecked);
}
}
}
And finally, here the appropriate actions are taken based on indexes.
private void CheckSelectedItem(int index)
{
if (index == 0)
{
//Do stuff on Zero
MessageBox.Show("Zero");
}
if (index == 3)
{
//Do stuff on One
MessageBox.Show("Three");
}
}

How to check a box in CheckedListBox while the items in the list are custom objects in a C# window form application?

I am creating an "export to excel" windows form in c#.
The class contains a CheckedListBox and a "check all" button.
When clicking on the button I want to check all the items in the list in case that at least one checkbox is not checked or uncheck all the checkboxes in case they are all already checked.
I added a small complication, the list of the items is a list of custom objects (see private class inside): "ObjectToExport" class.
public partial class ExcelCustomExportForm : Form
{
private class ObjectToExport
{
private readonly IExcelExportable _form;
public ObjectToExport(IExcelExportable form)
{
_form = form;
}
public override string ToString()
{
return $"{_form.FormName} ({_form.CreatedDate.ToShortDateString()} {_form.CreatedDate.ToShortTimeString()})";
}
}
// each form in the list contains a gridview which will be exported to excel
public ExcelCustomExportForm(List<IExcelExportable> forms)
{
InitializeComponent();
Init(forms);
}
private void Init(List<IExcelExportable> forms)
{
foreach (IExcelExportable form in forms)
{
// Checked List Box creation
FormsCheckedListBox.Items.Add(new ObjectToExport(form));
}
}
private void CheckAllButton_Click(object sender, EventArgs e)
{
// checking if all the items in the list are checked
var isAllChecked = FormsCheckedListBox.Items.OfType<CheckBox>().All(c => c.Checked);
CheckItems(!isAllChecked);
}
private void CheckItems(bool checkAll)
{
if (checkAll)
{
CheckAllButton.Text = "Uncheck All";
}
else
{
CheckAllButton.Text = "Check All";
}
FormsCheckedListBox.CheckedItems.OfType<CheckBox>().ToList().ForEach(c => c.Checked = checkAll);
}
}
The problem is that the following line returns true even if not check box is checked:
var isAllChecked = FormsCheckedListBox.Items.OfType<CheckBox>().All(c => c.Checked);
Similar issue with the following line, if checkAll is true, no check box is checked:
FormsCheckedListBox.CheckedItems.OfType<CheckBox>().ToList().ForEach(c => c.Checked = checkAll);
What is the correct way to fix those two lines of code?
Your Problem begins here.
FormsCheckedListBox.Items.Add(new ObjectToExport(form));
and
var isAllChecked = FormsCheckedListBox.Items.OfType<CheckBox>().All(c => c.Checked);
You are adding instances of 'ObjectToExport' to the FormsCheckedListBox, but while filtering, you are checking filtering with CheckBox.
This means, your filtered query always return empty, and query never reaches All. This can be demonstrated with following example.
var list = new [] { 1,2,3,4};
var result = list.OfType<string>().All(x=> {Console.WriteLine("Inside All"); return false;});
The result of above would be True, and it would never print the "Inside All" text. This is what is happening with your queries.
You can find if any of the checkbox is checked using
var ifAnyChecked = checkedListBox1.CheckedItems.Count !=0;
To change state, you could do the following.
for (int i = 0; i < checkedListBox1.Items.Count; i++)
{
if (checkedListBox1.GetItemCheckState(i) == CheckState.Checked)
{
// Do something
}
}

Loading an XML file in C# win forms

In my program, the user can save the settings with a button. The store works. Loading the settings does not work quite right. I have a problem when loading the rule. I have several rules as a list. I dont know how to specify the index. Can anybody help me please?
Methods to save/load the settings:
private void SaveUserConfigButton_Click(object sender, EventArgs e)
{
var userConfig = new UserConfig();
userConfig.RandomPopulation = (int)_probability;
userConfig.Rule = _gameOfLife.NextGenerationRule.RuleName;
userConfig.Speed = _timer.Interval;
userConfig.UseBoundary = _gameOfLife.UseBoundary;
SaveUserConfig(userConfig);
}
private void MainForm_Load(object sender, EventArgs e)
{
var userConfig = LoadUserConfig(_path);
InputRandomPopulationNumbericUpDown.Value = userConfig.RandomPopulation;
SelectRulesComboBox.SelectedItem = _rules[5]; // <-- here is the problem
SpeedTrackBar.Value = userConfig.Speed;
BoundaryCheckBox.Checked = userConfig.UseBoundary;
}
My english is not so good, I hope it is understandable.
Assuming userConfig.Rule is the name of the rule you want selected in the SelectRulesComboBox and each instance of a rule has a property named Name what you need to do is find the index of userConfig.Rule within the _rules collection.
If _rules is a List<T> then you can use the FindIndex method:
SelectedRulesCombobox.SelectedIndex = _rules.FindIndex(r => r.Name == userConfig.Rule);
Otherwise, you can just project each rule alongside its index within _rules collection and get the first one that has Name == userConfig.Rule:
SelectedRulesCombobox.SelectedIndex = _rules.Select((rule, index) => new
{
Rule = rule,
Index = index
})
.First(x => x.Rule.Name == userConfig.Rule)
.Index;
Keep in mind though that the code above will throw an exception if no rule was found with Name == userConfig.Rule.
Why not use datatable & WriteXml and ReadXml ?
void writeResults()
{
DataTable dt = new DataTable();
dt.Columns.Add("configID");
dt.Columns.Add("configValue");
//Other code you want to add
//Then add row for each setting
Datarow r = dt.NewRow();
r["configID"]= "Speed"; //e.g. Speed
r["configValue"]=_timer.Interval.ToString();
dt.Rows.Add(r);
// snip
//then save datatable to file
dt.TableName="UserConfigs";
dt.WriteXml(#"filename_goes_here");
}
To read settings from file is even easier :
void readSettings()
{
DataTable dt = new DataTable();
dt.ReadXml(#"filename_goes_here");
for(int i = 0; i < dt.Rows.Count; i++)
{
switch(dt.Rows[i][0])
{
case "Speed":
try
{
_timer.Interval=Int32.Parse(dr.Rows[i][1]);
}
catch
{
// we've got a problem !
}
break;
default:break;
}
}
}
Edit: That's not optimal way, but it can get you started. Always try/catch every single block where you're validating data from xml - never trust user input, Nuff said.

WinForms (C#) input field with database-driven autocomplete

I am trying to create a text input field with autocomplete functionality. The list of available options is huge (50,000+) and will need to be queried on TextChanged (after the first 3 characters have been entered).
I have a 99%-working solution with TextBox, setting AutoCompleteCustomSource to my new AutoCompleteStringCollection in the TextChanged event, but that results in occasional memory access violations due to a well-documented bug in the underlying AutoComplete implementation...
Microsoft Support say "Do not modify the AutoComplete candidate list dynamically during key events"...
Several SO threads: 1, 2, 3
These threads have some suggestions on how to prevent the exceptions but nothing seems to completely eliminate them, so I'm looking for an alternative. have tried switching to a ComboBox-based solution but can't get it to behave as I want.
After the user types the third character, I update the ComboBox's DataSource but the first item is automatically selected. The user is not able to continue typing the rest of the name.
The ComboBox items are not visible until the user clicks the triangle to expand the list
If the user selects the text they have entered and starts typing, I set DataSource to null to remove the list of suggestions. Doing this puts the cursor at the start of the text, so their characters appear in completely the wrong order!
My View:
public event EventHandler SearchTextChanged;
public event EventHandler InstrumentSelected;
public Instrument CurrentInstrument
{
get { return comboBoxQuickSearch.SelectedItem as Instrument; }
}
public IEnumerable<Instrument> Suggestions
{
get { return comboBoxQuickSearch.DataSource as IEnumerable<Instrument>; }
set
{
comboBoxQuickSearch.DataSource = value;
comboBoxQuickSearch.DisplayMember = "Name";
}
}
public string SearchText
{
get { return comboBoxQuickSearch.Text; }
}
private void comboBoxQuickSearch_TextChanged(object sender, EventArgs e)
{
if (SearchTextChanged != null)
{
SearchTextChanged(sender, e);
}
}
private void comboBoxQuickSearch_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter && InstrumentSelected != null)
{
InstrumentSelected(sender, e);
}
}
My Presenter:
private void SearchTextChanged(object sender, EventArgs e)
{
lock (searchLock)
{
// Do not update list of suggestions if:
// 1) an instrument has already been selected
// (the user may be scrolling through suggestion list)
// 2) a search has taken place within the last MIN_SEARCH_INTERVAL
if (DateTime.Now - quickSearchTimeStamp < minimumSearchInterval
|| (view.Suggestions != null && view.Suggestions.Any(i => i.Name == view.SearchText)))
{
return;
}
string searchText = view.SearchText.Trim();
if (searchText.Length < SEARCH_PREFIX_LENGTH)
{
// Do not show suggestions
view.Suggestions = null;
searchAgain = false;
}
// If the prefix has been entered or changed,
// or another search is needed to display the full sublist
else if (searchText.Length == SEARCH_PREFIX_LENGTH
|| searchText.Substring(0, SEARCH_PREFIX_LENGTH) != searchTextPrefix
|| searchAgain)
{
// Record the current time and prefix
quickSearchTimeStamp = DateTime.Now;
searchTextPrefix = searchText.Substring(0, SEARCH_PREFIX_LENGTH);
// Query matches from DB
IList<Instrument> matches = QueryMatches(searchText);
// Update suggestions
view.Suggestions = matches;
// If a large number of results was received, search again on the next chararacter
// This ensures the full match list is presented
searchAgain = matches.Count() > MAX_RESULTS;
}
}
}
(The searchAgain bit is left over from the TextBox implementation, where the AutoCompleteCustomSource wouldn't always show the complete list if it contained too many items.)
Can I get the ComboBox to work as I want it to, providing suggestions as the user types, given my requirement to query those suggestions on TextChanged?
Is there some other combination of controls I should use for a better user experience, e.g. ListBox?

Custom Sorting on a DataGridView

I found a few questions similar to this one here on SO, but none that matched this problem, so here we go.
I've got a DataGridView showing members of a team. All the team members have an assigned role within the team listed in one of the columns. Examples could something like be "Legal Representative", "Account Manager", "Assistant Account Manager" or "Accountant".
Now here's where it gets interesting. I basically want to sort the grid on this column alphabetically, with a couple of exceptions. The "Account Manager" should always be listed at the top, followed by the "Assistant Account Manager" if there is one.
The objects and grid are all operational at this point, and have been in production release for some time, so I don't want to do more work on this than strictly necessary.
Is there an easy way to do this? I assume I have to do it programatically...
Some pseudo-code to clarify:
if (memberRole == 'Account Manager')
{
//put in top row
}
else if (memberRole == 'Assistant Account Manager')
{
//put in second row
}
else
{
//sort remaining rows alphabetically
}
I do my work in C# .NET using Visual Studio 2008.
You can capture the SortCompare event of your datagridview:
private void dataGridView1_SortCompare(object sender, DataGridViewSortCompareEventArgs e)
{
string cell1, cell2;
if (e.Column == "Your_Column")
{
if (e.CellValue1 == null) cell1 = "";
else cell1 = e.CellValue1.ToString();
if (e.CellValue2 == null) cell2 = "";
else cell2 = e.CellValue2.ToString();
if (cell1 == "Account Manager") { e.SortResult = -1; e.Handled = true; }
else
{
if (cell2 == "Account Manager") { e.SortResult = 1; e.Handled = true; }
else
{
if (cell1 == "Assistant Account Manager") { e.SortResult = -1; e.Handled = true; }
else
{
if (cell2 == "Assistant Account Manager") { e.SortResult = 1; e.Handled = true; }
}
}
}
}
}
Or while getting the data from server side you can sort it and then pass it to the front end via w/e json, etc etc so all the list does is show the data, and the data was sorted on the server
An other solution that may not appeal to some but is fast to implement and works well is to introduce a new property on the object for sorting purposes. Make the new property contain a sorting character as the first character (a number works well) and the actual sorting value as the rest of the characters. Implement some easy if-else statements to set the appropriate value of the sorting property.
When adding this column to the grid just make it hidden and sort on that column.
Possibly a less elegant solution than the one proposed by najmeddine, but it works.

Categories