How to bind dynamic data to data grid in wpf? - c#

I have the below class as my DataModel:
public class InspectorOutput
{
string m_SymbolName;
// Each string is a column name with its value in double
List<KeyValuePair<string, double>> m_ListPrices = new List<KeyValuePair<string, double>>();
public string SymbolName
{
get
{
return m_SymbolName;
}
set
{
m_SymbolName = value;
}
}
public List<KeyValuePair<string, double>> ListPrices
{
get
{
return m_ListPrices;
}
set
{
m_ListPrices = value;
}
}
public void addResult(string strResultName, double nResult)
{
ListPrices.Add(new KeyValuePair<string, double>(strResultName, nResult));
}
}
In my xaml window data grid is defined as below:
<DataGrid x:Name="gridStockData">
</DataGrid>
Later on, in my mainwindow I have the below code:
private void runProfile(object sender, RoutedEventArgs e)
{
ObservableCollection<InspectorOutput> listOutput = null;
Profile.Profile objProfile = null;
Inspector.InspectorManager objInspectorManager = null;
try
{
// Some code here which makes a profile out of user input in objProfile
objInspectorManager = new Inspector.InspectorManager();
// Calculate data based on given profile
listOutput = objInspectorManager.startInspector(objProfile);
// Show calculated data
gridStockData.ItemsSource = listOutput;
}
catch (Exception ex)
{
Logger.getInstance().error(ex.getStackTrace());
}
}
The problem is as follows:
I have stock data for 10 companies.
Each company has a symbol name.
Calculated data for each symbol is stored in m_ListPrices where each key is a column name and each value is a cell value
Note: Columns are not known until run time(ie: Based on user's selected algorithm column names and numbers may vary).
I have a calculator class that runs user selected algorithms. Each algorithm has it's own output which stores it in above data model.
How could I possibly bind this DataModel to a DataGrid in WPF?
Currently above code gives me the following output:

This is how I got over the problem:
I create a DataTable as data source for my data grid which is filled with columns inside the dictionary.
DataTable tbl = new DataTable();
tbl.Columns.Add("Symbol Name", typeof(string));
// Add columns
foreach (InspectorOutput item in listScenarioOutput)
{
foreach (KeyValuePair<string, double> entry in item.ListPrices)
{
tbl.Columns.Add(entry.Key, typeof(double));
}
break;
}
for (int i = 0; i < listScenarioOutput.Count; i++)
{
DataRow row = tbl.NewRow();
row.SetField(0, listScenarioOutput[i].SymbolName);
int j = 0;
foreach (KeyValuePair<string, double> entry in listScenarioOutput[i].ListPrices)
{
row.SetField(++j, entry.Value);
}
tbl.Rows.Add(row);
}
gridStockData.ItemsSource = tbl.DefaultView;

Related

DataGridView generating rows without data

I've searched all over here and Google and still can't find an answer to this. I'm playing around with Amazon's API and am making a simple Windows Form to try and display the data in a DataGridView. The GridView is generating 10 rows for the 10 results I am getting, but is not filling the rows with the actual data. They're just blank.
The code below is a method (GetResults) that return a DataTable. I didn't show all of it because there is a bunch of code above to get the data.
DataTable dt = new DataTable();
dt.Columns.Add("ASIN", typeof(string));
dt.Columns.Add("Title", typeof(string));
// write out the results
foreach (var item in response.Items[0].Item)
{
Product product = new Product(item.ASIN, item.ItemAttributes.Title);
Console.WriteLine(product.ASIN);
var dr = dt.NewRow();
dr["ASIN"] = product.ASIN;
dr["Title"] = product.Title;
dt.Rows.Add();
}
return dt;
}
private void btnSearch_Click(object sender, EventArgs e)
{
dgvProducts.AutoGenerateColumns = false;
dgvProducts.DataSource = GetReults();
}
I know it is getting the info because I am writing it to console and it is showing up correctly.
I also have this basic class for the Product:
public class Product
{
private string asin;
private string title;
public Product() { }
public Product(string newAsin, string newTitle)
{
this.asin = newAsin;
this.title = newTitle;
}
public string ASIN
{
get { return asin; }
set { asin = value; }
}
public string Title
{
get { return title; }
set { title = value; }
}
I've tried setting AutoGenerateColumns = false and setting the column data bindings myself, but that didn't do anything either.
You're adding an empty row to the table, not adding your newly created row.
Change
dt.Rows.Add();
To
dt.Rows.Add(dr);

Loop through DataTable and select only certain values in column

I have a DataTable from which I would like to loop through each row and column and then select a value from a specific column depending on the other values in the columns/each row.
My code currently looks like this:
foreach (DataRow drow in dt.Rows)
{
foreach (DataColumn dcol in dt.Columns)
{
foreach (var Item in ImportData)
{
if (Item.Value.Equals(true))
{
if (Item.Key.Equals("" + dcol))
{
string value = drow[dcol].ToString();
if (value.Equals("X"))
{
outDraws += drow["Drawing"].ToString();
outDraws += "\n";
}
}
}
}
}
}
ImportData is a Dictionary<string, bool>, which holds the data that I want to compare with my DataTable.
string outDraws is just a string which should hold the content of the drawings I want to print out.
My problem now is that I only want to print out the content in the column 'Drawing' of the row where all columns with the same name as the Keys in ImportData have 'X' as value. At the moment I'm getting all the rows where any of the columns have 'X' as value and has the same name as any Key in ImportData.
I understand that it will be quite hard for you to get what I want to do but please ask if you need any more information and I will try to provide.
Many thanks in advance.
Edit:
ImportData contains the name of different products as keys. These products have either been selected or not by the customer through another program, if they have been selected they have the value true and if not selected they have the value false.
With the method presented above I would like to compare ALL the keys that have the value true with the column names in the DataTable. If the column name corresponds to the key in ImportData (which is the name of a product) then I want to check if that column in a specific row has 'X' as value.
This goes on for ALL the keys in ImportData and in the end I should know which row in the DataTable that has an 'X' in all the columns with the same name as the keys in ImportData. For this row I would like to get the content of the column called 'Drawing'.
So for an example say that ImportData contains:
[Motor, true][Product6, true][Product7, true]
Then I would like to print out the column Drawing at row 6.
Unfortunately I can't post pictures..
As with any problem: divide and conquer. Break down your problem in smaller pieces and go from there.
From what I understand, you want to do something with certain rows from the datatable. Something like:
foreach (var drow in dt.Rows.OfType<DataRow>())
{
if (SomeConditionIsMet(dt, drow, ImportData))
{
outDraws += drow["Drawing"].ToString();
outDraws += "\n";
}
}
The function SomeConditionIsMetcould looks like this:
private static bool SomeConditionIsMet(
DataTable dt, DataRow drow,
IDictionary<string, bool> importData)
{
// TODO if the condition is met, return true
// otherwise, return false
}
Now your problem is simplified to thinking about what it means that 'Some condition is met'. Once you can clearly express that in words, rename the function to reflect that (e.g. to 'AllColumnsAreChecked')
Here's a sample with solution as I understand it:
internal class Program
{
private static void Main(string[] args)
{
var importData = new Dictionary<string, bool>()
{
{"Product1", true},
{"Product2", false},
{"Product3", true},
};
var dt = new DataTable();
dt.Columns.Add("Product1");
dt.Columns.Add("Product2");
dt.Columns.Add("Product3");
dt.Columns.Add("Product4");
dt.Columns.Add("Drawing");
// row1 should be added
var row1 = dt.NewRow();
row1["Product1"] = "X";
row1["Product3"] = "X";
row1["Drawing"] = "Drawing1";
dt.Rows.Add(row1);
// row2 should not be added
var row2 = dt.NewRow();
row2["Product1"] = "X";
row2["Drawing"] = "Drawing2";
dt.Rows.Add(row2);
string outDraws = string.Empty;
foreach (DataRow drow in dt.Rows.OfType<DataRow>())
{
if (AllColumnsAreChecked(drow, importData))
{
outDraws += drow["Drawing"].ToString();
outDraws += "\n";
}
}
Console.WriteLine(outDraws);
}
private static bool AllColumnsAreChecked(DataRow drow, Dictionary<string, bool> importData)
{
foreach (var key in importData.Keys)
{
if (!importData[key])
continue;
var value = drow[key] as string;
if (value != "X")
return false;
}
}
}
Bonus: here's a LINQ based implementation of the check:
private static bool AllColumnsAreChecked(DataRow drow, Dictionary<string, bool> importData)
{
return importData.Keys
.Where(k => importData.ContainsKey(k) && importData[k]) // the field must be enabled in importData
.All(k => (drow[k] as string) == "X"); // the corresponding value in the row must be 'X'
}
Try this
DataTable tbl = new DataTable();
foreach (DataRow row in tbl.Rows)
{
object cellData = row["colName"];
}

What does the data source need to have for a GridView to autogenerate columns?

I created custom datatype classes that format the data the way I need it. The data I retrieve from the database comes as .NET base types, so I need to loop through the DataTable, convert each item into it's custom type, and put the converted item into a new table. I also copy the column names from the old table to the new one. The problem is, when I bind a GridView to my new table, it throws an exception:
HttpException:
The data source for GridView with id 'TestGrid' did not have any properties
or attributes from which to generate columns. Ensure that your data source
has content.
Stack Trace:
at System.Web.UI.WebControls.GridView.CreateAutoGeneratedColumns(PagedDataSource dataSource)
at System.Web.UI.WebControls.GridView.CreateColumns(PagedDataSource dataSource, Boolean useDataSource)
at System.Web.UI.WebControls.GridView.CreateChildControls(IEnumerable dataSource, Boolean dataBinding)
at System.Web.UI.WebControls.CompositeDataBoundControl.PerformDataBinding(IEnumerable data)
at System.Web.UI.WebControls.GridView.PerformDataBinding(IEnumerable data)
at System.Web.UI.WebControls.DataBoundControl.OnDataSourceViewSelectCallback(IEnumerable data)
at System.Web.UI.DataSourceView.Select(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback)
at System.Web.UI.WebControls.DataBoundControl.PerformSelect()
at System.Web.UI.WebControls.BaseDataBoundControl.DataBind()
at System.Web.UI.WebControls.GridView.DataBind()
What do I need to add to my DataTable to get autogeneratecolumns to work?
Edit: Here is the code for the DataTable:
// Populate first DataTable with data from database.
adapter = new DataAdapter("SELECT status, date_ordered, date_due FROM import_table", OpenConnection);
DataTable originalTable = new DataTable();
adapter.Fill(originalTable)
// Second DataTable for converted table data.
DataTable convertedTable = new DataTable();
// The list of custom datatypes to convert to.
Type[] newTypes = {typeof(TrackingStatus), typeof(Date), typeof(Date)};
// Set the ColumnName and DataType on each column of the new table.
for(int i = 0; i < originalTable.Columns.Count; i++)
{
convertedTable.Columns.Add();
convertedTable.Columns[i].ColumnName = originalTable.Columns[i].ColumnName;
if(newTypes.Length > i)
convertedTable.Columns[i].DataType = newTypes[i];
}
// Convert each item from the old table and add it to the new table.
foreach(DataRow oldRow in originalTable.Rows)
{
DataRow newRow = convertedTable.NewRow();
for(int i = 0; i < convertedTable.Columns.Count; i++)
{
if(newTypes.Length <= i)
newRow[i] = oldRow[i];
else if(newTypes[i] == typeof(Date))
newRow[i] = Date.FromObject(oldRow[i]);
else if(newTypes[i] == typeof(TrackingStatus))
newRow[i] = TrackingStatus.FromObject(oldRow[i]);
else if(newTypes[i] == typeof(EmailAddress))
newRow[i] = EmailAddress.FromObject(oldRow[i]);
}
convertedTable.Rows.Add(newRow);
}
// Bind the GridView.
displayGrid.DataSource = convertedTable;
displayGrid.DataBind();
Instead of creating a new datatable when you convert each to custom types, create a new List and bind the list to the datasource. e.g.
var list = new List<MyClass>();
var myType = //Convert your type
list.Add(myType);
grid.DataSource = list;
And ofcourse this all is just an idea of how to do it.
UPDATE:
Saw your code afterwards:
Try this:
Create a class called ImportRow
class ImportRow
{
private string m_Status = string.Empty;
private DateTime m_DateOrdered;
private DateTime m_DateDue;
public ImportRow() { }
public string Status
{
get { return m_Status; }
set { m_Status = value; }
}
public DateTime DateOrdered
{
get { return m_DateOrdered; }
set { m_DateOrdered = value; }
}
public DateTime DateDue
{
get { return m_DateDue; }
set { m_DateDue = value; }
}
}
Then use it as:
var importedData = new List<ImportRow>();
foreach (DataRow oldRow in originalTable.Rows)
{
var newRow = new ImportRow();
newRow.Status = oldRow["status"].ToString();
newRow.DateDue = Convert.ToDateTime(oldRow["date_due"].ToString());
newRow.DateOrdered = Convert.ToDateTime(oldRow["date_ordered"].ToString());
importedData.Add(newRow);
}
Then
displayGrid.DataSource = importedData;
displayGrid.DataBind();
Your custom type needs public properties like these:
public class Foo
{
public int Id;
public string Name;
}
or you need to use the DataTable itself as DataSource.

Trying to create DataGridView from key-value pairs in C#

In a Windows Form application, I'm trying to create a DataGridView with two columns: one for the key given by an XML element and one for the value of said XML element. This is my code so far:
this.myData = new DataGridView();
((System.ComponentModel.ISupportInitialize)(myData)).BeginInit();
myData.Location = new System.Drawing.Point(12, 42);
myData.Name = "myData";
myData.Size = new System.Drawing.Size(1060, 585);
myData.TabIndex = 32;
foreach (XElement xElem in xInfoItems)
{
numItems++;
}
myData.Columns.Add(new DataGridViewTextBoxColumn());
myData.Columns.Add(new DataGridViewTextBoxColumn());
myData.Columns[0].Name = "Key";
myData.Columns[0].DataPropertyName = "key";
myData.Columns[1].Name = "Value";
myData.Columns[1].DataPropertyName = "value";
List<myRow> data = new List<myRow>();
foreach (XElement xElem in xInfoItems)
{
data.Add(new myRow(xElem.Attribute("key").Value, xElem.Value));
}
myData.DataSource = data;
myData.Refresh();
this.PerformLayout();
I have confirmed that all of the information in data is being loaded via foreach, so that part is working. My problem is that the grid displays, but nothing shows up on the grid. What am I doing wrong? I'm not very good with this data type so I apologize if it's something obvious.
UPDATE
I figured out that I hadn't set myData up properly in the Design view. After adding the myRow class, it worked perfectly. Thanks for the help!
The problem may lie in your myRow class. When I was trying to reproduce your code, I first defined "key" and "value" as public fields of the myRow class as so:
public class myRow {
public string key;
public string value;
public myRow( string Key, string Value )
{
key = Key;
value = Value;
}
}
This caused the bound rows to show up but the text was not in the cells. When I changed both of them to properties, the binding worked much better:
public class myRow{
private string _key;
public string key
{
get
{
return _key;
}
}
private string _value;
public string value
{
get
{
return _value;
}
}
public myRow( string Key, string Value )
{
_key = Key;
_value = Value;
}
}
Probably the modifications I made on your code can help. (I just have focused on the part where you create the columns and add the rows, using a DataTable).
this.myData = new DataGridView();
((System.ComponentModel.ISupportInitialize)(myData)).BeginInit();
myData.Location = new System.Drawing.Point(12, 42);
myData.Name = "myData";
myData.Size = new System.Drawing.Size(1060, 585);
myData.TabIndex = 32;
foreach (XElement xElem in xInfoItems)
{
numItems++;
}
// Here we create a DataTable with two columns.
DataTable table = new DataTable();
table.Columns.Add("Key", typeof(string));
table.Columns.Add("Value", typeof(string));
foreach (XElement xElem in xInfoItems)
{
//Here we add rows to table
table.Rows.Add(xElem.Attribute("key").Value, xElem.Value);
}
myData.DataSource = table;
myData.Refresh();
this.PerformLayout();

Datatable subset of columns from another datatable

I have a datatable with 17 columns and a bunch of data.
I wnat a datatable with only 6 of the columns and the data for those 6 columns.
So I need a subset of the original datatable.
How do I loop through the original datatable with 17 columns and end up with a datatable with only the 6 columns I want with the corresponding data for those 6 columns?
Private Function createSmallCopyofExistingTable(ByVal SourceTable As DataTable) As DataTable
Dim newTable As DataTable = New DataTable()
'Copy Only 6 columns from the datatable
Dim ColumnsToExport() As String = {"ID", "FirstName", "LastName", "DateOfBirth", "City", "State"}
newTable = SourceTable.DefaultView.ToTable("tempTableName", False, ColumnsToExport)
Return newTable
End Function
Without knowing more about how generic this needs to be its really just...
foreach (DataRow dr in dt.Rows)
{
newDt.Rows.Add(dr["col1"],dr["col5"],etc);
}
what about data types, and columns? are these same? if yes, you can create
object[] row = new object[]{// Fill your rows manually};
before filling it create
DataTable dt = new DataTable();
dt.Columns.Add("Title",typeof(string etc..));.....
and finally
dt.Rows.Add(row);
Personally, I would avoid creating another instance of a DataTable.
It depends on your situation, of course, but if this is purely for usability and not for security (i.e. you're not trying to remove columns with sensitive data before transmitting it somewhere), then I would create a wrapper object that encapsulates the columns that you want to expose.
The benefit of using a wrapper is in case you are doing any updates, then you can update the source table directly rather than the copy. Whether this really matters, of course, depends on your situation.
A simple example with limited functionality:
public class MyFormOrPage
{
void UsageExample()
{
DataTable allDataTable = new DataTable();
// populate the data table with whatever logic ...
// wrap the data table to expose only the Name, Address, and PhoneNumber columns
var limitedDataTable = new DataTableWrapper(allDataTable, "Name", "Address", "PhoneNumber");
// iterate over the rows
foreach (var limitedDataRow in limitedDataTable)
{
// iterate over the columns
for (int i = 0; i < limitedDataTable.ColumnCount; i++)
{
object value = limitedDataRow[i];
// do something with the value ...
}
}
// bind the wrapper to a control
MyGridControl.DataSource = limitedDataTable;
}
}
public class DataTableWrapper : IEnumerable<DataRowWrapper>
{
private DataTable _Table;
private string[] _ColumnNames;
public DataTableWrapper(DataTable table, params string[] columnNames)
{
this._Table = table;
this._ColumnNames = columnNames;
}
public int ColumnCount
{
get { return this._ColumnNames.Length; }
}
public IEnumerator<DataRowWrapper> GetEnumerator()
{
foreach (DataRow row in this._Table.Rows)
{
yield return new DataRowWrapper(row, this._ColumnNames);
}
}
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
// if you _really_ want to make a copy of the DataTable, you can use this method
public DataTable CopyToDataTable()
{
DataTable copyTable = new DataTable();
for (int index = 0; index < this._ColumnNames.Length; index++)
{
DataColumn column = this._Table.Columns[index];
copyTable.Columns.Add(column);
}
foreach (DataRow row in this._Table.Rows)
{
DataRow copyRow = copyTable.NewRow();
for (int index = 0; index < this._ColumnNames.Length; index++)
{
copyRow[index] = row[this._ColumnNames[index]];
}
copyTable.Rows.Add(copyRow);
}
return copyTable;
}
}
// let's make this a struct, since potentially very many of these will be instantiated
public struct DataRowWrapper
{
private DataRow _Row;
private string[] _ColumnNames;
public DataRowWrapper(DataRow row, params string[] columnNames)
{
this._Row = row;
this._ColumnNames = columnNames;
}
// use this to retrieve column values from a row
public object this[int index]
{
get { return this._Row[this._ColumnNames[index]]; }
set { this._Row[this._ColumnNames[index]] = value; }
}
// just in case this is still needed...
public object this[string columnName]
{
get { return this._Row[columnName]; }
set { this._Row[columnName] = value; }
}
}

Categories

Resources