I'm having strange issues with the check box control in C# .Net
My code below shows all logic that is required - _itemsChecked is a private dictionary containing all of the _fixtures and whether they are true or false (checked or un checked)
What I want is to be able to search my check list whilst retaining those which have been checked previously. If a checked item is included in the search results I want it to be checked.
The code nearly works! But for some reason boxes are randomly checked here and there, and it appears to work through debug but when the screen returns to the control it then hasn't worked.
Sure I'm missing something very simple.
My logic is:
DataSource includes those which match the typed search query,
Iterate through this list and check if the Guid is true in the dictionary.
If it is true then we set it as checked.
Hope I have provided adequate information.
Many thanks in advance.
private void searchTextBox_KeyUp(object sender, EventArgs e)
{
lst.DataSource = _fixtures
.OrderBy(f =>
f.Description)
.Where(f =>
f.Description.ToLower().Contains(searchFixturesTextBox.Text.ToLower()))
.ToList();
lst.DisplayMember = "Description";
for (var i = 0; i < lst.Items.Count; i++)
if(_itemsChecked.Contains(new KeyValuePair<Guid, bool>(((Fixture)lst.Items[i]).Guid, true)))
lst.SetItemChecked(i, true);
}
void lst_ItemCheck(object sender, ItemCheckEventArgs e)
{
var selectedItem = ((ListBox) sender).SelectedItem as Fixture;
if (selectedFixtureItem != null)
_itemsChecked[selectedItem.Guid] = e.CurrentValue == CheckState.Unchecked;
}
So I put this together from a few examples I found. The majority of the work came from How do I make a ListBox refresh its item text?
public class Employee
{
public string Name { get; set; }
public int Id { get; set; }
public bool IsChecked { get; set; }
public override string ToString()
{
return Name;
}
}
public partial class Form1 : Form
{
// Keep a bindable list of employees
private BindingList<Employee> _employees;
public Form1()
{
InitializeComponent();
// Load some fake employees on load
this.Load += new EventHandler(Form1_Load);
// Click once to trigger checkbox changes
checkedListBox1.CheckOnClick = true;
// Look for item check change events (to update there check property)
checkedListBox1.ItemCheck +=
new ItemCheckEventHandler(CheckedListBox_ItemCheck);
}
// Load some fake data
private void Form1_Load(object sender, EventArgs e)
{
_employees = new BindingList<Employee>();
for (int i = 0; i < 10; i++)
{
_employees.Add(new Employee()
{ Id = i, Name = "Employee " + i.ToString() });
}
// Display member doesnt seem to work, so using ToString override instead
//checkedListBox1.DisplayMember = "Name";
//checkedListBox1.ValueMember = "Name";
checkedListBox1.DataSource = _employees;
// Another example databind to show selection changes
txtId.DataBindings.Add("Text", _employees, "Id");
txtName.DataBindings.Add("Text", _employees, "Name");
}
// Item check changed, update the Employee IsChecked property
private void CheckedListBox_ItemCheck(object sender, ItemCheckEventArgs e)
{
CheckedListBox clb = sender as CheckedListBox;
if (clb != null)
{
Employee checked_employee = clb.Items[e.Index] as Employee;
if (checked_employee != null)
{
checked_employee.IsChecked = (e.NewValue == CheckState.Checked);
}
}
}
// Just a simple test that removes an item from the list, rebinds it
// and updates the selected values
private void btnChangeList_Click(object sender, EventArgs e)
{
_employees.RemoveAt(1);
checkedListBox1.DataSource = _employees;
for (var i = 0; i < checkedListBox1.Items.Count; i++)
{
Employee employee_to_check = checkedListBox1.Items[i] as Employee;
if (employee_to_check != null)
{
checkedListBox1.SetItemChecked(i, employee_to_check.IsChecked);
}
}
}
}
Related
When I add an item into a listbox I also add a new line because I want there to be a blank line between each item added. When I remove a selected item I also want to remove the blank line I added otherwise I will end up getting 2 blank lines between each item this is the problem I am having so I thought if I could delete the selected item as well as the blank line above and below the selected item this would work. Is there a better approach to this?
ListBox1.Items.Remove(ListBox1.SelectedItem);
I have typed the items and differentiate what is the blank item and what is the value item. At the time of deleting I have the reference of both. It worked fine, see if it helps.
Here's an example:
Form:
private void Form1_Load(object sender, EventArgs e)
{
Data data = new Data { description = "Test1" };
listBox1.Items.Add(data);
data.BlankLine = new BlankItem();
listBox1.Items.Add(data.BlankLine);
data = new Data { description = "Test2" };
listBox1.Items.Add(data);
data.BlankLine = new BlankItem();
listBox1.Items.Add(data.BlankLine);
data = new Data { description = "Test3" };
listBox1.Items.Add(data);
data.BlankLine = new BlankItem();
listBox1.Items.Add(data.BlankLine);
data = new Data { description = "Test4" };
listBox1.Items.Add(data);
data.BlankLine = new BlankItem();
listBox1.Items.Add(data.BlankLine);
}
Event to delete the item on click:
private void listBox1_Click(object sender, EventArgs e)
{
if((listBox1.SelectedItem != null && listBox1.SelectedItem.GetType() != typeof(BlankItem)))
{
Data item = (Data)listBox1.SelectedItem;
listBox1.Items.Remove(item);
listBox1.Items.Remove(item.BlankLine);
}
}
Object Data
public class Data
{
public string description { get; set; }
public BlankItem BlankLine { get; set; }
public override string ToString()
{
return description;
}
}
Object BlankItem
public class BlankItem
{
public override string ToString()
{
return Environment.NewLine;
}
}
I wanted to try to implement the above functionality, but using a data-bound Listbox such that I make changes to the underlying list instead of the Listbox. If possible, use BindingList<T> instead of List<T> because it implements additional functionality specific to data binding.
The core is still the same, as each item added must also be followed by adding a string.Empty item. The same for removal, when an item is removed
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var list = new BindingList<string>();
list.Add("ABC");
list.Add(string.Empty);
list.Add("GHK");
list.Add(string.Empty);
list.Add("OPQ");
listBox1.DataSource = list;
var binding = listBox1.BindingContext[list] as CurrencyManager;
listBox1.KeyDown += (s, ev) =>
{
if (ev.KeyData == Keys.Delete)
{
if (listBox1.SelectedItem != null && !listBox1.SelectedItem.Equals(string.Empty))
{
int index = listBox1.SelectedIndex;
if (index >= 0)
{
list.RemoveAt(index);
if (index < list.Count && list[index].Equals(string.Empty))
{
list.RemoveAt(index);
}
binding.Refresh();
}
}
}
if (ev.KeyData == Keys.Insert)
{
int index = listBox1.SelectedIndex;
if (index==-1 || list[index] == string.Empty)
{
index++;
}
list.Insert(index, "NEW " + (index + 1).ToString());
list.Insert(index+1, string.Empty);
}
};
}
}
press the [DEL] key to remove an item, and the [INS] key to add an item.
But I am not happy with this solution. I think there is a way to create a class that implements IListSource that you directly add/remove items and it creates a list with blanks in between automatically for binding.
I am trying to store multiple values from numerous buttons so I can return values of two or more things e.g. if chocolate and vanilla clicked both prices and names can be returned. I will also need to make calculations on the data set later. Whenever I return the data only the most recent values return rather than all of those I have selected.
private void VanillaBtn_Click(object sender, RoutedEventArgs e)
{
items.Price = 450;
items.Name = "Vanilla"
}
private void ChocolateBtn_Click(object sender, RoutedEventArgs e)
{
items.Price = 500;
items.Name = "Chocolate";
}
This is my class, any help or tips would be appreciated.
class Items
{
private int thePrice;
private string theName;
public int Price
{
get
{
return thePrice;
}
set
{
thePrice = value ;
}
}
public string Name
{
get
{
return theName;
}
set
{
theName = value;
}
}
Keep a list of whatever was clicked.
private List<Items> selectedItems = new List<Items>();
So, every time something is clicked, you store the object in the list defined above.
private void VanillaBtn_Click(object sender, RoutedEventArgs e)
{
var newItem = new Items();
newItem.Price = 450;
newItem.Name = "Vanilla";
selectedItems.Add(newItem);
}
I have bound to my combobox this simple class:
public class Company
{
public Guid CorporationId { set; get; }
public Guid TokenId { set; get; }
public string Name { set; get; }
}
And this is my binding:
private void FillCompaniesComboBox()
{
_doneLoadingComboBox = false;
comboBox_Companies.Items.Clear();
if (CurrentSettings.AllCompanies.Count == 0)
{
return;
}
bindingSource1.DataSource = CurrentSettings.AllCompanies;
comboBox_Companies.DataSource = bindingSource1.DataSource;
comboBox_Companies.DisplayMember = "Name";
comboBox_Companies.ValueMember = "CorporationId";
comboBox_Companies.SelectedIndex = 1;
_doneLoadingComboBox = true;
}
When I attempt to get the value of the selected item, I'm getting different results. Here is the code I am using to get my value:
private void comboBox_Companies_SelectedIndexChanged(object sender, EventArgs e)
{
if (!_doneLoadingComboBox && comboBox_Companies.SelectedIndex == -1)
{
return;
}
var value = (Company)comboBox_Companies.SelectedValue;
Console.WriteLine("Value: " + value.CorporationId);
}
Here is what is happening:
This one works at intended:
And this is were it is causing an issue:
Am I not retrieving the data correctly? I need the Company information that it is bound to.
Okay so here's what you need to do...
Assuming that your CurrentSettings.AllCompanies is an IList<Company> that you've already populated with data, here's what your code should look like:
public class ComboBoxItem {
// your class
private Company Comp;
}
private readonly BindingSource _bsSelectedCompany = new BindingSource();
private readonly ComboBoxItem _comboBoxItem = new ComboBoxItem();
// your main form method
public MainForm() {
// initialization code...
InitializeComponent();
// prevents errors in case your data binding objects are empty
ResetComboBox(comboBox1);
comboBox1.DataBindings.Add(new Binding(
"SelectedItem",
_bsSelectedCompany,
"Comp",
false,
DataSourceUpdateMode.OnPropertyChanged
));
comboBox1.DataSource = CurrentSettings.AllCompanies;
comboBox1.DisplayMember = "Name";
}
// simple method for resetting a given combo box to a default state
private static void ResetComboBox(ComboBox comboBox) {
comboBox.Items.Clear();
comboBox.Items.Add("Select a method...");
comboBox.SelectedItem = comboBox.Items[0];
}
By doing this, you're able to just use _comboBoxItem to safely get the information about your selected item without having to potentially Invoke it (in the case of accessing it on a separate thread).
I need to implement cascading ComboBoxes in few DataGridViews. As a proof of concept I have put together the code below. 3 Columns (Customer, Country, City) When selecting Country, City should populate but it doesn't work.
Is there a better way to achieve this and fix what I am doing wrong?
public partial class Form1 : Form
{
private List<Customer> customers;
private List<Country> countries;
private List<City> cities;
private ComboBox cboCountry;
private ComboBox cboCity;
public Form1()
{
InitializeComponent();
countries = GetCountries();
customers = GetCustomers();
SetupDataGridView();
}
private List<Customer> GetCustomers()
{
var customerList = new List<Customer>
{
new Customer {Id=1,Name = "Jo",Surname = "Smith"},
new Customer {Id=2,Name = "Mary",Surname = "Glog"},
new Customer {Id=3,Name = "Mark",Surname = "Bloggs"}
};
return customerList;
}
private List<Country> GetCountries()
{
var countryList = new List<Country>
{
new Country {Id=1,Name = "England"},
new Country {Id=2,Name = "Spain"},
new Country {Id=3,Name = "Germany"}
};
return countryList;
}
private List<City> GetCities(string countryName)
{
var cityList = new List<City>();
if (countryName == "England") cityList.Add(new City { Id = 1, Name = "London" });
if (countryName == "Spain") cityList.Add(new City { Id = 2, Name = "Madrid" });
if (countryName == "Germany") cityList.Add(new City { Id = 3, Name = "Berlin" });
return cityList;
}
private void SetupDataGridView()
{
dataGridView1.CellLeave += dataGridView1_CellLeave;
dataGridView1.EditingControlShowing += dataGridView1_EditingControlShowing;
DataGridViewTextBoxColumn colCustomer = new DataGridViewTextBoxColumn();
colCustomer.Name = "colCustomer";
colCustomer.HeaderText = "CustomerName";
DataGridViewComboBoxColumn colCountry = new DataGridViewComboBoxColumn();
colCountry.Name = "colCountry";
colCountry.HeaderText = "Country";
DataGridViewComboBoxColumn colCity = new DataGridViewComboBoxColumn();
colCity.Name = "colCity";
colCity.HeaderText = "City";
dataGridView1.Columns.Add(colCustomer);
dataGridView1.Columns.Add(colCountry);
dataGridView1.Columns.Add(colCity);
//Databind gridview columns
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCountry"]).DisplayMember = "Name";
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCountry"]).ValueMember = "Id";
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCountry"]).DataSource = countries;
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCity"]).DisplayMember = "Name";
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCity"]).ValueMember = "Id";
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCity"]).DataSource = cities;
foreach (Customer cust in customers)
{
dataGridView1.Rows.Add(cust.Name + " " + cust.Surname);
}
}
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
//register a event to filter displaying value of items column.
if (dataGridView1.CurrentRow != null && dataGridView1.CurrentCell.ColumnIndex == 2)
{
cboCity = e.Control as ComboBox;
if (cboCity != null)
{
cboCity.DropDown += cboCity_DropDown;
}
}
//Register SelectedValueChanged event and reset item comboBox to default if category changes
if (dataGridView1.CurrentRow != null && dataGridView1.CurrentCell.ColumnIndex == 1)
{
cboCountry = e.Control as ComboBox;
if (cboCountry != null)
{
cboCountry.SelectedValueChanged += cboCountry_SelectedValueChanged;
}
}
}
void cboCountry_SelectedValueChanged(object sender, EventArgs e)
{
//If category value changed then reset item to default.
dataGridView1.CurrentRow.Cells[2].Value = 0;
}
void cboCity_DropDown(object sender, EventArgs e)
{
string countryName = dataGridView1.CurrentRow.Cells[1].Value.ToString();
List<City> cities = new List<City>();
cities = GetCities(countryName);
cboCity.DataSource = cities;
cboCity.DisplayMember = "Name";
cboCity.ValueMember = "Id";
}
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e)
{
if (cboCity != null) cboCity.DropDown -= cboCity_DropDown;
if (cboCountry != null)
{
cboCountry.SelectedValueChanged -= cboCountry_SelectedValueChanged;
}
}
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
}
I wish I could easily give you a coded solution in a few lines, but I would probably have to post an entire Visual Studio project to demonstrate in code.
The idea here is that you should never try to control this kind of scenario by acting through Controls' events. Rather, you should aim to use Windows Forms' data binding mechanism. By binding the controls to a data source that is capable of letting the UI know when its state changes, you only have to modify the underlying data, and the UI will update itself accordingly.
What you need is to setup what is usually known as a ViewModel to hold the state of the various controls involved, and whatever business logic (such as setting the list of cities based on the country) should be taken care of within this ViewModel object in reaction to setting properties on it.
I invite you to search information on data binding as well as on the various .NET interfaces that participate in it. The first one is definitely INotifyPropertyChanged, which your ViewModel will need to implement to trigger changes in the UI when its state changes.
Judicious use of the BindingSource component will also facilitate your job, for example to fill the various ComboBoxes with the desired values.
Get familiar with Windows Form's data binding, and you will have much less pain in handling such scenarios.
Like I said, I wish I could demonstrate this in just a few lines of codes, and I hope that what I wrote will point you in the right direction.
Cheers
As explained in the comments above, DataGridViewComboBox related issues might become tricky (you are basically adding a different control inside an-already-pretty-complex one); and what you aim does bring this configuration to its limits. DataGridView is a control expected to ease the management of medium-complexity, data-related issues; you can get the best performance with its most defining features (e.g., textbox-based cells, events triggered after the cell has been validated, etc.). Thus, including comboboxes (or checkboxes or equivalent) cells is OK as far as you don't bring its performance to the limits. To get the best result possible for what you want (coordinating different comboboxes), I suggest you to not rely on a DataGridView control (or, at least, not for the combobox coordination part) as far as the implemention is problematic, the final result not as reliable as it can get and, in any case, the overall structure much more rigid than the one resulting from a DGV-independent approach (i.e., individual ComboBox controls).
In any case, I have felt curious about this implementation (mainly after seeing quite a few problems in my preliminary tests) and decided to write this code to answer your concern.
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.EditingControlShowing +=new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
DataGridViewComboBoxColumn curCol1 = new DataGridViewComboBoxColumn();
List<string> source1 = new List<string>() { "val1", "val2", "val3" };
curCol1.DataSource = source1;
DataGridViewComboBoxColumn curCol2 = new DataGridViewComboBoxColumn();
dataGridView1.Columns.Add(curCol1);
dataGridView1.Columns.Add(curCol2);
for (int i = 0; i <= 5; i++)
{
dataGridView1.Rows.Add();
dataGridView1[0, i].Value = source1[0];
changeSourceCol2((string)dataGridView1[0, i].Value, (DataGridViewComboBoxCell)dataGridView1[1, i]);
}
}
private void changeSourceCol2(string col1Val, DataGridViewComboBoxCell cellToChange)
{
if (col1Val != null)
{
List<string> source2 = new List<string>() { col1Val + "1", col1Val + "2", col1Val + "3" };
cellToChange.DataSource = source2;
cellToChange.Value = source2[0];
}
}
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (dataGridView1.CurrentRow != null)
{
ComboBox col1Combo = e.Control as ComboBox;
if (col1Combo != null)
{
if (dataGridView1.CurrentCell.ColumnIndex == 0)
{
col1Combo.SelectedIndexChanged += col1Combo_SelectedIndexChanged;
}
}
}
}
private void col1Combo_SelectedIndexChanged(object sender, EventArgs e)
{
if (dataGridView1.CurrentCell.ColumnIndex == 0)
{
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
changeSourceCol2(dataGridView1.CurrentCell.Value.ToString(), (DataGridViewComboBoxCell)dataGridView1[1, dataGridView1.CurrentCell.RowIndex]);
}
}
This code works fine with one limitation: when you change the index of the first combobox, the value is not immediately committed (and thus the second combobox cannot be updated). After doing some tests, I have confirmed that the proposed configuration (i.e., just writing dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit); before populating the second-combobox source delivers the best performance). Despite that, note that this code does not work perfectly on this front: it starts working (updating automatically the second combobox every time a new item is selected in the first one) from the second selection onwards; not sure about the exact reason of this but, as said, any other alternative I tried delivers a still worse performance. I haven't worked too much on this front because of my aforementioned comments (actually, doing this is not even recommendable) and because of feeling that you have to do part of the work.....
I am trying to update a SQL databound combobox with new information from a new form. After I make the adds/edits and save on the popup form, I want to refresh the combobox with the new info and select the currently added/edited item. Currently, the code below refreshes the list, but will not modify "myID" on the parent form as I thought a reference variable should. How can I most efficiently do this? I have about 20 forms to do a similar type thing.
In form1
int newid = 0;
private void addToolStripMenuItem1_Click(object sender, EventArgs e)
{
CoalSeamsForm csf = new CoalSeamsForm(ref newid);
csf.ShowDialog();
coalSeamsTableAdapter.Fill(well_Information2DataSet.CoalSeams);
coalSeamsBindingSource.Find("CoalSeamID", newid);
}
In form 2
int myID = 0;
public CoalSeamsForm(ref int myId)
{
this.myID = myId;
InitializeComponent();
}
private void CoalSeamsForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (!isOK)
{
if (DialogResult.Yes == MessageBox.Show("Would you like to save the changes?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question))
{
Save();
DataRowView drv = (DataRowView)coalSeamsBindingSource.Current;
myID = (int)drv["CoalSeamID"];
}
}
}
}
There is a problem here. I don't even know if this code compiles. (two vars with the same name?)
int myID = 0;
public CoalSeamsForm(ref int myID)
{
this.myID = 0;
InitializeComponent();
}
However your problem is that you pass by ref the myID inside the form constructor, then your code exit. When you update the var myID in Form_Closing you are no more referencing the var passed inside the constructor.
To resolve your problem declare a global property like
public property MyID {get; set;}
then in the constructor
public CoalSeamsForm()
{
this.MyID = 0;
InitializeComponent();
}
update the property when the user clicks a CONFIRM button (not in the form_closing event)
private void ConfirmButton_Click(object sender, EventArgs e)
{
...
this.MyID = (int)drv["CoalSeamID"];
this.DialogResult = DialogResult.OK;
}
and finally use the property value when calling
using(CoalSeamsForm csf = new CoalSeamsForm())
{
if(DialogResult.OK == csf.ShowDialog())
{
coalSeamsTableAdapter.Fill(well_Information2DataSet.CoalSeams);
coalSeamsBindingSource.Find("CoalSeamID", this.MyID);
}
}
Reference parameters only work within the calling method/constructor. Once they are outside of that individual method, they become copies within the second object. To implement, from the suggestions of Steve and others, I used a public property and accessed it from the first form. Also, I used a databound combobox in the posted code, which is a more typical application from me vs. setting the BindingSource.Position property.
In form1
private void addEditCoalSeams(bool isAdd)
{
int? myId=null;
if (!isAdd)
{
myId = (int?)coalSeamsComboBox.SelectedValue;
}
using (CoalSeamsForm csf = new CoalSeamsForm(myId, isAdd))
{
if (DialogResult.OK == csf.ShowDialog())
{
coalSeamsTableAdapter.Fill(well_Information2DataSet.CoalSeams);
coalSeamsComboBox.SelectedValue = csf.coalID;
}
}
}
In CoalSeamsForm
public int? coalID {get; set;}
bool isAdd = false;
public CoalSeamsForm(int? coalId, bool isAdd)
{
this.coalID = coalId;
this.isAdd = isAdd;
InitializeComponent();
}
private void okButton_Click(object sender, EventArgs e)
{
Save();
DataRowView drv = (DataRowView)coalSeamsBindingSource.Current;
this.coalID = (int)drv["CoalSeamID"];
this.DialogResult = DialogResult.OK;
}