Moving Item In Observable Collection resets moved item to default values - c#

I have a Listview that contains Comboboxes, a textbox, and a check box. I can add multiple rows, and each time a row is added you get a new row in the list view that contains all 5 of those items. I have a default prepended to the beginning of each of the Comboboxes to prompt the user to select a value from the dropdownlist that it is bound to. The problem is when I try to reorder the rows the move event resets all the values to their default values, clears out the textbox and unchecks the check box.
public ObservableCollection<RemovePunctuationRules> PunctuationRules
{
get
{
return _punctuationRules ?? (_punctuationRules = new ObservableCollection<RemovePunctuationRules>());
}
set
{
_punctuationRules = value;
}
}
private void MenuItemMoveUp_OnClick(object sender, RoutedEventArgs e)
{
var selectedIndex = ListView.SelectedIndex;
if (ListView.SelectedIndex > 0)
{
var itemToMoveUp = PunctuationRules[selectedIndex];
PunctuationRules.RemoveAt(selectedIndex);
PunctuationRules.Insert(selectedIndex - 1, itemToMoveUp);
ListView.SelectedIndex = selectedIndex - 1;
}
}

Related

C# Fail to set DatagridComboboxCell value to null

I am trying to make a datagridview in which there exists a combobox column, and this column is binded a list of options. My logic is that, if the user selects the wrong option, the program could detect and deselect this wrong option(which means comboboxCell.value = null).
The psuedo code for this logic is as follows:
//use CurrentCellDirtyStateChanged and CellValueChanged as event listeners
this.DGV.CurrentCellDirtyStateChanged += DGV_CurrentCellDirtyStateChanged;
this.DGV.CellValueChanged += DGV_CellValueChanged;
//CurrentCellDirtyStateChanged event
private void DGV_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (DGV.IsCurrentCellDirty)
{
DGV.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
//CellValueChanged event
private void DGV_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (!CheckingCorrect(DGV.CurrentCell))
{
// set selected value null
DGV.CurrentCell.Value = null
}
}
However, after the code sets currentcell value to null, the datagridview cell still kept the selected value shown.
I also tried may other ways like DGV.ClearSelection(), but none of these works.
There are two things that will make working with the DataGridViewComboBoxColumn/Cell easier...
Make a Class specifically for the combo box column
Use the proper event to help in casting the DataGridViewComboBoxCell to a
regular ComboBox.
In this example I made a simple Class called ComboValues that has two properties for the column ValueMember and DisplayMember. In this example the ValueMember is an int, however you could leave it out or use some other type that fits your needs. This class will simplify things as we will have the properties for both the ValueMember and DisplayMember for the combo box column.
This ComboValues Class may look something like…
public class ComboValues {
public int Value { get; set; }
public string Display { get; set; }
}
Then a method to get a List<ComboValues> from the DB that we will use as a DataSource for the combo box column.
private List<ComboValues> GetComboValues() {
List<ComboValues> items = new List<ComboValues>();
items.Add(new ComboValues { Value = 0, Display = null });
items.Add(new ComboValues { Value = 1, Display = "Option 1" });
items.Add(new ComboValues { Value = 2, Display = "Option 2" });
items.Add(new ComboValues { Value = 3, Display = "Option 3" });
items.Add(new ComboValues { Value = 4, Display = "Option 4" });
return items;
}
And setting up the combo box column for the grid…
private DataGridViewComboBoxColumn GetComboCol() {
DataGridViewComboBoxColumn col = new DataGridViewComboBoxColumn();
col.HeaderText = "Combo Items";
col.ValueMember = "Value";
col.DisplayMember = "Display";
col.DataSource = GetComboValues();
return col;
}
We will call the above method in the forms Load event to add the column to the Grid…
private void Form1_Load(object sender, EventArgs e) {
DGV.Columns.Add(GetComboCol());
}
Next, we want to set up the edited DataGridViewComboBoxCell to act like a REGULAR ComboBox. We can do this using the grids EditingControlShowing event. This event will fire when the user clicks into a cell and in this case if the user clicks into a combo box cell, then we want to cast that cell to a globally defined ComboBox. So, we will need a global ComboBox variable called something like… EditedComboBox.
ComboBox EditedComboBox = null;
Is what we will do is that when the grids EditingControlShowing event fires and we check to see if it is the combo box cell, then we simply cast that cell to our global EditedComboBox and wire up its SelectedIndexChanged event. Then we can capture “when” the user “changes” a value in the combo box cell. The EditingControlShowing event may look something like…
private void DGV_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
if (DGV.CurrentCell.ColumnIndex == 0) {
EditedComboBox = (ComboBox)e.Control;
if (EditedComboBox != null) {
EditedComboBox.SelectedIndexChanged += new EventHandler(ComboBox_SelectedIndexChanged);
}
}
}
Above, column index 0 is the target combo box column. All we need to do is implement and wire up the SelectedIndexChanged event and wait for the user to change the combo box value.
When the user does indeed change the combo boxes value, then our SelectedIndexChanged event will fire and it is in this event where you would “CHECK” if the selected value needs adjustments on some parameters. In this example, the condition is if the user selects “Option 3”, then that is an invalid value and we will set the cells value to null. This event may look something like…
private void ComboBox_SelectedIndexChanged(object sender, EventArgs e) {
//Debug.WriteLine("CB_SelectedIndexChanged");
if (EditedComboBox != null) {
ComboValues cv = (ComboValues)EditedComboBox.SelectedItem;
if (cv != null && !CheckingCorrect(cv.Display)) {
EditedComboBox.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
EditedComboBox.SelectedIndex = 0;
EditedComboBox.SelectedIndexChanged += new EventHandler(ComboBox_SelectedIndexChanged);//DGV.CurrentCell.Value = 0;
}
}
}
In this event, we grab (cast) the combo boxes SelectedItem to a ComboValues object. With it we can easily check what value is selected and in turn set its value to null if needed. Note “item” 0 in our List<ComboValues> is the null value, so we set the combo boxes SelectedIndex to zero (0) to make the grid display a null value.
Also, since we are “changing” the combo boxes Index, we do NOT want this event to re-fire.
Lastly, we need one more event to wire up to TURN OFF the SelectedIndex changed event to prevent it from firing more than we want. In this case we will wire up the grids CellLeave event. In that event, the user is leaving the cell and if the EditedComboBox is not null... then we know we need to un-wire the event. It may look something like…
private void DGV_CellLeave(object sender, DataGridViewCellEventArgs e) {
if (EditedComboBox != null) {
EditedComboBox.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
EditedComboBox = null;
}
}
I altered the checking code to check for a string and not necessarily a cell. The condition checker code…
private bool CheckingCorrect(string cellValue) {
if (cellValue != null && cellValue == "Option 3") {
return false;
}
else {
return true;
}
}
I hope this helps and makes sense. If you have questions, then feel free to ask.

Error when selecting second item from ListView C#

I have a list view with the multiselect property set to false. When the user clicks on it, I take the NAME property of the list view item and convert it to a decimal then feed that to a method that loads the correct record.
The code below works perfectly when I select one item regardless of how many items are in the list and regardless of which item I select.
private void ListInstruments_SelectedIndexChanged(object sender, EventArgs e)
{
ListViewItem selection = listInstruments.SelectedItems[0];
if (selection != null)
{
string strSelection = selection.Name;
SelectedInstrumentID = Convert.ToDecimal(strSelection);
LoadSelectedInstrument();
}
}
When I make a second selection (not multi-select, but a different selection from the listbox) I get an error referencing listInstruments.SelectedItems[0].
System.ArgumentOutOfRangeException Message=InvalidArgument=Value of
'0' is not valid for 'index'. Parameter name: index
Source=System.Windows.Forms
Any help would be appreciated.
It's possible, that no items are selected, and thus list.SelectedItems is empty; you are tring to get 0th item from the empty collection and thus have the exception thrown. The quick patch is
// instead of original
// ListViewItem selection = listInstruments.SelectedItems[0];
ListViewItem selection = list.SelectedItems.Count > 0
? listInstruments.SelectedItems[0] // the collection has at least one item
: null; // if the collection is empty
Or we can check if we have a selection and return when there's none
private void ListInstruments_SelectedIndexChanged(object sender, EventArgs e)
{
if (list.SelectedItems.Count <= 0)
return;
listViewItem selection = listInstruments.SelectedItems[0];
string strSelection = selection.Name;
SelectedInstrumentID = Convert.ToDecimal(strSelection);
LoadSelectedInstrument();
}

ListBox deletion

I have a listbox, with 2 buttons, new and delete. new adds an item into the list box, and the delete button should delete the item out of the list box. The list box items are tied to a class that stores user entered data from text boxes below.
private void AddListBox()
{
lstCondition.BeginUpdate();
Condition cond = new Condition("");
cond.Name = string.Format("Condition {0}", _selection.NetConditions.Count + 1);
_selection.NetConditions.Add(cond);
lstCondition.EndUpdate();
lstCondition.SelectedItem = cond;
cboNetCondition.Properties.Items.Clear();
cboNetCondition.Properties.Items.AddRange(NetCondition);
cboControlType.Properties.Items.Clear();
cboControlType.Properties.Items.AddRange(ControlType);
cboFlowRate.Properties.Items.Clear();
cboFlowRate.Properties.Items.AddRange(FlowRate);
}
private void btnNew_Click(object sender, EventArgs e)
{
AddListBox();
}
the cbo items are comboboxes, whose data gets tied in the condition class to each instance of the list box.
public frmNetConditions(Condition condo, Selection selection)
{
InitializeComponent();
_selection = selection;
lstCondition.DataSource = _selection.NetConditions;
condition = _selection.NetConditions.Count;
}
private void btnDelete_Click(object sender, EventArgs e)
{
selectedCondition = (Condition)lstCondition.SelectedItem;
cboControlType.SelectedIndex = -1;
cboNetCondition.SelectedIndex = -1;
cboFlowRate.SelectedIndex = -1;
txtFlowRate.Text = string.Empty;
txtStatPressure.Text = string.Empty;
txtDampOpening.Text = string.Empty;
txtDensity.Text = string.Empty;
cboDensity.SelectedIndex = -1;
lstCondition.Items.Remove(lstCondition.SelectedItem);
lstCondition.Refresh();
}
After pressing this delete button, the listbox, still contains the item i wish to delete, im unsure why thats the case?
Update with datasource
public List<Condition> NetConditions { get { return _netconditions; } }
As already suggested, you should bind to a BindingList<Condition> instead of a List<Condition>. This allows you to change the datasource and the control (ListBox) to get notified by your changes. The code should look like this:
lstCondition.ValueMember = "ConditionId";
lstCondition.DisplayMember = "Name";
lstCondition.DataSource = NetConditions;
After defining the binding, the correct way of operating on the ListBox items is to remove from the datasource, not the ListBox itself:
// SelectedItem should be checked for null (no selection is an option)
NetCondition.Remove((Condition)lstCondition.SelectedItem);
However, if you plan to change properties from an element (so, not the list itself), the control is notified only if your element (Condition) implements INotifyPropertyChanged interface.

C# 2 Comboboxes with identical, not twice-selectable content

I have a dialog form where the user has to selected which colums from a textfile he wants to use for drawing a graph.
If someone doesn't quite understand what I mean, please look at the following example:
The dialog opens
The user selects e.g. that the x-values of his graph shall be from the second column of the textfile
The user selects e.g. that the y-values of his graph shall be from the third column of the textfile
The user clicks "OK"
The problem I have is the following:
I want to prevent the user from selecting the same column for x and y values, which would result in a line in an angle of probably 45 degrees and make the graph useless.
Both comboboxes are filled with the same array of strings, which contains the headlines of the columns in the textfile. Getting those strings into the comboboxes works great, but:
I tried removing the item selected in one combobox from the other combobox and otherwise.
Before that, the currently selected item is stored in a variable and the items are reset to the default state, which means all headlines from the textfile.
But, as I programmatically set the index to where it was before, so that the user doesn't have to, the SelectedIndexChanged event fires and traps my code in an infinite loop.
public void setComboboxText()
{
cbX.Items.Clear();
cbY.Items.Clear();
cbX.Items.AddRange(cbText);
cbY.Items.AddRange(cbText);
}
void CbXSelectedIndexChanged(object sender, EventArgs e)
{
var item = cbX.SelectedItem;
setComboboxText();
cbX.SelectedItem = item;
cbY.Items.Remove(cbX.SelectedItem);
}
void CbYSelectedIndexChanged(object sender, EventArgs e)
{
var item = cbY.SelectedItem;
setComboboxText();
cbY.SelectedItem = item;
cbX.Items.Remove(cbY.SelectedItem);
}
The code does the following:
The currently selected item is temporarily stored
The items of the combobox are reset
The currently selected item is set to be the item stores before
The item selected in the changed box disappears from the other combobox
Any help appreciated, especially if someone could tell me if I can do what I want with another event or even without events.
Thanks in advance
I think this is what you are trying to achieve.
public partial class Form1 : Form
{
List<string> source1 = new List<string>();
List<string> source2 = new List<string>();
public Form1()
{
InitializeComponent();
for (int i = 0; i < 10; i++)
{
source1.Add("item" + i);
source2.Add("item" + i);
}
comboBox1.Items.AddRange(source1.ToArray());
comboBox2.Items.AddRange(source2.ToArray());
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox2.Items.Contains(comboBox1.SelectedItem))
{
comboBox2.Items.Clear();
List<string> updatedList = new List<string>();
updatedList = (from x in source2
where !x.Equals(comboBox1.SelectedItem)
select x).ToList<string>();
comboBox2.Items.AddRange(updatedList.ToArray());
}
}
private void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.Items.Contains(comboBox2.SelectedItem))
{
comboBox1.Items.Clear();
List<string> updatedList = new List<string>();
updatedList = (from x in source1
where !x.Equals(comboBox2.SelectedItem)
select x).ToList<string>();
comboBox1.Items.AddRange(updatedList.ToArray());
}
}
}
Make the source collections available avaiable to each combobox SelectedIndexChanged handlers
On each selection change update the source of the other combobox only if the newly selected item exists in the other combobox Items.

Hide item from Combobox temporarily

I was wondering if there was a way to temporarily hide or remove an item from a combo box that is being populated from a database?
I have two combo boxes that are being populated by the same column (To and From Machine Number)
You can't really have Motor1 connected to Motor1, and short of having to repopulate an entire combo box with each selection, I was thinking there has to be a way to temporarily hide the same selection from the second combobox.
Please let me know if you need information on how the comboboxes are being populated(code etc.)
EDIT Here is the population code for the combo boxes:
void PopulateCreateView(CableID_CreateView CView)
{
// Creates a new Model, and gets data from the Db.
CModel = new CableID_Model();
CModel.CNId = 1;
Database_Facade.Operation_Switch(OPREAD);
// Populates the form with data for the Plant Area Codes, Supplier Info and Major Equipment.
foreach (PlantAreaCode_Model Model in PlantAreaCode_Controller.PList)
{
CView.cmbAreaCode.Items.Add(Model.AreaName);
CView.lblDummy.Text = Model.AreaName;
if (CView.lblDummy.Width > CView.cmbAreaCode.DropDownWidth)
{
// Sets the width +20 to allow for the scroll bar.
CView.cmbAreaCode.DropDownWidth = CView.lblDummy.Width + 20;
}
}
foreach (SupplierID_Model Model in SupplierID_Controller.SList)
{
if (Model.CondConfig != null) { CView.cmbXsec.Items.Add(Model.CondConfig); }
if (Model.Insulation != null)
{
CView.cmbInsulation.Items.Add(Model.Insulation);
CView.lblDummy.Text = Model.Insulation;
if (CView.lblDummy.Width > CView.cmbInsulation.DropDownWidth)
{
// Sets the width +20 to allow for the scroll bar.
CView.cmbInsulation.DropDownWidth = CView.lblDummy.Width + 20;
}
}
}
foreach (MajorEquipment_Model Model in MajorEquipment_Controller.MeList)
{
CView.cmbFromLoc.Items.Add(Model.EqipmentNumber);
CView.cmbToLoc.Items.Add(Model.EqipmentNumber);
}
}
Here is the code for the MySQL Query:
public void GetCableId(CableID_Model CModel)
{
DbConnect();
try
{
MajorEquipment_Controller.MeList = new List<MajorEquipment_Model>();
mySqlCommand = mySqlConnect.CreateCommand();
mySqlCommand.CommandText = "SELECT * FROM MajorEquipment;";
mySqlReader = mySqlCommand.ExecuteReader();
while (mySqlReader.Read())
{
MajorEquipment_Controller.MeList.Add(new MajorEquipment_Model
{
EqipmentNumber = Convert.ToString(mySqlReader["EquipmentNumber"])
});
}
mySqlReader.Close();
mySqlCommand.ExecuteNonQuery();
}
catch (MySqlException e) { MessageBox.Show(e.Message); }
finally
{
if (mySqlConnect != null)
{
mySqlConnect.Close();
}
}
}
Unfortunately there is no simple way to keep a ListBox / ComboBox item hidden.
What you can do is simplfy the logic you are using to Load item onto the List. Since both the ComboBox are list of machines that are bound to be dependent on each other
To keep a hidden item you have to create your custom controls that only renders the visible item from collection.
A pseudocode solution would be
On DropDownListChange
Get Selected Item
for all other drop down lists
restore items from backup
if selected value is not default
remove current item from list box

Categories