Updating the existing DataGridViewComboBoxColumn Items collection - c#

We have DataGridViewComboBoxColumn in which we have four values which are fixed. During run-time when the event dataGridView1_EditingControlShowing occurs we are trying to append new items to DataGridViewComboBoxColumn.
private void dataGridView1_EditingControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e)
{
ComboBox combo = e.Control as ComboBox;
if (combo != null)
{
combo.DropDown += new System.EventHandler(ComboBox1_DropDown);
}
}
private void ComboBox1_DropDown(object sender, System.EventArgs e)
{
ComboBox comboBox = (ComboBox)sender;
if (comboBox.Items != null)
{
List<String> elementname = Elements();
foreach (string s in elementname)
{
if (!comboBox.Items.Contains(s))
{
comboBox.Items.Add(s);
}
}
}
}
I am getting this exception :
Can you please suggest how to add the values to the existing DataGridViewComboBoxColumn in Items collection.

You are adding into the Items of editing control but not into the Items of the ComboBoxColumn, which will eventually be used for validation.
To get easy access to the hosting DGV we use the special type DataGridViewComboBoxEditingControl.
Now we will add the new choices to both Items collections now in the ComboBox1_DropDown event: :
DataGridViewComboBoxEditingControl comboBox = (DataGridViewComboBoxEditingControl)sender;
DataGridView dgv = comboBox.EditingControlDataGridView;
if (comboBox.Items != null && dgv != null)
{
DataGridViewComboBoxColumn dcbc =
(DataGridViewComboBoxColumn) dgv.Columns[dgv .CurrentCell.ColumnIndex];
List<String> elementname = Elements.ToList();
foreach (string s in elementname)
{
if (!comboBox.Items.Contains(s))
{
comboBox.Items.Add(s);
dcbc.Items.Add(s);
}
}
}
Note:
The new Items will not be persistet unless you code for it.
So if you have set fields to new values and saved them, you must also save and reload those new values before re-loading the DGV or else the values will not be in the Column's list of values and throw the DataError again!
The usual place to store them would be a DataTable in your DBMS, but any other external storage may also be used like an XML file or maybe dynamic resources etc.. But the DBMS is the most natural choice imo.

Related

A self-learning C# DataGridView Combobox?

I would like to have a column of ComboBoxes in a DataGridView, which allows the user to freely input some text, which is collected in the dropdown menus, so that entering the same text in the next box is faster. I'd prefer not to use DataGridViewComboBoxColumn, unless I really have to.
The following code nearly does the job but has these issues:
After entering some new text and hitting return, the newly entered text is immediately replaced with the old value
But the new text is successfully added to the dropdown menus of all of the comboboxes
when I select this newly added text in one of the boxes, I get DataGridView-Exceptions complaining about an invalid value.
It seems the boxes somehow have for validation purposes a copy of the datasource which doesn't get updated?
public partial class Form1 : Form
{
List<string> data = new List<string>(); // shared data source for all ComboBoxes
private void checkData(string s) // check wether s in the list, add it if not, keep things sorted
{
if (data.Contains(s))
return;
data.Add(s);
data.Sort();
}
private void addCell(string s) // add a new cell to the grid
{
checkData(s);
DataGridViewComboBoxCell c = new DataGridViewComboBoxCell();
c.DataSource = data;
c.Value = s;
int i = theGrid.Rows.Add();
theGrid.Rows[i].Cells[0] = c;
}
public Form1()
{
InitializeComponent();
theGrid.ColumnCount = 1;
addCell("Foo");
addCell("Bar");
}
// handler to enable the user to enter free text
private void theGrid_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
ComboBox cb = e.Control as ComboBox;
if (cb != null)
cb.DropDownStyle = ComboBoxStyle.DropDown;
}
// handler which adds the entered text to the data source
private void theGrid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
if (e.RowIndex < 0 || e.ColumnIndex < 0)
return;
checkData(e.FormattedValue.ToString());
}
}
After some tests, I am guessing that the individual combo boxes are not getting updated as you think they are. It appears that in the checkData method, that the data is updated with a new s. This will visually update the combo box cell, however the DataSource for each of the combos needs to be updated. Hence the DataError exception when the new added value is selected.
Considering that each combo box cell is “independent” and not part of a DataGridViewComboBoxColumn… then a loop through all the rows is necessary to update each combo box cell. I am not sure why a DataGridViewComboBoxColumn would not be used here.
private void checkData(string s) // check wether s in the list, add it if not, keep things sorted
{
if (data.Contains(s))
return;
data.Add(s);
data.Sort();
// now because each cell is independent... we have to update each data source!
UpdateCombos();
}
private void UpdateCombos() {
foreach (DataGridViewRow row in theGrid.Rows) {
if ((!row.IsNewRow) && (row.Cells[0].Value != null)) {
string currentValue = row.Cells[0].Value.ToString();
DataGridViewComboBoxCell c = new DataGridViewComboBoxCell();
c.Value = currentValue;
c.DataSource = data;
row.Cells[0] = c;
}
}
}
Using the posted code, A call to UpdateCombos is added to the checkData method. This method as expected loops through all the rows in the grid and replaces each combo box with the updated data. I will not disagree that it may be prudent to replace the data source, however I would use a combo box column, which the code below does. With this change, the UpdateCombos is not needed and simply update the combo box column.
The DataGridViewComboBoxColumn is exposed since the data source is updated frequently.
private List<string> comboData;
private DataGridViewComboBoxColumn comboColumn;
private void Form2_Load(object sender, EventArgs e) {
comboData = new List<string>();
comboData.Add("Foo");
comboData.Add("Bar");
comboColumn = new DataGridViewComboBoxColumn();
comboColumn.DataSource = comboData;
theGrid2.Columns.Add(comboColumn);
theGrid2.RowCount = 3;
}
private void checkData2(string s) {
if (!comboData.Contains(s)) {
comboData.Add(s);
comboData.Sort();
comboColumn.DataSource = null;
comboColumn.DataSource = comboData;
}
}
Hope that helps

Resort DataGridView after adding to the bound list

I have a DataGridView that is bound to a BindingSource that is bound to a BindingList. I can sort the DataGridView by clicking on a column header, as expected. But when I drop an item onto the DataGridView, it appears at the bottom of the list rather than in its correct sorted position. I found code in a StackOverflow grid to check the grid's SortOrder and SortedColumn properties, and if they are set, then to resort the grid. However, when I put a breakpoint in the DragDrop handler, I find that SortOrder is set to None and SortedColumn is null.
Here's the DragDrop handler:
private void dgvCoils_DragDrop(object sender, DragEventArgs e)
{
int? coilStack = m_draggedCoil.Stack;
m_currentCharge.RemoveCoil(m_draggedCoil);
if (coilStack.HasValue)
{
DisplayStack(coilStack.Value);
}
if (!m_inventoryList.Any(coil => coil.Coil_id == m_draggedCoil.Coil_id))
{
m_inventoryList.Add(m_draggedCoil);
if (dgvCoils.SortOrder != SortOrder.None && dgvCoils.SortedColumn != null)
{
ListSortDirection dir = ListSortDirection.Ascending;
if (dgvCoils.SortOrder == SortOrder.Descending) dir = ListSortDirection.Descending;
dgvCoils.Sort(dgvCoils.SortedColumn, dir);
}
}
}
And here is the code where I create the list and bind the source to it:
InventorySet coilSet = new InventorySet(m_db);
coilSet.FilterOnField(coilSet.m_archived, 0);
coilSet.Open();
// coilBindingSource.DataSource = coilSet.UnderlyingTable;
while (!coilSet.IsEOF())
{
m_inventoryList.Add(coilSet.ExportData());
coilSet.MoveNext();
}
coilBindingSource.DataSource = m_inventoryList;
coilBindingSource.ResetBindings(false);
dgvCoils.Refresh();
I am not setting any Sort columns in my BindingSource object. Do I need to?
And do I need to use a BindingSource if the underlying list in a BindingList?
Edit:
In looking back through my source code, I remembered that I had to add code to my ColumnHeaderMouseClick event handler to get sorting to work. Here is that handler:
private void dgvCoils_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
string strColumnName = dgvCoils.Columns[e.ColumnIndex].DataPropertyName;
SortOrder strSortOrder = GetSortOrder(dgvCoils, e.ColumnIndex);
if (strSortOrder == SortOrder.Ascending)
{
m_inventoryList = new BindingList<InventorySetData>(m_inventoryList.OrderBy(x => typeof(InventorySetData).GetProperty(strColumnName).GetValue(x, null)).ToList());
}
else
{
m_inventoryList = new BindingList<InventorySetData>(m_inventoryList.OrderByDescending(x => typeof(InventorySetData).GetProperty(strColumnName).GetValue(x, null)).ToList());
}
dgvCoils.DataSource = m_inventoryList;
dgvCoils.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = strSortOrder;
}
I am guessing that I am doing too much programmatically, and not letting the DataGridView do what it was designed to do.

Set datasource of particular cell's combobox based on another column of particular row

private void dgGrid_CellListSelect(object sender, CellEventArgs e)
{
if (e.Cell.Column.Key == "ColumnA")
{
UltraGridRow selectedItem = ((UltraCombo)e.Cell.EditorControlResolved).SelectedRow;
if (selectedItem != null)
{
//Option A
cmbColumnB.DataSource = GetUISender<someBF>().RetrieveData(dataset).dataTable;
cmbColumnB.DataBind();
//Option B
//((UltraCombo)e.Cell.Row.Cells["ChipSetID"].EditorControlResolved).DataSource = GetUISender<someBF>().RetrieveData(dataset).dataTable;
}
}
}
There is a button that allow the datagrid to add new row.
This datagrid have 2 columns and both columns are UltraCombo. ColumnB combobox's dataSource will based on ColumnA. Based on the above code, it works if the datagrid only have 1 row, but once user add another row, both row's ColumnB will be sharing the same DataSource.
How to make sure ColumnB's DataSource stay independently without affecting other rows? It's very obvious that this happened because every row are sharing the same component which is cmbColumnB but I'm not sure on how to remove the reference
I've found the solution which is everytime create a new UltraCombo and bind it to the particular cell's EditorControl
private void dgGrid_CellListSelect(object sender, CellEventArgs e)
{
if (e.Cell.Column.Key == "ColumnA")
{
UltraGridRow selectedItem = ((UltraCombo)e.Cell.EditorControlResolved).SelectedRow;
if (selectedItem != null)
{
UltraCombo cmbValue = new UltraCombo();
cmbValue.LimitToList = true;
cmbValue.DropDownStyle = UltraComboStyle.DropDownList;
cmbValue.DataSource = GetUISender<someBF>().RetrieveData(dataset).dataTable;
cmbValue.ValueMember = someDS.someDT.someColumnIDColumn.ColumnName;
cmbValue.DisplayMember = someDS.someDT.someColumnDescriptionColumn.ColumnName;
cmbValue.BindingContext = someDg.BindingContext;
cmbValue.DataBind();
e.Cell.Row.Cells["ColumnB"].EditorControl = cmbValue;
e.Cell.Row.Cells["ColumnB"].Style = Infragistics.Win.UltraWinGrid.ColumnStyle.DropDownList;
}
}
}

Search from selected values of comboboxes windows forms C#

I have like 10 combobox in a form and what i need to do is if a item from any combobox (may be 1 or more comboboxes) is selected, search if theres a coincidence in an string array and the value(s) selected from the combobox(es) but exclude the comboboxes that have not been selected or changed, I tried using if but its not enough or efficient way to do this I think, I was just wondering if theres any way to do this.
You should be able to store the information on your class in a way that you can determine if the current ComboBox from the rest of the others. I cam up with a small sample that tracks the changes that have been made to the ComboBox using an event handler on SelectedIndexChanged and used LINQ in order to filter out and exclude the current ComboBox.
List<ComboBox> _allComboBoxes;
ComboBox _comboBox1;
ComboBox _comboBox2;
string[] _values;
void InitForm()
{
// TODO Implement values.
_values = new string[0];
_allComboBoxes = new List<ComboBox>();
_comboBox1 = new ComboBox();
_allComboBoxes.Add(_comboBox1);
_comboBox1.SelectedIndexChanged += ComboBoxSelectedIndexChanged;
_comboBox2 = new ComboBox();
_allComboBoxes.Add(_comboBox2);
_comboBox2.SelectedIndexChanged += ComboBoxSelectedIndexChanged;
}
void ComboBoxSelectedIndexChanged(object sender, EventArgs e)
{
if (HasCoincidence(sender as ComboBox, _values, _allComboBoxes))
{
throw new InvalidOperationException("Coincidence occurred.");
}
}
bool HasCoincidence(ComboBox comboBox, string[] values, IEnumerable<ComboBox> allComboBoxes)
{
IEnumerable<ComboBox> excludedComboBoxes = allComboBoxes.Where(c => c != comboBox);
throw new NotImplementedException("Implement ComboBox to string[] comparison.");
}

C# WPF ListView with GridView - add/remove columns context menu

I'm trying to add a context menu to a ListView (using GridView) to toggle column visibility. It's a simple menu - it just has a list of all the available columns, and if the column name is checked, that column should be visible.
My ListView is displayed with a GridView. The GridViewColumnHeader for each column has a Name property and Header property that match with a ContextMenu MenuItem with the same Name and Header values. My problem is that I can't figure out how to select a GridViewColumn by its GridViewColumnHeader's name without iterating through all the columns.
I can select a column by index, but that won't work since the column index changes if the columns are reordered by the user.
Is there a way to select a column by header data? I'm thinking something like this:
MenuItem m = e.Source as MenuItem;
GridView gv = (GridView)list.View;
GridViewColumn gvc = gv.Columns[m.Name];
//code to make width 0 or Auto goes here...
Looks like there is no built in method for this.
You can use LINQ expression:
var col = gv.Columns.FirstOrDefault(it => it.Header == "name");
but it will iterate through the columns inside. I believe iterating will be pretty fast.
The other way to avoid iterating completely is to subscribe on CollectionChanged event and save mapping between column header and column. But this is tricky way.
private Dictionary<object, GridViewColumn> _columns = new Dictionary<object, GridViewColumn>();
public MainWindow()
{
InitializeComponent();
gv.Columns.CollectionChanged += Columns_CollectionChanged;
}
private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if((e.Action==NotifyCollectionChangedAction.Remove
|| e.Action==NotifyCollectionChangedAction.Replace)
&& e.OldItems!=null)
{
foreach(GridViewColumn oldItem in e.OldItems)
{
if(_columns.ContainsKey(oldItem.Header)) _columns.Remove((oldItem.Header));
}
}
if(e.NewItems!=null)
{
foreach(GridViewColumn newItem in e.NewItems)
{
_columns[newItem.Header] = newItem;
}
}
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
try
{
gv.Columns.CollectionChanged -= Columns_CollectionChanged;
}
catch { }
}
private GridViewColumn GetColumn(string header)
{
GridViewColumn column = null;
_columns.TryGetValue(header, out column);
return column;
}

Categories

Resources