I have a DataGridView that loads data from a file. I also want to use the DropDownFilter Column - I've used the one from Karl Erickson on Microsoft. It works all good while the columns are set in the Designer Mode.
I need to set the DGV and DropDownFilter columns programmatically since I don't always want to have all the columns with Filter. The problem is that the DGV is autogenerating its column type automatically.
The solution might be the DGV.AutoGenerateColumns = false, however this will throw a BindingSource error while adding new DropDownFilter column to the DGV, let me describe:
The DropDownFilter column has a property FilteringEnabled, where the following code returns null to the BindingSource data object, even when the DGV.DataSource contains relevant data:
BindingSource data = this.DataGridView.DataSource as BindingSource;
You can see in the image, that the DataSource is not empty, it has its columns and data as needed:
DataSource visualization
Is it possible to create the DTG with custom column type in the way I intend, or am I missing something? How can DataSource return null to BindingSource when this DataSource contains regular data?
Update 1
Minimal code to reproduce the err:
Form1.cs
// Create WinForm app, add DataGridView control and the code below
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
LoadToDtg();
}
private void LoadToDtg()
{
dataGridView1.AutoGenerateColumns = false;
// Prepare DataTable columns fill with data
DataTable dt = new DataTable();
dt.Columns.Add("Col1");
dt.Columns.Add("Col2");
dt.Columns.Add("Col3");
dt.Columns.Add("Col4");
dt.Rows.Add("ValueA1", "ValueA2", "ValueA3", "ValuA4");
dt.Rows.Add("ValueB1", "ValueB2", "ValueB3", "ValueB4");
dt.Rows.Add("ValueC1", "ValueC2", "ValueC3", "ValueC4");
// The DataSource binding could be used here too, however it yelds the same error
//dataGridView1.DataSource = dt;
foreach (DataColumn col in dt.Columns)
{
DataGridViewAutoFilterTextBoxColumn newColumn = new DataGridViewAutoFilterTextBoxColumn
{
HeaderText = col.ColumnName,
DataPropertyName = col.ColumnName
};
dataGridView1.Columns.Add(newColumn);
}
dataGridView1.DataSource = dt;
}
}
The DataGridViewAutoFilterTextBoxColum refers to already mentioned project # Microsoft page. I didn't paste it here since it has 1400+ lines of code. You can download the zip directly here
Related
I am creating a winform application and using data binding to display information about a collection of objects. I've successfully retrieved Strings and Lists from the object and displayed them on my form. I'm running into trouble displaying the object's DataTable.
The object has a DataTable property:
public DataTable Table { get; set; }
The table will be populated by reading a file, but for debugging right now I'm manually creating and populating the table like so:
private void CreateDataTable()
{
Table = new DataTable();
Table.Columns.Add("ID", typeof(int));
Table.Columns.Add("FuelKg", typeof(int));
Table.Columns.Add("Model", typeof(string));
Table.Rows.Add(1, 800, "Boeing 747");
Table.Rows.Add(2, 1023, "Airbus A380");
Table.Rows.Add(3, 62, "Cessna 162");
}
I use data binding to bind the DataTable to my DataGridView:
BindingSource bsAirplanes = new BindingSource();
BindingSource bsTable = new BindingSource();
//...additional code...
//databinding for datatable
bsTable.DataSource = bsAirplanes;
bsTable.DataMember = "Table";
dataGridView2.DataSource = bsTable;
dataGridView2.AutoGenerateColumns = true;
When I load the form on the first record everything looks great.
However, when I move to any other record, the DataGridView that's bound to my DataTable does not populate. For each column in my datatable, I get an error stating:
System.ArgumentException: Column 'ID' does not belong to the table .
at System.Data.DataRow.CheckColumn(DataColumn column)
at System.Data.DataRow.get_Item(DataColumn column, DataRowVersion version)
at System.Data.DataRowView.GetColumnValue(DataColumn column)
at System.Data.DataColumnPropertyDescriptor.GetValue(Object component)
at System.Windows.Forms.DataGridView.DataGridViewDataConnection.GetValue(Int32 boundColumnIndex, Int32 columnIndex, Int32 rowIndex)
The DataGridView is now empty except for the column names.
I know that the columns do indeed belong to my table because I've manually stepped through the code and can see them in my DataTable object. It seems like there is some issue refreshing the data when I move to the next record. Perhaps the DataGridView is only bound to the DataTable in my FIRST Airplane object?
After many rounds of trial and error, I devised this solution. I am not sure it's the best solution, but it works. If there is a better way I'd love to know!
My suspicion that the bsTable BindingSource was linked only to the first object's DataTable seems to be correct. I need to manually update the bsTable's DataSource when I change the main object.
I added an event listener to my main DataGridView (bound to bsAirplanes). When the user selects a different row in the grid, the secondary DataGridView (bound to bsTable) is re-bound to the current Airplane's DataTable and the correct data populates.
In the Form1.Designer file:
this.dataGridView1.SelectionChanged += new System.EventHandler(this.dataGridView1_Changed);
In the Form.cs file:
private void dataGridView1_Changed(object sender, EventArgs e)
{
bsTable.DataSource = bsAirplanes.Current; //notice I am using bsAirplanes.Current, not bsAirplanes
bsTable.DataMember = "Table";
dataGridView2.DataSource = bsTable;
dataGridView2.AutoGenerateColumns = true;
}
I solved an error similar to this error by clearing and reseting the BindingSource before re-assigning its DataSource property like the following:
bsTable.DataSource = null;
bsTable.ResetBindings(true);
// ...
// recreate or reload myDataTable
// if I did not clear and reset the BindingSource,
// the following line throws the DataGridView.DataError
bsTable.DataSource = myDataTable.DefaultView;
Hard to post code, but easy to describe how to repro:
Create a new Winforms project (I used VS 2017 and .NET 4.7)
Add a DataSet to the project.
Within the DataSet, add a DataTable (no adapter).
Within the DataTable, add a single column. It will default to varchar, but change it to NOT allow nulls.
Add a Form to the project.
Add a DataGridView to the form.
Set the DataSource for the DGV to the DataTable in the DataSet.
Run the program.
Click in the single column of the single row.
Type some content.
Select all the content and hit backspace.
Click in the empty second row that appeared.
What happens: A System.Data.NoNullAllowedException is raised indicating that the data column does not allow DBNull.Value.
What should happen: The column in the row bound to the first row of the dgv should be set to blank, not null.
What I've tried:
Using a CellEndEdit handler to catch the bad value and convert it to string.Empty. This does actually fix SOME cases (when there's more than one column)
Using a CellValidating handler - not helpful
Using a CellFormatting handler - not helpful
Using a CellParsing handler - not helpful
Using an AddingNew handler on the BindingSource to supply new rows that have non-null values - not helpful
Any other ideas?
The DataGridView can be configured to accommodate the desired usage case to send an Empty.String when a null input is received.
From: DataGridViewCellStyle.DataSourceNullValue Property
Gets or sets the value saved to the data source when the user enters a null value into a cell.
From: DataGridViewColumn.DefaultCellStyle Property
Gets or sets the column's default cell style.
The above properties are used to configure the DataGridView. However, the DataTable needs a slight modification.
From: DataColumn.DefaultValue Property
Gets or sets the default value for the column when you are creating new rows.
You might think that the DataGridVierwColumn's CellTemplate's DefaultNewRowValue Property would supply this, but not in the case of a bound column. If additional columns are used and this property is not changed from the default, the original issue would resurface.
The following example is designed to handle your issue as described in your reproduction guidelines and assumes the DataGridview.DataSource is set to either a DataTable or DataView instance. It uses the DataGridView.BindingContextChanged event to process the DataGridView's auto-generated columns after setting the DataSource property.
public partial class Form1 : Form
{
private DataTable dt;
public Form1()
{
InitializeComponent();
dataGridView1.BindingContextChanged += new System.EventHandler(this.dataGridView1_BindingContextChanged);
dt = new DataTable();
DataColumn dc = dt.Columns.Add("C0");
dc.AllowDBNull = false;
dataGridView1.DataSource = dt.DefaultView;
}
private void dataGridView1_BindingContextChanged(object sender, EventArgs e)
{
if (dataGridView1.DataSource != null)
{
DataTable boundTable = dataGridView1.DataSource as DataTable;
if (boundTable == null)
{
DataView dv = dataGridView1.DataSource as DataView;
if (dv != null)
{
boundTable = dv.Table;
}
}
if (boundTable != null)
{
foreach (DataGridViewColumn c in dataGridView1.Columns)
{
if (c.IsDataBound)
{
DataColumn dc = boundTable.Columns[c.DataPropertyName];
if (!dc.AllowDBNull && dc.DataType == typeof(string))
{
c.DefaultCellStyle.DataSourceNullValue = string.Empty;
dc.DefaultValue = string.Empty; // this value is pulled for new rows
}
}
}
}
}
}
}
This looks like an acceptable solution - tweak as needed to get rid of hard-wired column indexes, etc.
private void dataGridView1_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
var dgv = sender as DataGridView;
if (null != dgv)
{
var row = dgv.Rows[e.RowIndex];
var drv = row.DataBoundItem as DataRowView;
if (null != drv)
{
var dr = drv.Row as DataSet1.DataTable1Row;
if (dr.IsNull(0))
{
dr.Path = string.Empty;
}
}
}
}
I had a similar problem and solved it by adding the 'ConvertEmptyStringToNull' property on my parameters in aspx page, like this
<UpdateParameters>
<asp:Parameter Name="myparam" ConvertEmptyStringToNull="false" />
</UpdateParameters>
This is my first answer so hope that's OK, it was so simple I though I would post to help others searching
Good day. I have passed a data variable from one class to another to put into a datagridview in the main form. I put some message boxes in each case to know that it accesses the said function and that the data is clearly passed. But when I run the program. The table doesn't put the data inside it.
Here is the code when I pass the data
if (txtCode1.ElementAt(intCtr + 1).Equals(val4)) {
MessageBox.Show("Lol");
Compilourdes_GUI cmp = new Compilourdes_GUI();
cmp.AddtotblLexeme(val2, val2);
break;
}
And here is the code of AddtotblLexeme
public void AddtotblLexeme(string lexeme, string token) {
MessageBox.Show(lexeme+" "+token);
tblLexeme.Rows.Add(lexeme , token); //adding tokens and lexeme to the table
}
Code where I made the DataTable
private void Start()
{
tbl1.AutoGenerateColumns = true;
tbl1.DataSource = null;
tbl1.Rows.Clear();
InitTable();
string txtCode1 = txtCode.Text;
LexicalAnalyzer lex = new LexicalAnalyzer(txtCode1);
lex.StartLex();
tbl1.DataSource = tblLexeme;
}
public void InitTable()
{
tblLexeme = new DataTable();
tblLexeme.Columns.Add("Lexeme", typeof(string));
tblLexeme.Columns.Add("Token", typeof(string));
}
DataTable tblLexeme = new DataTable();
Here is the image of the output . the "TEST" word/s should be inside the table, but as you can see, it didn't get put in.
Ok I think I understand your problem. If you added the columns directly in the designer, my guess is that you added unbound columns. If so, then the DataGridView cannot match up the row you are adding to the rows in the table. To fix this, delete the columns from the DatagridView. Then make sure that your DataGridView has property AutoGenerateColumns = true, before setting DataSource = tblLexeme. Now two things happen automatically: firstly the DataGridView picks up the columns from your DataTable; and secondly, when adding a new row to the DataTable, it should show automatically in the DataGridView.
In AddtotblLexeme, for testing purposes, can you please add, in place of your Rows.Add():
DataRow nR = tblLexeme.NewRow();
nR[0] = lexeme;
nR[1] = token;
tblLexeme.Rows.Add(nR);
Then in debugger check that nR does have an ItemArray with 2 columns.
I have a datagridview bound to a datasource, all headers are added to the columns collection with a datapropertyname set.
When I clear the datagridview using
DataGridView1.DataSource = null;
The headers disappear also and when I fill the datagridview again the header texts are the database column names. How do I clear a bound datagridview without removing the headers?
You can use this
if (dataGridViewNotas.DataSource != null)
((DataTable) dataGridViewNotas.DataSource).Rows.Clear();
One of the approach to handle this issue is to use collection as data source,
Create a class with properties representing the data source (Each property would represent a column in the database)
public class Student
{
public string Name { get; set; }
public string Address { get; set; }
}
You need Add column to datagridview manually and set relevant DataPropertyName for each column and set the HeaderText. When you load the data from database first fill this data into a List. So you will have a List<Student>.
List<Student> studentDetail = new List<Student>();
Set this as the data source of the datagridview.
dataGridView1.DataSource = studentDetail;
Clearing Data source
To clear the data source of the Grid just create a empty Student list and set it as data source again. When you set like this header of each column will be retained.
List<Student> emptyStudentDetail = new List<Student>();
dataGridView1.DataSource = emptyStudentDetail;
If you do not want to use collection of objects and still refresh your data source using this.dataGridView1.DataSource = null; then try this approach:
Assuming you are using for data source either DataSet or local database. Each time before you bind the dataGridView with new data source, create unbound columns with the same names as the names of your data source. Once they are created, you should hide them, because they will be needed when you refresh dataGridView's data source. The following sample code is aware of the data source columns names and therefore they are hard coded, but you can loop each time through the data source and create new collection of unbound columns, if it is necessary.
private void Form1_Load(object sender, EventArgs e)
{
this.dataGridView1.DataSource = GetDataSet();
this.dataGridView1.DataMember = "Students";
this.dataGridView1.Columns.Add("unboundColumn1", "ID");
this.dataGridView1.Columns.Add("unboundColumn2", "Name");
this.dataGridView1.Columns["unboundColumn1"].Visible = false;
this.dataGridView1.Columns["unboundColumn2"].Visible = false;
}
private void button1_Click(object sender, EventArgs e)
{
this.dataGridView1.Columns["unboundColumn1"].Visible = true;
this.dataGridView1.Columns["unboundColumn2"].Visible = true;
this.dataGridView1.DataSource = null;
}
private DataSet GetDataSet()
{
DataSet dataSet = new DataSet();
dataSet.Tables.Add("Students");
dataSet.Tables["Students"].Columns.Add("ID", typeof(int));
dataSet.Tables["Students"].Columns.Add("Name", typeof(string));
dataSet.Tables["Students"].Rows.Add(1, "John Joy");
dataSet.Tables["Students"].Rows.Add(2, "Ivan Nova");
dataSet.Tables["Students"].Rows.Add(3, "Michael German");
return dataSet;
}
Struggled with this for 24 hrs. Not sure why it works, but the solution that did not produce a runtime error for me is to dispose of the table adapter associated with the datagridview:
if (this.dataTableTableAdapter != null)
{
this.dataTableTableAdapter.Dispose();
}
I am coming across something strange or maybe it is the way it works and its just my lack of understand of how the datagridview works. I am dynamically adding a column when I click on a button
private void btnAssign_Click(object sender, EventArgs e)
{
if (!gvData.Columns.Contains("DepartmentId"))
{
DataGridViewTextBoxCell cell = new DataGridViewTextBoxCell();
DataGridViewColumn dc = new DataGridViewColumn();
dc = new DataGridViewTextBoxColumn();
dc.HeaderText = "DepartmentId";
dc.Name = "DepartmentId";
dc.CellTemplate = cell;
gvData.Columns.Add(dc);
}
foreach(DataGridViewRow row in gvData.SelectedRows)
{
row.Cells["DepartmentId"].Value = getSelectedValues(clbDept);
}
}
first it checks if the DepartmentId column is in the datagridview (dgv). If not then I create a new Datagridviewtextboxcolumn and sets the appropriate settings. Then adds values to the cells. SO far so good... this where is does weird things... when I sort another column, the data in the 'DepartmentId' column disappears. Data disappears in the columns that I dynamically create. Do i have to use a save method or something?
Instead of adding data directly to datagridview you need to add column to datasource and reassign the datasource to datagridview. This will resolve your sorting issue.