I have been binding short data to DataGridView in C# Winforms. However, I need to bind long string array with size 75 to DataGridView. My data list class consists of 6 individual variables with get and set and array of string which I have defined get and set properties. The individual variables are displayed but the array of strings is not displayed in DataGridView. In debug, I checked the data source of DataGridView and it seems ok. How can I display binded array in gridview.
Below is my source code to populate DataGridView named Logview
public void populateLogData(string path)
{
StreamReader sr = null;
BindingList<LogList> bindLogList;
BindingSource bLogsource = new BindingSource();
List<LogList> loglist = new List<LogList>();
try
{
Logview.DataSource = null;
Logview.Rows.Clear();
Logview.Columns.Clear();
Logview.AutoGenerateColumns = true;
if (File.Exists(path))
{
try
{
sr = new StreamReader(path);
StringBuilder readline = new StringBuilder(sr.ReadLine());
if (readline.ToString() != null && readline.ToString() != "")
{
readline = new StringBuilder(sr.ReadLine());
while (readline.ToString() != null && readline.ToString() != "")
{
string[] subdata = readline.ToString().Split(',');
LogList tloglist = new LogList(subdata[0], subdata[1], subdata[2], subdata[3], subdata[4], subdata[5], max_index);
for (int i = 6; i < subdata.Length; i++)
tloglist.setPartList(i-6, subdata[i]);
loglist.Add(new LogList(subdata, subdata.Length));
readline = new StringBuilder(sr.ReadLine());
}
}
bindLogList = new BindingList<LogList>(loglist);
bLogsource.DataSource = bindLogList;
Logview.AutoGenerateColumns = true;
Logview.DataSource = bindLogList;
Logview.Columns[0].Width = 140; // project name
Logview.Columns[1].Width = 140; // data/time
Logview.Columns[2].Width = 90;
Logview.Columns[3].Width = 90;
Logview.Columns[4].Width = 90;
Logview.Columns[5].Width = 90;
// max_index is set from another part of code
for(int i = 0; i <= max_index; i++)
{
int counter = 6 + i;
Logview.Columns.Add(headertext[i], headertext[i]);
Logview.Columns[counter].Width = 90;
Logview.Columns[counter].HeaderText = headertext[i];
}
}
catch (IOException io)
{
MessageBox.Show("Error: Cannot Open log file.");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
if (sr != null) sr.Close();
}
}
else
{
MessageBox.Show("Log file not found \n" + path);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
GC.Collect();
}
}
Below is LogList class
class LogList
{
const int max_size = 100;
private string[] holdList;
public string project { get; set; }
public string date_time { get; set; }
public string Qty { get; set; }
public string Pass { get; set; }
public string Fail { get; set; }
public string Result { get; set; }
public string[] partlist
{
get
{
return holdList;
}
set
{
holdList = value;
}
}
public LogList(string project, string date_time, string Qty, string Pass, string Fail, string Result, int partsize)
{
this.project = project;
this.date_time = date_time;
this.Qty = Qty;
this.Pass = Pass;
this.Fail = Fail;
this.Result = Result;
partlist = new string[partsize+1];
}
public void setPartList(int size, string getValue)
{
partlist[size] = getValue;
}
}
Project, date/time, Qty, Pass, Fail, Result is displayed. But partlist array is not displayed.
To supplement IVSoftware’s answer, below is an example using two grids in a master-detail scenario.
One issue I would have with your current approach, is that it uses an Array for the “parts list.” Currently this is a string array, and that isn’t going to work if we want to display it in a grid. Fortunately, there are a few easy ways we can get the data to display as we want.
One simple solution is to create a “wrapper” Class for the string. I will call this Class Part. I added a simple int ID property and the string PartName property. You could easily leave out the ID and have a simple string wrapper. This simple Class may look something like…
public class Part {
public int ID { get; set; }
public string PartName { get; set; }
}
This should allow the data to display correctly in the grid using just about any construct like an array, list etc.… So, we “could” change your current code to use an array of Part objects like…
Part[] Parts = new Parts[X];
And this would work, however, if we use an array and we know for sure that each LogItem may have a different number of parts in its PartsList, then we will have to manage the array sizes. So, a BindingList of Part objects will simplify this. The altered LogList (LogItem) Class is below…
public class LogItem {
public BindingList<Part> PartsList { get; set; }
public string Project { get; set; }
public string Date_Time { get; set; }
public string Qty { get; set; }
public string Pass { get; set; }
public string Fail { get; set; }
public string Result { get; set; }
public LogItem(string project, string date_Time, string qty, string pass, string fail, string result) {
Project = project;
Date_Time = date_Time;
Qty = qty;
Pass = pass;
Fail = fail;
Result = result;
PartsList = new BindingList<Part>();
}
}
So given the updated Classes, this should simplify things and we will use the same DataSource for both grids. This DataSource for the “master” grid will be a BindingList of LogItem objects. In the “detail” grid, we simply need to point it’s DataMember property to the PartsList property of the currently selected LogItem. And this would look something like…
dgvLogs.DataSource = LogsBL;
if (LogsBL.Count > 0) {
dgvParts.DataMember = "PartsList";
dgvParts.DataSource = LogsBL;
}
Below is the code to test the Classes above in a master-detail scenario with two grids. Create a new winform solution and drop two (2) DataGridViews on the form. The grid on the left is dgvLogs and the grid on the right is dgvParts.
public void populateLogData(string path) {
BindingList<LogItem> LogsBL = new BindingList<LogItem>();
string currentLine;
if (File.Exists(path)) {
try {
using (StreamReader sr = new StreamReader(path)) {
LogItem tempLogItem;
currentLine = sr.ReadLine(); // <- header row - ignoring
currentLine = sr.ReadLine();
while (currentLine != null) {
if (!string.IsNullOrEmpty(currentLine)) {
string[] splitArray = currentLine.Split(',');
if (splitArray.Length >= 6) {
tempLogItem = new LogItem(splitArray[0], splitArray[1], splitArray[2], splitArray[3], splitArray[4], splitArray[5]);
for (int i = 6; i < splitArray.Length; i++) {
tempLogItem.PartsList.Add(new Part { ID = i, PartName = splitArray[i] });
}
LogsBL.Add(tempLogItem);
}
else {
Debug.WriteLine("DataRead Error: Not enough items to make a LogItem: " + currentLine);
}
}
else {
Debug.WriteLine("DataRead Empty row");
}
currentLine = sr.ReadLine();
}
}
dgvLogs.DataSource = LogsBL;
if (LogsBL.Count > 0) {
dgvParts.DataMember = "PartsList";
dgvParts.DataSource = LogsBL;
}
}
catch (IOException io) {
MessageBox.Show("Error: Cannot Open log file.");
}
catch (Exception ex) {
MessageBox.Show(ex.Message + " Stacktrace- " + ex.StackTrace);
}
}
else {
MessageBox.Show("Log file not found \n" + path);
}
}
And some test data…
H1,h2,h3,h4,h5,h6,h7,h8
Model: LMG600N_IF_2blablas,2022-9-6,112,61,51,Fail,p1,p3,p4,p5,p6
1,2022-9-6,2112,621,251,Pass,px4,px5,px6,px1,px2,px3
data1,2022-9-7,3456,789,123,Fail,z3,z3,z4
Model: LMG600N_IF_2blablas,2022-9-6,112,61,51,Fail
Model: LMG600N_IF_2blablas,2022-9-6,112,61,51,Fail,p1,p3,p4,p5,p6,p7,p8,p99
BadData Model: LMG600N_IF_2blablas,2022-9-6,112,61
Moxxxdel: LMG600N_IF_2blablas,2022-9-6,11x2,6x1,5x1,Fail
Hope this helps and makes sense.
Your data list class consists of 6 individual variables with get and set, and an array of string. Your question is about the variables are displayed but the array of strings is not.
Here's what has worked for me (similar to the excellent suggestion by JohnG) for displaying the string array. What I'm doing here is taking a DataGridView and dropping in my main form without changing any settings (other than to Dock it). Given the default settings, the LogList class (shown here in a minimal reproducible example of 1 variable and 1 array of strings) is defined with a public string property named PartList and with this basic implementation:
class LogList
{
public LogList(string product, string[] partList)
{
Product = product;
_partList = partList;
}
public string Product { get; set; }
private string[] _partList;
public string PartList => string.Join(",", _partList);
}
To autoconfigure the DataGridView with Product and PartList columns, here is an example initializer method that sets the DataSource and adds the first three items as a test:
// Set data source property once. Clear it, Add to it, but no reason to nullify it.
BindingList<LogList> DataSource { get; } = new BindingList<LogList>();
private void InitDataGridView()
{
dataGridView1.DataSource = DataSource;
// Auto config columns by adding at least one Record.
DataSource.Add(
new LogList(
product: "LMG450",
// Four parts
partList: new string[]
{
"PCT2000",
"WCT100",
"ZEL-0812LN",
"EN61000-3-3/-11",
}
));
DataSource.Add(
new LogList(
product: "LMG600N",
// Three parts
partList: new string[]
{
"LTC2280",
"BMS6815",
"ZEL-0812LN",
}
));
DataSource.Add(
new LogList(
product: "Long Array",
// 75 parts
partList: Enumerable.Range(1, 75).Select(x => $"{ x }").ToArray()
));
// Use string indexer to access columns for formatting purposes.
dataGridView1
.Columns[nameof(LogList.Product)]
.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
dataGridView1
.Columns[nameof(LogList.PartList)]
.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}
After running this code, the DGV looks like this:
With the mouse hovered over the item all 75 "parts" can be viewed.
One last thing - I notice you have some methods to assign a new partList[] of perhaps change an individual part at a specified index. (I didn't show them in the minimal sample but for sure you'll want things like that). You probably know this but make sure to call dataGridView1.Refresh after altering properties of an existing row/LogList object so that the view will reflect the changes.
I hope there's something here that offers a few ideas to achieve the outcome you want.
I'm trying to access a List within an ArrayList to bind that list to a listbox.
Here is the code for my driver class.
public class Driver
{
private string name;
private string occupation;
private DateTime dateOfBirth;
private List<DateTime> dateOfClaim;
public Driver(string name, string occupation, DateTime dateOfBirth, List<DateTime> dateOfClaim)
{
this.name = name;
this.occupation = occupation;
this.dateOfBirth = dateOfBirth;
this.dateOfClaim = new List<DateTime>();
}
public string driverName
{
get{ return name; }
set{ name = value; }
}
public string driverOccupation
{
get { return occupation; }
set { occupation = value; }
}
public DateTime driverDateOfBirth
{
get { return dateOfBirth; }
set { dateOfBirth = value; }
}
public List<DateTime> driverDateOfClaim
{
get { return dateOfClaim; }
set { dateOfClaim = value; }
}
}
I have a button which allows you to add a date for a claim to a driver, up to a maximum of 5 claims so I have a temporary list which holds the dates list before it is assigned to the list in the arraylist as the new driver has not yet been created in the arraylist.
Here are parts of the code from a form which declares and populates the arraylist.
private ArrayList drivers = new ArrayList();
private List<DateTime> claimDates = new List<DateTime>();
if (noOfClaims <= 4)
{
claimDates.Add(dtpAddClaim.Value.Date);
noOfClaims++;
}
if (noOfDrivers <= 4)
{
drivers.Add(new Driver(txtName.Text, txtOccupation.Text, dtpDOB.Value.Date, claimDates));
noOfDrivers++;
}
So my problem comes when trying to access the dateOfClaim list.
I'm trying to bind that list to a listbox and have tried using this to do that but am getting an error saying 'ArrayList' does not contain a definition for 'driverDateOfClaim'.
lbxClaimDates.DataSource = drivers.driverDateOfClaim;
Any help solving this would be greatly appreciated.
You need to index the array list. You would need to access an item of the array list and then acces driverDateOfClaim for a specific Driver instance in the ArrayList
lbxClaimDates.DataSource = ((Driver) drivers[0]).driverDateOfClaim
Also in Drivers constructor you arent assigning the dateOfClaim parameter to the DateOfClaim property.
Having model with property type of object.
Model:
public class Employee
{
private object _BirthDate;
public object BirthDate
{
get { return this._BirthDate; }
set
{
this._BirthDate = value;
this.RaisePropertyChanged("BirthDate");
}
}
}
View Model:
Populating data for BirthDate like in below.
if (i % 5 == 0)
{
emp.BirthDate = "ABC";
// emp.Name = "NUll";
}
else if (i % 4 == 0)
{
emp.BirthDate = null;
// emp.Name = "DBNull";
}
else
emp.BirthDate = new DateTime(1 / 1 / 2013);
Having 100 birthdates. i am try to sort the data using LINQ query with LAMDA exressions.
private void GetSortedQuerableCollection_Click(object sender, RoutedEventArgs e)
{
var sourceType = new Employee().GetType();
var source = (this.datagrid.ItemsSource as IEnumerable).AsQueryable() as IQueryable;
var paramExpression = Expression.Parameter(source.ElementType, sourceType.Name);
var lambda = GetLambdaWithComplexPropertyNullCheck(source, "BirthDate", paramExpression, sourceType);
var data = OrderBy(source, "BirthDate", sourceType);
}
In data, having the results with "Object Must be a type of String".
Note: GetLambdaWithComplexPropertyNullCheck is the method having lots of code. if i include that also, its look inconsistency , and as per StackOverFlow norm its deletes the page. but it just a expression to calculate the sorting.
Let me have any idea to resolve this issue ?
If you have to mix dates, null, and non-dates, then use string:
public class Employee
{
private string = null;
public string BirthDate
{
get { return this._BirthDate; }
set
{
this._BirthDate = value;
this.RaisePropertyChanged("BirthDate");
}
}
}
and then:
emp.BirthDate = new DateTime(2013, 1 , 21).ToString("yyyy'-'MM'-'dd");
This is sortable, and you can easily parse BirthDate to a DateTime value:
DateTime trueDate = DateTime.Parse(emp.BirthDate);
I have a rather strange requirement for a Wpf Project I'm working on. I want to be able to build a XamDataGrid with a series of DateTime fields when the user saves the data from another grid. Currently I see the second XamDataGrid with it's fields, but upon execution of the command that saves the data, although I can see in the debugger that my second list (which is bound to the second XamDataGrid) is generated, nothing displays on this second XamDataGrid.
I'll post most of my code so that somebody might help me:
The xaml (for the second datagrid as the first one is working fine):
<igDP:XamDataGrid.FieldLayouts>
<igDP:FieldLayout>
<igDP:Field Label="ID" Name="id" Width="50"></igDP:Field>
<igDP:Field Label="Descripcion" Name="descripcion" Width="400"></igDP:Field>
<igDP:UnboundField Label="Fechas de Pago" Name="cadaPago" Width="400">
</igDP:UnboundField>
<igDP:Field Label="Colores" Name="Colores" Visibility="Collapsed" />
</igDP:FieldLayout>
</igDP:XamDataGrid.FieldLayouts>
</igDP:XamDataGrid>
`
The code in my viewmodel for the second grid:
public List<ClaseFechasPago> ListaFechasPago
{
get { return listaFechasPago; }
set { listaFechasPago = value; notifyChanges("ListaFechasPago"); }
}
public void PintarFechas(List<ClaseFechasPago> f)
{
ListaFechasPago.Clear();
foreach (ClaseFechasPago fecha in f)
{
fecha.cadaPago = new List<DateTime>();
for (int i = 0; i < fecha.numPagos; i++)
{
fecha.cadaPago.Add(new DateTime());
}
ListaFechasPago.Add(fecha);
}
}
public vmCursos_y_Diplomados()
{
Comando = new cmdCursos_y_Diplomados();
Comando.ViewModel = this;
ListaCursosyDiplomados = new List<ClaseCursosyDiplomados>();
ListaFechasPago = new List<ClaseFechasPago>();
this.cargarDatos();
this.PintarFechas(ListaFechasPago);
}
Now on the command I'm doing the following
public void Execute(object parameter)
{
List<CatEntidadesEducacionContinua> cursos = new List<CatEntidadesEducacionContinua>();
List<ClaseFechasPago> fechas = new List<ClaseFechasPago>();
foreach (ClaseCursosyDiplomados C in ViewModel.ListaCursosyDiplomados.Where(t=>t.Colores==1).ToList())
{
cursos.Add(new CatEntidadesEducacionContinua
{
IdEntidadEducacionContinua = C.id, Coordinador=C.coordinador, Descripcion=C.descripcion, FechaUltimoCambio = DateTime.Now,
FechaInicio = C.fechaInicio, FechaTermino=C.fechaTermino, Precio=C.precio, NumeroDePagos=C.numeroDePagos, FechasPagos=C.fechasPagos, Inhabilitado=C.inhabilitado,
});
if (C.numeroDePagos > 1)
{
ClaseFechasPago f = new ClaseFechasPago();
f.numPagos = C.numeroDePagos;
f.descripcion = C.descripcion;
f.id = C.id;
fechas.Add(f);
}
}
System.Windows.MessageBox.Show(new Entidades.MetodoCursos_y_Diplomados().SetEntidadEContinua(cursos), "Entidades de Educación Continua", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information);
//System.Windows.MessageBox.Show(new Entidades.MetodoFechasPago().pintarFechasPago
ViewModel.cargarDatos();
ViewModel.PintarFechas(fechas);
}
But as I said it's not working, the execution results in the following screenshot, where the second grid is not populated:
Oh and I also forgot earlier to show the code for my custom class, out of which the list bound to the XamDataGrid is made of:
public class ClaseFechasPago : Utils.PropertyChange
{
private List<DateTime> _cadaPago;
public List<DateTime> cadaPago
{
get { return _cadaPago; }
set
{
_cadaPago = value;
if (EntroPrimeraVez)
{
Colores = 1;
}
}
}
private int? _numPagos;
public int? numPagos
{
get { return _numPagos; }
set
{
_numPagos = value;
if (EntroPrimeraVez)
{
Colores = 1;
}
}
}
private int _id;
public int id
{
get { return _id; }
set
{
_id = value;
}
}
private string _descripcion;
public string descripcion
{
get { return _descripcion; }
set { _descripcion = value; }
}
private int _Colores;
private bool _EntroPrimeraVez;
public bool EntroPrimeraVez
{
get { return _EntroPrimeraVez; }
set { _EntroPrimeraVez = value; notifyChanges("EntroPrimeraVez"); }
}
public int Colores
{
get { return _Colores; }
set { _Colores = value; notifyChanges("Colores"); }
}
}
It turned out the only thing I needed to do was passing the List explicitly as a list, like so:
ListaFechasPago = ListaFechasPago.ToList()
However, I seemed to have a mistake of concept in the way I was building the date fields. I ended up building as many registries as were needed of the same entry and binding a DateTime field to each, like so:
public static List<ClaseFechasPago> PintarFechas(ClaseFechasPago f)
{
List<ClaseFechasPago> ListaFechasPago = new List<ClaseFechasPago>();
for (int i = 0; i < f.numPagos; i++)
{
ClaseFechasPago fecha = new ClaseFechasPago();
fecha.cuotaInscripcion = 0M;
fecha.Inscripcion = true;
fecha.fechaPago = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
fecha.id = f.id;
fecha.descripcion = f.descripcion;
fecha.numPagos = f.numPagos;
fecha.Colores = f.Colores;
fecha.EntroPrimeraVez = f.EntroPrimeraVez;
ListaFechasPago.Add(fecha);
}
return ListaFechasPago;
//ListaFechasPago = ListaFechasPago.ToList();
}
Oh and of course initialize the ListaFechasPago List in the class that is set as DataContext for the window:
ListaFechasPago = new List<ClaseFechasPago>();
insde the class vmCursos_y_Diplomados
because I do:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new vmCursos_y_Diplomados();
}
I have a List that contains a series of transaction objects. What I'm trying to do is to display these transaction objects in a Datagridview control on loading a form, basically the Datagridview should represent something of a transaction register to display the data for each of the transaction objects in the list.
I must admit to a lack of experience when it comes to using Datagridviews and I'm having some difficulty with understanding what I need to do here.
My question is, how do I go about getting the details of each of the objects in the list to display in the Datagridview?
Here is my code.
First the transaction class:
public class Transaction
{
// Class properties
private decimal amount;
private string type;
private decimal balance;
private string date;
private string transNum;
private string description;
// Constructor to create transaction object with values set.
public Transaction(decimal amount, string type, decimal currBal, string date, string num, string descrip)
{
this.amount = amount;
this.type = type;
this.balance = currBal;
this.date = date;
this.transNum = num;
this.description = descrip;
}
// Get and Set accessors to allow manipulation of values.
public decimal Amount
{
get
{
return amount;
}
set
{
amount = value;
}
}
public string Type
{
get
{
return type;
}
set
{
type = value;
}
}
public decimal Balance
{
get
{
return balance;
}
set
{
balance = value;
}
}
public string Date
{
get
{
return date;
}
set
{
date = value;
}
}
public string TransNum
{
get
{
return transNum;
}
set
{
transNum = value;
}
}
public string Description
{
get
{
return description;
}
set
{
description = value;
}
}
public decimal addCredit(decimal balance, decimal credit)
{
decimal newBalance;
newBalance = balance + credit;
return newBalance;
}
public decimal subtractDebit(decimal balance, decimal debit)
{
decimal newBalance;
newBalance = balance - debit;
return newBalance;
}
}
}
Now the code for the "Register" form:
public partial class Register : Form
{
List<Transaction> tranList = new List<Transaction>();
public Register(List<Transaction> List)
{
InitializeComponent();
this.tranList = List;
}
private void Register_Load(object sender, System.EventArgs e)
{
//regView represents the Datagridview that I'm trying to work with
regView.AutoSize = true;
regView.DataSource = tranList;
regView.Rows.Add(tranList[0]);
}
}
And here's the output I get.
There's really two high level approaches to this.
1) Add the manually created rows directly to the DataGridView. In this case, you have to manually update/remove them as things change. This approach is "ok" if you don't intend to alter/change the content of the display after you initialize it. It becomes untenable if you do.
To add it directly, you need to create a DataGridViewRow, and populate it with the individual values, and then add the DataGridViewRow to the DataGridView.Rows.
2) Data bind the DGV. There's many articles about databinding to a DataGridView. In some cases, it's easier to just add your data to a DataTable, and then extract a DataView from that, and bind the DataGridView to the DataView. Other people find it easier to directly bind to a collection.
CodeProject has a decent article to get you started down that path, but a quick Google search will yield many other articles.
http://www.codeproject.com/Articles/24656/A-Detailed-Data-Binding-Tutorial
use as DGV:
DataGridView groupListDataGridView;
column:
DataGridViewTextBoxColumn groupListNameColumn;
column setup should be like this:
groupListNameColumn.DataPropertyName = "name";
use this property, else all columns will be added.
groupListDataGridView.AutoGenerateColumns = false;
populate like this:
private void populateGroupList() {
groupListDataGridView.DataSource = null;
formattedGroupList = new SortableBindingList<DataGridGroupObject>();
foreach (GroupObject go in StartUp.GroupList) {
DataGridGroupObject dggo = new DataGridGroupObject();
dggo.id = go.Id;
dggo.name = go.Name;
formattedGroupList.Add(dggo);
}
groupListDataGridView.DataSource = formattedGroupList;
groupListDataGridView.Invalidate();
}
and model:
public class DataGridGroupObject
{
public int id { get; set; } //this will be match id column
public string name { get; set; } // this will be match name column
}
Simply add using System.Linq; at the top. Then you can do this:
//This will create a custom datasource for the DataGridView.
var transactionsDataSource = tranList.Select(x => new
{
Amount = x.amount,
Type = x.type,
Balance = x.balance,
Date = x.date,
TransNum = x.transNum
Description = x.description
}).ToList();
//This will assign the datasource. All the columns you listed will show up, and every row
//of data in the list will populate into the DataGridView.
regView.DataSource = transactionsDataSource;