DataGridColumn Binding with Dynamically Generated Data - c#

I have a large set of data that is generated from a web service in my program. I need to display this data in a DataGrid, but the returned collections of data all have different formats (One may be 3 columns and the next 7) so I cannot create a specific object to hold this data. I am having trouble getting this information to display. Right now I am trying to use this method. I put the data into a two-dimensional list of KeyValuePairs. The Key being the Column this data would belong to, the Value being the output for that row. This is what I am trying right now.
private void SetDataValuesExecute(List<List<KeyValuePair<string,object>>> Data)
{
ResultsCollection = Data;
ValuesDataGrid.Columns.Clear();
foreach (string colName in Data[0].Select(x => x.Key).ToList())
{
DataGridTextColumn tempCol = new DataGridTextColumn();
tempCol.Header = colName;
Binding bind = new Binding();
bind.Source = ResultsCollection.Select(x => x.FirstOrDefault(y => y.Key == colName).Value.ToString()).ToList();
tempCol.Binding = bind;
ValuesDataGrid.Columns.Add(tempCol);
}
}
The ResultsCollection is a non-volatile variable to be used as the source of my bindings. It is a copy of all of the two-dimensional List data I need to construct the DataGrid.
This looks at the first entry an extracts the Column Header values and creates new columns based on that. The binding statement looks at each Row, grabs data for the specified Column, and tries to set that as the Column's Binding. This allows me to have both the column name and a list of all the data that goes in that column. Unfortunately binding the list of data to the column ends up not displaying any data.
So my question is: How do I need to format my data in order for the Column Binding to actually display data? Am I approaching this situation all wrong? Any help is appreciated as I am quite stumped.

Here's a complete working sample
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var data = new MyViewModel();
int columnIndex = 0;
foreach (var name in data.ColumnNames)
{
grid.Columns.Add(
new DataGridTextColumn
{
Header = name,
Binding = new Binding(
string.Format("Values[{0}]", columnIndex++))
});
}
DataContext = data;
}
}
public class MyViewModel
{
public MyViewModel()
{
Items = new List<RowDataItem>
{
new RowDataItem(),
new RowDataItem(),
new RowDataItem(),
};
ColumnNames = new List<string>{ "Col 1", "Col 2", "Col 3"};
}
public IList<string> ColumnNames { get; private set; }
public IList<RowDataItem> Items { get; private set; }
}
public class RowDataItem
{
public RowDataItem()
{
Values = new List<string>{ "One", "Two", "Three"};
}
public IList<string> Values { get; private set; }
}
The Xaml
<Grid>
<DataGrid x:Name="grid" ItemsSource="{Binding Items}"
AutoGenerateColumns="False"/>
</Grid>

This is a GridView but it works. I think you need to use bind.Path but with a KVP not sure about the rest of the syntax.
WPF - Display Grid of Results With Dynamic Columns/Rows

Related

Bind list to DataGridView not displaying anything and wiping out objects every time I click save

I am trying to bind a class list to a DataGridView on my WinForms application. Currently the bind code doesn't do anything. I have debugged through it and the source is correct contains the list with the correct item. But I don't see the rows in the DataGridView.
How do I display whats in the list onto my DataGridView? Also currently every time I click save it clears my class object, however I want to keep the previous values I put in the constructor. If there is already a country that exists I want a user pop up box to state whether they want to over write this or not - is this possible/ how can I best achieve this?
CountryWithDates.cs
class CountryWithDates
{
public string country;
public DateTime firstDate;
public DateTime furtherDate;
public DateTime rolldownDate;
public DateTime endDate;
}
On save click:
private void Save_Click(object sender, EventArgs e)
{
List<CountryWithDates> countryDates = new List<CountryWithDates>();
for (int i = 0; i < Countries_LB.Items.Count; i++)
{
if (Countries_LB.GetItemChecked(i))
{
countryDates.Add(new CountryWithDates(){country = Countries_LB.Items[i].ToString(),
firstMarkdownDate = DateTime.Parse(firstDatePicker.Text),
furtherMarkdownDate = DateTime.Parse(furtherDatePicker.Text),
rolldownMarkdownDate = DateTime.Parse(rolldownDatePicker.Text),
endMarkdownDate = DateTime.Parse(endMarkdownPicker.Text)});
}
}
//THIS DOESNT DO ANYTHING - I WANT TO BIND THE SOURCE TO THE GRIDVIEW but i dont see the output in the data gridview on the form when i click save
var bindingList = new BindingList<CountryWithDates>(countryDates);
var source = new BindingSource(bindingList, null);
gv_Countries.DataSource = source;
}
You should create public Porperties in your class and not public Fields:
class CountryWithDates
{
//Following the naming rule is good. I'll leave it to you.
public string country { get; set; }
public DateTime firstDate { get; set; }
public DateTime furtherDate { get; set; }
public DateTime rolldownDate { get; set; }
public DateTime endDate { get; set; }
public CountryWithDates() { }
public override string ToString()
{
return country;
}
}
This way you can bind a list of CountryWithDates objects to the CheckedListBox:
var lst = new List<CountryWithDates>();
//Add new objects ...
//lst.Add(new CountryWithDates { country = "Country 1", firstDate ... });
checkedListBox1.DataSource = null;
checkedListBox1.DataSource = lst;
To get and update the checked items from the list, create a new BindingSource, and bind it to the DataGridView, you just need to do:
checkedListBox1.CheckedItems
.Cast<CountryWithDates>()
.ToList()
.ForEach(item =>
{
item.firstDate = firstDatePicker.Value;
item.furtherDate = furtherDatePicker.Value;
item.rolldownDate = rolldownDatePicker.Value;
item.endDate = endMarkdownPicker.Value;
});
dataGridView1.DataSource = null;
var bs = new BindingSource(checkedListBox1.CheckedItems.Cast<CountryWithDates>(), null);
dataGridView1.DataSource = bs;
If I were doing this I would:
Add a new DataSet to my project
Open the DataSet designer
Add a DataTable, call it CountryWithDates, 6 columns (Country, First, Further, Markdown, End, SaveThisRow(boolean))
Save the DataSet
Open the form designer
Show the datasources window
Drag the node representing CountryWithDates onto the designer
DataGridView appears, as does dataset, bindingsource etc. Everything is bound already.
When time comes to save to disk and you only want to save those checked items, datatable.Copy() it, then delete every row that isn't marked for save, then call WriteXml to save the cleaned up copy. Perhaps something like this (untested):
private blah SaveButton_Click(blah){
var dt = (CountryWithDatesDataTable)_myDataset.CountryWithDates.Copy();
for(int i = dt.Count - 1; i >= 0; i--){
if(!dt[i].SaveThisRow)
dt[i].Delete(); //mark row deleted
}
dt.WriteXml(#"C:\temp\dt.xml")
}

How to bind a ReadOnlyCollection<T> to DataGridView

I am working with a Schneider product called ClearSCADA that allows you to query its proprietary backend database through a .Net interface. I have successfully queried the DB using this query:
SELECT
"Id", "FullName", "Foreground", "Blink", "Background", "TypeDesc", "MemoryUsage"
FROM
"CCustomColour"
ORDER BY
"FullName" ASC
and have results returned to me as a QueryResult:
using System.Collections.ObjectModel;
namespace ClearScada.Client.Advanced
{
//
public class QueryResult
{
//
// Summary:
// Gets the results of the query.
public ReadOnlyCollection<QueryRow> Rows { get; }
//
// Summary:
// Gets the number of rows affected for a DML query.
public int RowsAffected { get; }
//
// Summary:
// Gets the status of the query.
public QueryStatus Status { get; }
}
}
I have tried to bind the ReadOnlyCollection of rows to the datagrid a couple of different ways.
dataGridViewResults.DataSource = this.MyQueryResult.Rows;
and
BindingSource bs = new BindingSource();
bs.DataSource = this.MyQueryResult.Rows;
dataGridViewResults.DataSource = bs;
The grid shows me a single column showing only the row index but no data:
For reference here is the definition of the QueryRow class:
using System.Collections.ObjectModel;
namespace ClearScada.Client.Advanced
{
//
public class QueryRow
{
//
public ReadOnlyCollection<object> Data { get; }
//
public int Index { get; }
}
}
I created a local copy of my QueryResult instance so that I could capture a view of it in the 'Locals' window in Visual Studio to show here:
What I am trying to achieve is a view that displays like this:
Can you please help me by showing me how I can bind those values to the DataGridView?
I am sure there must be a better way but this is working for me by transferring the data to a DataTable and then binding that to the DataGridView:
if (this.MyQueryStatus.Equals(QueryStatus.Succeeded))
{
#region IF Succeeded
#region Create Results DataTable
DataTable resultsTable = new DataTable("Results");
resultsTable.Columns.Add("Id");
resultsTable.Columns.Add("FullName");
resultsTable.Columns.Add("Foreground");
resultsTable.Columns.Add("Blink");
resultsTable.Columns.Add("Background");
resultsTable.Columns.Add("TypeDesc");
resultsTable.Columns.Add("MemoryUsage");
#endregion Create Results DataTable
foreach (var row in this.MyQueryResult.Rows)
{
#region ForEach Row
DataRow newRow = resultsTable.NewRow();
newRow["Id"] = row.Data[0].ToString();
newRow["FullName"] = row.Data[1].ToString();
newRow["Foreground"] = row.Data[2].ToString();
newRow["Blink"] = row.Data[3].ToString();
newRow["Background"] = row.Data[4].ToString();
newRow["TypeDesc"] = row.Data[5].ToString();
newRow["MemoryUsage"] = row.Data[6].ToString();
resultsTable.Rows.Add(newRow);
#endregion ForEach Row
}
dataGridViewResults.DataSource = resultsTable;
tabControlMain.SelectedTab = tabControlMain.TabPages["tabPageResults"];
#endregion IF Succeeded
}
I would still be grateful if someone can explain how to bind the ReadOnlyCollection directly to the DataGridView.

Unit testing with XamDataGrid and Sorting event

I'm trying to unit test a class, and that class uses a XamDataGrid.
My problem is that my code should trigger a Sorted or Sorting event from the XamDataGrid, but it doesn't: what am I missing?
Here how I create the grid:
public static class XamDataGridObjectMother
{
public static XamDataGrid CreateGrid(params string[] columns)
{
var grid = new XamDataGrid();
var layout = new FieldLayout();
grid.FieldLayouts.Add(layout);
int i = 0;
foreach (var column in columns)
{
var field = new Field(column, typeof(string), column+" Label");
field.Column = i;
field.Width = new FieldLength(100);
field.Name = column;
layout.Fields.Add(field);
i++;
}
var data = new List<XamDataGridFakeItem>();
data.Add(new XamDataGridFakeItem("P1", "A1"));
data.Add(new XamDataGridFakeItem("P2", "A1"));
data.Add(new XamDataGridFakeItem("P3", "A1"));
data.Add(new XamDataGridFakeItem("P4", "A2"));
data.Add(new XamDataGridFakeItem("P5", "A2"));
data.Add(new XamDataGridFakeItem("P6", "A2"));
foreach (var xamDataGridFakeItem in data)
{
grid.DataItems.Add(xamDataGridFakeItem);
}
return grid;
}
public class XamDataGridFakeItem
{
public XamDataGridFakeItem(string portfolio, string area)
{
Portfolio = portfolio;
Area = area;
}
public string Portfolio { get; set; }
public string Area { get; set; }
}
}
And here is the part of my unit test where I programmaticaly add a sorting to the grid, and where it should raise a Sorted/Sorting event (probably both):
var sortDescription = new FieldSortDescription("Portfolio", ListSortDirection.Descending, false);
_dataGrid.FieldLayouts[0].SortedFields.Add(sortDescription); // That should trigger the event(s)!
Of course _dataGrid is initialised somewhere else with my object mother class showed above.
So my problem if it's not obvious already, is that me adding a sorted field SHOULD trigger a Sorted/Sorting event (it should sort the grid), but it doesn't, and I can't find any way to (programaticaly) force it to sort the grid and fire those events.
What am I missing?
The Sorting and Sorted event are only fired in response to user interaction so it is expected that they do not fire when sorting the grid in code.
If you need to see what fields are sorted through the grid, you would check the relevant FieldLayout.SortedFields collection to see what fields are sorted.
Note that if your test is to verify that sorting was successful, it may be better to check the order of the records in the grid rather than checking for the events to fire.

How to set List<List<double>> (Or two dimensional array) as data source to DataGrid

I have List<List<double>> with values and wpf datagrid.
How can I set this as dataSource to my dataGrid?
I have tried following:
public class DataContainer
{
public List<List<double>> List { get; set; }
public List<string> Headers { get; set; }
}
private void InitializeGrid(DataContainer container)
{
var table = new DataTable();
foreach (var header in container.Headers)
{
dataGrid1.Columns.Add(new DataGridTextColumn(){Header = header});
table.Columns.Add(header);
}
foreach (var lst in container.List)
{
var dr = table.NewRow();
var array = (from o in lst
select (object)o).ToArray();
dr.ItemArray = array;
table.Rows.Add(dr);
}
foreach (var row in table.Rows)
{
dataGrid1.Items.Add(row);
}
// dataGrid1.ItemsSource = table.Rows;
}
And this only add headers and empty rows.
You can go two directions but you need to pick one.
One:
Create the DataTable (not the columns). Use the headers to name the columns in the DataTable. Bind the DataTable with autogenerate columns.
Two:
Do NOT create the DataTable. Bind to List (using List as a property name is a bad practice and confusing). Then you you bind the column content to something like List[0], List[1]. I am not sure what the syntax is as I have done List where MyClass has a public List MyRows and then the syntax for the content binding is MyRows[0], MyRows[1] ....

Bind values from a list array to listbox

Could any body give a short example for binding a value from list array to listbox in c#.net
It depends on how your list array is.
Let's start from an easy sample:
List<string> listToBind = new List<string> { "AA", "BB", "CC" };
this.listBox1.DataSource = listToBind;
Here we have a list of strings, that will be shown as items in the listbox.
Otherwise, if your list items are more complex (e.g. custom classes) you can do in this way:
Having for example, MyClass defined as follows:
public class MyClass
{
public int Id { get; set; }
public string Text { get; set; }
public MyClass(int id, string text)
{
this.Id = id;
this.Text = text;
}
}
here's the binding part:
List<MyClass> listToBind = new List<MyClass> { new MyClass(1, "One"), new MyClass(2, "Two") };
this.listBox1.DisplayMember = "Text";
this.listBox1.ValueMember = "Id"; // optional depending on your needs
this.listBox1.DataSource = listToBind;
And you will get a list box showing only the text of your items.
Setting also ValueMember to a specific Property of your class will make listBox1.SelectedValue containing the selected Id value instead of the whole class instance.
N.B.
Letting DisplayMember unset, you will get the ToString() result of your list entries as display text of your ListBox items.

Categories