so my problem is that I want to display a table inside another table in WPF.
I use a DataTable in order to display some data and there is one column, in which I need to display another DataTable. I set AutoGenerateColumns="True". For a little testing, this is what I wrote (well, it works as expected):
var curDataTable = new DataTable();
curDataTable.Columns.Add("name" , typeof(string));
curDataTable.Columns.Add("number", typeof(int));
DataRow curRowData = curDataTable.NewRow();
curRowData[0] = "jones";
curRowData[1] = 90;
curDataTable.Rows.Add(curRowData);
Now, let's say I already have a filled DataTable _dataTable. I now want to display this _dataTable in my second column. This is what i would expect to work, but what does not work:
var curDataTable = new DataTable();
curDataTable.Columns.Add("name" , typeof(string));
curDataTable.Columns.Add("table", typeof(DataTable));
DataRow curRowData = curDataTable.NewRow();
curRowData[0] = "jones";
curRowData[1] = _dataTable;
curDataTable.Rows.Add(curRowData);
Has anyone an idea how to fix this?
Using nested DataTables won't work.
What you should do is to replace the outer DataTable with a custom type and bind to a custom collection property of this type in a DataGridTemplateColumn.
I don't think that nested DataTables are possible...
What you can do is create new column in '_datatable' with the name of "name"
_datatable.Columns.Add("name" , typeof(string)).SetOrdinal(0);
Using SetOrdinal(0) you can make "name" column first column. Now you maybe able to add new columns to it.
I hope it helps
Related
I'm trying to create a DataGridView bound to a DataTable where one column is a ComboBox. The code runs but I get the following error after binding (not when data is bound): System.ArgumentException: DataGridViewComboBoxCell value is not valid.
In the DataGridView one of the columns is a DataGridViewComboBoxColumn that uses an enum (named structureType) as it's source:
// ColumnStructure
//
this.ColumnStructure.ValueType = typeof(structureType);
this.ColumnStructure.DataSource = Enum.GetValues(typeof(structureType));
this.ColumnStructure.HeaderText = "Structure";
this.ColumnStructure.Name = "ColumnStructure";
this.ColumnStructure.DataPropertyName = "Structure";
//
When I populate the DataGridView without using a DataTable, it works just fine:
structureType? structure = GetStructure(part);
dgvObjectTypes.Rows.Add(name, type, structure, count);
Now I would want to use a DataTable instead, but can't get it to work. The DataTable is created as follows:
DataTable table = new DataTable();
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Type", typeof(string));
table.Columns.Add("Structure", typeof(DataGridViewComboBoxCell));
table.Columns.Add("Count", typeof(int));
Other columns work great but I can't get the "Structure" column to work. Here's how I've tried to create the combobox:
var cb = new DataGridViewComboBoxCell();
cb.ValueType = typeof(structureType);
cb.DataSource = Enum.GetValues(typeof(structureType));
cb.Value = (structureType)structure;
After that I just create the rows for the table and and set the table as datasource for the DataGridView:
table.Rows.Add(name, type, cb, count);
dgv.DataSource = table;
I've read a lot of posts where it has been stated that using enums in comboboxes causes problems (this for example: DataGridView linked to DataTable with Combobox column based on enum), but that doesn't seem to be the case here. I even tried to use explicitly typed arrays of strings but still get the same error. I think I'm doing something wrong with the DataGridViewComboBoxCell.
What could be the problem?
It seems like the step you are missing is providing the Names and Values for the CBO. The DataTable can store the value, and the DGV can display the related name, but you need to help provide the translation.
private enum structureType
{ None, Circle, Square, Pyramid}
...
dtStruct = new DataTable();
dtStruct.Columns.Add("Name", typeof(string));
dtStruct.Columns.Add("Type", typeof(string));
dtStruct.Columns.Add("Structure", typeof(structureType));
dtStruct.Columns.Add("Count", typeof(int));
// autogen columns == true
dgv2.DataSource = dtStruct;
// create DataSource as list of Name-Value pairs from enum
var cboSrc = Enum.GetNames(typeof(structureType)).
Select( x => new {Name = x,
Value = (int)Enum.Parse(typeof(structureType),x)
}
).ToList();
// replace auto Text col with CBO col
DataGridViewComboBoxColumn cb = new DataGridViewComboBoxColumn();
cb.ValueType = typeof(structureType);
cb.DataSource = cboSrc;
cb.DisplayMember = "Name"; // important
cb.ValueMember = "Value"; // important
cb.HeaderText = "Structure";
cb.DataPropertyName = "Structure"; // where to store the value
dgv2.Columns.Remove(dgv2.Columns[2]); // remove txt col
dgv2.Columns.Add(cb);
cb.DisplayIndex = 2;
// add data
dtStruct.Rows.Add("Ziggy", "Foo", structureType.Circle, 6);
The first part creates the DataTable, note that the Structure column type is structureType (or often int). The DataTable will be storing data, not DataGridViewComboBoxCell elements. In cases where the data comes from a database, that column would be int since structureType would not be a known type.
A DataSource is then created from the enum names and values. This provides the means for the control to show the name, yet store the value in the DataTable.
If the DGV is set to auto generate columns, you will need to replace the default TextBoxColumn with a ComboBoxColumn. This is being done after the DataSource has been set, but before any data is added. When the data comes from a DB (and so there is not usually an empty, typed table) you can use the ColumnAdded event to swap out one column for another.
The important thing when adding the CBO column is to set the ValueMember and DsiplayMember properties to provide the Value <-> Name translation and DataPropertyName so it knows where to store the selected value in the DataTable.
I have a datatable filled with a report from a web service. I am now trying to display the datatable in an datagridview. This is the code I use to build the datatable:
// Create DataTabe to handle the output
DataTable dt = new DataTable();
dt.Clear();
dt.Columns.Add("EmployeeFirstName");
dt.Columns.Add("EmployeeLastName");
dt.Columns.Add("DepartmentName");
dt.Columns.Add("DepartmentCode");
dt.Columns.Add("LocationName");
dt.Columns.Add("DivisionCode");
dt.Columns.Add("EarningName");
dt.Columns.Add("OTHours");
dt.Columns.Add("WorkDate")
Fill the new datatable:
foreach (ReportRow row in report.Rows)
{
dt.Rows.Add(string.Join(",", row.ColumnValues));
}
Then I try to bind the data in the datatable to the dataGridview:
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = dt;
dataGridView1.Refresh();
When I run the application it only displays the data from the first column in the datatable. Do I need a loop of sorts to work through the columns or am I just missing a step?
Yes that's cause you are adding only one value to your dt when you say dt.Rows.Add(string.Join(",", row.ColumnValues));. You should be doing something like below (assuming that ReportRow also has the columns with same names like "EmployeeFirstName" else change the names accordingly)
foreach (ReportRow row in report.Rows)
{
DataRow dr = dt.NewRow();
dr["EmployeeFirstName"] = row["EmployeeFirstName"];
dr["EmployeeLastName"] = row["EmployeeLastName"];
dr["DepartmentName"] = row["DepartmentName"];
//rest of the columns fill
//once all columns filled
dt.Rows.Add(dr);
}
dt.Rows.Add(string.Join(",", row.ColumnValues)); -> You can either add a single DataRow item or a array of objects.
From your call, you chose the later, you are adding a array of objects, except you are adding ONE SINGLE object.
string.Join(",", row.ColumnValues) is one object.
Well after sleeping I have found the issue with dropping it into an sql table... I didn't take into account that the export to a CSV and the addition of the " , " would affect the export to sql. Here is the modification of the lines of code that was the issue:
foreach (ReportRow row in report.Rows)
{
dt.Rows.Add(row.ColumnValues);
}
Thank you all for your responses!
I got problem with WPF DataGrid.
I try to empty datagrid, add columns and fill it with data from database , and than hide 1 column which is id that user don't need to see.
But when i try, it says that DataGrid columnsCount is 0, even after adding everything at the end of method.
I even tried to hide it in another method after doing 1st one, but still same error.
So my trick to do it is very messy and i want to learn how to do it in better way.
this is my simple code inserting columns etc. to DataGrid:
private void insertSubjects()
{
DataSet dataSet = DBConnect.Instance.getSubjects();
DataTable subjects = new DataTable();
subjects.Columns.Add("id", typeof(int));
subjects.Columns.Add("Przedmiot", typeof(string));
foreach(DataTable table in dataSet.Tables)
{
foreach(DataRow row in table.Rows)
{
subjects.Rows.Add(row["id"], row["name"]);
}
}
subjectsTable.ItemsSource = subjects.DefaultView;
new Thread(new ThreadStart(hideColumns)).Start();
}
My fixing here is new thread that executing method, which execute another one because I need to do it by Dispatcher.Invoke which looks like this:
private void hideColumns()
{
while (subjectsTable.Columns.Count == 0)
{ }
this.Dispatcher.Invoke(hideColumns2);
}
private void hideColumns2()
{
subjectsTable.Columns[0].Visibility = Visibility.Hidden;
}
Problem is when I had on 1 Page or Window , 2 other DataGrids, and if I want to hide columns in both of them, i had to fill them in one method.
I tried to find answer for this, but I failed, and fixed in my way.
The way you've programmed it, I would recommend creating a datacolumn object then inserting that datacolumn object into datatable. With that you can reference the datacolumn using its location.
...
DataTable subjects = new DataTable();
DataColumn idColumn = new DataColumn("id", typeof(int));
// You can set various properties of the DataColumn.
// MSDN Documentation: https://msdn.microsoft.com/en-us/library/x1tyd60z%28v=vs.110%29.aspx
subject.Columns.Add(idColumn);
Now you should be able to access the specific column to reference the actual DataColumn object. It's a pseudo binding trick that I've used in the past.
How can I copy 1 data column from 1 data table to a new datatable. When I try to do it, I get the error Column 'XXX' already belongs to another DataTable.?
dataColumn = datatable1.Columns[1];
datatable2 = new DataTable();
datatable2.Columns.Add(dataColumn);
Thanks in Advance
You cannot copy DataColumns. What you'll need to do is create a new DataColumn in the new datatable with the same data type as in the old datatable's column, and then you need to run a FOR loop to bring in all the data from the old datatable to the new datatable.
See the following code. This assumes that the datatables have exactly the same number of rows.
DataTable dt1 = new DataTable();
DataTable dt2 = new DataTable();
dt2.Columns.Add("ColumnA", dt1.Columns["ColumnA"].DataType);
for (int i = 0; i < dt1.Rows.Count; i++)
{
dt2.Rows[i]["ColumnA"] = dt1.Rows[i]["ColumnA"];
}
Also, If the data you are copying are reference types and not value types you might want to see if a .Clone() method is available for the type, or make one yourself. Just doing 'this = that' in the FOR loop will not work on reference types.
You cannot copy a DataColumn. (DataColumns are very tightly coupled with their tables)
Instead, you can add a new column with the same name and datatype.
You might be looking for DataTable.Clone(), which will create a structual copy of an entire table. (With the same schema, but no data)
Just a thought, are your DataTables both in the same DataSet?
If so, you can create a named DataRelation between the columns of two tables (think foreign key).
Then you can add a Calculated DataColumn to your table that has its Expression property set to "Child(RelationName).ColumnName" or "Parent(RelationName).ColumnName" depending on the direction of the relationship.
This will give you the same effect as copying the column, but I believe it only evaluates it lazily. So maybe it will give you what you need.
There is an example here of how this works. The example uses the Sum aggregate function, but you just need to reference the column name and it will duplicate it in your DataTable
myDataSet.Relations.Add(
"Orders2OrderLines",
myDataSet.Tables["Orders"].Columns["OrderID"],
myDataSet.Tables["OrderLines"].Columns["OrderID"]);
ordersTable.Columns.Add("OrderTotal", typeof(decimal), "Sum(Child(Orders2OrderLines).ExtendedPrice)");
HTH
The problem is caused by the c# can not reuse the object instance created and uses it on multiples DataTables. For this it is necessary to create a new object DataCollumn for each loop iteration.
foreach (DataTable table in DATASET.Tables)
{
DataColumn yourDataCollumn = new DataColumn("Name of DataCollumn", typeof(Your data type));
// your logic here
}
Hope it's help...
I used the below to merge two tables using mostly LINQ and only looping through the rows at the end. I wouldn't call it pretty but it does work. Using the join to prevent some of the assumptions listed above.
DataTable tableOne = getTableOne();
DataTable tableTwo = getTableTwo();
var oneColumns = tableOne.Columns.Cast<DataColumn>()
.Select(p => new Column(p.ColumnName, DataType))
.ToArray();
var twoColumns = tableTwo.Columns.Cast<DataColumn>()
.Select(p => new DataColumn(p.ColumnName, p.DataType))
.ToArray();
var matches = (from a in tableOne.AsEnumerable()
join b in tableTwo.AsEnumerable() on a["column_name"] equals b["column_name"]
select a.ItemArray.Concat(b.ItemArray)).ToArray();
DataTable merged = new DataTable();
merged.Columns.AddRange(oneColumns);
merged.Columns.AddRange(twoColumns);
foreach (var m in matches) { merged.Rows.Add(m.ToArray()); }
No looping required , Refer this , Hope this should solve your problem...
DataTable dt = new DataTable();
//fill the dt here
DataTable dt2 = new DataTable();
string[] strCols = {"Column Name to copy"};
dt2 = dt.DefaultView.ToTable("newTableName", false, strCols);
I'm beginner in .Net ,so maybe my question will seem naive to some of you.
I have DataGridView table in WinForm project:
It contain three columns(image,combobox and textBox columns).
Any idea how to create and attach rows to this table?
Thank you in advance!
You create a data source, then bind the data source to the grid's DataSource property. You then add a record to your data source.
// create data source
BindingList<Shape> dataSource = new BindingList<Shape>();
// add record to data source
dataSource.Add(new Shape("Some Contour", "Circle", "Some Name"));
// bind data source
yourDataGridView.DataSource = typeof(BindingList<Shape>);
yourDataGridView.DataSource = dataSource;
Set the DataPropertyName of each column to matches the names of the fields in your Shape class.
DataGridViewTextBoxColumn colName = new DataGridViewTextBoxColumn();
colName.DataPropertyName = "Name";
yourDataGridView.Columns.Add(colName );
However, I recommend you use Virtual Mode instead to keep your data separate and decoupled.
If you wish to accept inputs from user, you have to create a form on this page using which the user can provide inputs. Take those values and add them to a DataTable. Following is a sample snippet showing it:
DataTable dt = new DataTable();
dt.Columns.Add("Contour",typeof(string)); //I am assuming that you will store path
//of image in the DataTable
dt.Columns.Add("Shape",typeof(string));
dt.Columns.Add("Name",typeof(string));
Keep adding new rows to the DataTable as you receive inputs from the user:
DataRow row = dt.NewRow();
row["Contour"] = txtContourPath.Text;
row["Shape"] = ddlShape.SelectedValue;
row["Name"] = txtName.Text;
dt.Rows.Add(row);
Assign above DataTable to DataSource property of the GridView.
dgv.DataSource = dt;
You can use method:
dataGridView1.Rows.Insert(...)
dataGridView1.Rows.Add(...)
Jay's answer : use dataGridView1.DataSource = dataSource;
Hope I can help you.