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;
}
Related
In my datagridview, i have a textboxcolumn and an editable combobox column in winforms.But while typing the new value in combobox text and pressing the enter key, i am not getting the typed value as the corresponding cell value.Could anyone please help with this.
private void dgv_customAttributes_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
DataGridViewRow row = dgv_customAttributes.CurrentRow;
if (row.Cells[1].Value.ToString() != null)
{
//Here the selectedVal is giving the old value instead of the new typed text
string SelectedVal = row.Cells[1].Value.ToString();
foreach (CustomAttribute attribute in customAttributes)
{
if (row.Cells[0].Value.ToString() == attribute.AttributeName)
{
attribute.AttributeValue = SelectedVal;
break;
}
}
}
}
You need to find out the combo boxes as they are being shown, and attached an event handler to them for when the selected index changes (since it is not possible to obtain that information from the column or the cell themselves).
This means that, unfortunately, capturing the event CellEndEdit is useless.
In the following example, a text box is filled with the chosen option, but you can do anything else, such as selecting a specific value in your enumerated variable or whatever.
void OnEditingControlShowing(DataGridViewEditingControlShowingEventArgs e)
{
if ( e.Control is ComboBox comboEdited ) {
// Can also be set in the column, globally for all combo boxes
comboEdited.DataSource = ListBoxItems;
comboEdited.AutoCompleteMode = AutoCompleteMode.Append;
comboEdited.AutoCompleteSource = AutoCompleteSource.ListItems;
// Attach event handler
comboEdited.SelectedValueChanged +=
(sender, evt) => this.OnComboSelectedValueChanged( sender );
}
return;
}
void OnComboSelectedValueChanged(object sender)
{
string selectedValue;
ComboBox comboBox = (ComboBox) sender;
int selectedIndex = comboBox.SelectedIndex;
if ( selectedIndex >= 0 ) {
selectedValue = ListBoxItems[ selectedIndex ];
} else {
selectedValue = comboBox.Text;
}
this.Form.EdSelected.Text = selectedValue;
}
Find the complete source code for the table in which a column is a combobox in GitHub.
Hope this helps.
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;
}
}
}
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.
I need to show a datagrid inside another datagrid. I have done this by using RowDetailsTemplate and adding a datagrid in it. But the main problem is, I need to show multiple inner grids at same time. When the selected item is changed, the inner datagrid of the previous selected item is not displayed. Any suggestions :(
I am using expander control to show/hide the details. When expander control is opened, I am changing the RowDetailstemplate's visiblity to true.
When the selected item is changed, RowDetailsTemplate of the current selected row is only being visible if I expand the expander.
You can control the visibility of the row details of each row using the DataGridRow.DetailsVisibility property.
Perhaps you also need to change DataGrid.RowDetailsVisibilityMode to another value than VisibleWhenSelected
Found the answer,
private void Expander_Expanded(object sender, RoutedEventArgs e)
{
int rowIndex = this.DataGridForEvents.SelectedIndex;
List<DataGridRow> rows = GetRows();
rows[rowIndex].DetailsVisibility = Visibility.Visible;
}
private void Expander_Collapsed(object sender, RoutedEventArgs e)
{
int rowIndex = this.DataGridForEvents.SelectedIndex;
List<DataGridRow> rows = GetRows();
rows[rowIndex].DetailsVisibility = Visibility.Collapsed;
}
public List<DataGridRow> GetRows()
{
List<DataGridRow> rows = new List<DataGridRow>();
foreach (var rowItem in this.DataGridForEvents.ItemsSource)
{
this.DataGridForEvents.ScrollIntoView(rowItem, this.DataGridForEvents.Columns.Last());
FrameworkElement el = this.DataGridForEvents.Columns.Last().GetCellContent(rowItem);
DataGridRow row = DataGridRow.GetRowContainingElement(el.Parent as FrameworkElement);
if (row != null)
rows.Add(row);
}
return rows;
}
I have a DataGridView in a WinForm. I fill the DataGridView from a database table. I was wondering if there is any way to program my DataGridView so that I can choose which columns I want the gridview to show at runtime?
The simple answer is "yes".
In the first instance you need to set the AutoGenerateColumns property of the DataGridView to false then you can control which columns get displayed.
In the past I've created a context menu for the DGV:
ContextMenu = new ContextMenu();
foreach (var column in this.dataGridView.Columns)
{
this.AddContextMenuItem(ContextMenu, column.Name, column.Visible);
}
private void AddContextMenuItem(ContextMenu contextMenu,
string columnName,
bool visible)
{
var menuItem = new MenuItem(columnName,
new EventHandler(this.ContextMenu_onClick)) { Checked = visible };
contextMenu.MenuItems.Add(menuItem);
}
Then when the the menu option is toggled change the Visible property of the column.
private void ContextMenu_onClick(object sender, EventArgs e)
{
var clicked = sender as MenuItem;
if (clicked != null)
{
// Update the state of the context menu
clicked.Checked = !clicked.Checked;
// Update the visibity of this column
this.dataGridView.Columns[clicked.Text].Visible = clicked.Checked;
}
}
Use the DataGridView.AutoGenerateColumns property--set to false. Explicitly configure the columns you want, and you're good to go.