I have such a situaton, that I am reading txt file making some operation on the lines and at the end I want to display everything in gridview. I have 3 separated columns. In first and second one I am displaying normal string values. But in middle one I have object returned by one class and I would like to display it normally in my gridview. How can I achieve it? I have something like this so far.
while ((line = sr.ReadLine()) != null)
{
string[] lines = line.Split(",".ToCharArray());
object returnValue;
MyColumns object = new MyColumns();
object.Time = line[0];
object.System_Description = line[1];
object.User_Description = line[2];
///earlier in my code I have object of class called method
returnValue = method.MyMethod(mc.System_Description);
Class main = new Class();
main.Data1= object.Time;
main.ProblemData= returnValue;
main.Data2= object.User_Description;
list3.Add(main);
}
this.dataGridView3.DataSource = list3;
I have problem with showing ProblemData. Now in this column gridview shows me "project_name.Class_Name" (name of the class that this value was retured by)
EDIT:
Ok, I also have to mention that this class, from which returnValue gets values has 5 properties, let's say Categry, Name, Second_Name, Status and Value. This returnValue holds all this 5 properties with their current values.
EDIT2: Maybe someone knows how to display all this fields in one column? How can I join them only for displaying purpose? When I make normal List and insert this returnValue, it creates these 5 columns and insert values inside. Maybe it will make it easier to understand.
Please see my first comment on your question.
You have to use a nested GridView inside your second column which will bind to the returnValue. This is because GridView cannot automatically cascade your object datasource. The inner binding needs to be done in the RowDataBound event of your main GridView. For this to work, you will have to re-organise / re-factor your code.
Alternatively, you can concatenate the properties of the returnValue if their string representations can work for your scenario.
Edit:
The OP is asking about WinForms DataGridView (not ASP.Net):
The WinForms DataGridView does not support nesting out-of-the-box. However, there are some templating workarounds which are complicated. You are looking for a simple solution. I found one which can serve your immediate needs.
Hook into the CellFormatting event.
if (e.value is YOUR_OBJECT_TYPE) {
e.Value = (e.Value as YOUR_OBJECT_TYPE).YOUR_PROPERTY_NAME;
}
For details please refer to this: Binding to Nested Properties
Alternate option:
The alternate option of concatenating the properties of the returnValue as string, will also work.
main.ProblemData = "Cat: " + returnValue.Category + ", Name: " + returnValue.Name;
you should have defined your class variables like a propertiesbecause you are using them in databinding. like this..
public String Data1 {get;set;}
also make your list a ObservableCollection as it will notify the view whenever you change something in your list..
Two options
Override ToString() method in your ProblemData type
public class ProblemData
{
//whatever...
public override string ToString()
{
return string.format("{0}", this.SomeObject); //set proper display
}
}
public class YourClass()
{
//...
public ProblemData ProblemData{ get; set;}
}
Or you can set grid column formatter if object type can be formatted using string.Format
dataGridView3.Columns["ProblemData"].DefaultCellStyle.Format = "N";
//display string.Format({0:N}
Related
I am trying to set a value for DataGridViewRow.Tag when data binding but I don't know how to do it?
I tried with DataRow row = table.NewRow(); But row doesn't have tag.
How to set a value for DataGridViewRow.Tag when binding DataGridView to table (for example)? Or it isn't possible?
Edit1:
Here is the code i am using:
var table = new DataTable();
table.Columns.Add("Title", typeof(string));
table.Columns.Add("URL", typeof(string));
table.Columns.Add("Read Later", typeof(bool));
foreach (XElement node in nodes)
{
Helper.CheckNode(node);
var id = node.Attribute("id").Value;
var url = node.Element("path").Value;
var comment = node.Element("title").Value;
var readlater = node.Attribute("readLater")?.Value.ToString() == "1";
var row = table.NewRow();
row.ItemArray = new object[] { url, comment, readlater };
table.Rows.Add(row);//Edit2
}
dataGridView1.DataSource = table;
I am trying to set a tag for the row to use it in CellClick event:
var cRow = dataGridView1.Rows[e.RowIndex];
var id = cRow.Tag.ToString();
Separate the data from how it is displayed
When using a DataGridView, it is seldom a good idea to access the cells and the columns directly. It is way more easier to use DataGridView.DataSource.
In modern programming there is a tendency to separate your data (= model) from the way your data is displayed (= view). To glue these two items together an adapter class is needed, which is usually called the viewmodel. Abbreviated these three items are called MVVM.
Use DataGridView.DataSource to display the data
Apparently, if the operator clicks a cell, you want to read the value of the tag of the row of the cell, to get some extra information, some Id.
This displayed row is the display of some data. Apparently part of the functionality of this data is access to this Id. You should not put this information in the view, you should put it in the model.
class MyWebPage // TODO: invent proper identifier
{
public int Id {get; set;}
public string Title {get; set;}
public string Url {get; set;}
public bool ReadLater {get; set;}
... // other properties
}
Apparently you have a method to fetch the data that you want to display from a sequence of nodes. Separate fetching this data (=model) from displaying it (= view):
IEnumerable<MyWebPage> FetchWebPages(...)
{
...
foreach (XElement node in nodes)
{
Helper.CheckNode(node);
bool readLater = this.CreateReadLater(node);
yield return new MyWebPage
{
Id = node.Attribute("id").Value,
Url = node.Element("path").Value,
Title = node.Element("title").Value,
ReadLater = this.CreateReadLater(node),
};
}
}
I don't know what is in node "ReadLater", apparently you know how to convert it to a Boolean.
bool CreateReadLater(XElement node)
{
// TODO: implement, if null return true; if not null ...
// out of scope of this question
}
For every property that you want to display you create a DataGridViewColumn. Property DataPropertyName defines which property should be shown in the column. Use DefaultCellStyle if a standard ToString is not enough to display the value properly, for instance, to define the number of digits after the decimal point, or to color negative values red.
You can do this using the visual studio designer, or you can do this in the constructor:
public MyForm
{
InitializeComponents();
this.dataGridViewColumnTitle.DataPropertyName = nameof(MyWebPage.Title);
this.dataGridViewColumnUrl.DataPropertyName = nameof(MyWebPage.Url);
...
}
You don't want to display the Id, so there is no column for this.
Now to display the data, all you have to do is assign the list to the datasource:
this.dataGrieViewWebPages.DataSource = this.FetchWebPages().ToList();
This is display only. If the operator can change the displayed values, and you want to access the changed values, you should put the items in an object that implements interface IBindingList, for instance, using class (surprise!) BindingList<T>:
private BindingList<MyWebPage> DisplayedWebPages
{
get => (BindingList<MyWebPage>)this.dataGrieViewWebPages.DataSource;
set => this.dataGrieViewWebPages.DataSource = value;
}
Initialization:
private void DisplayWebPages()
{
this.DisplayedWebPages = new BindingList<MyWebPage>(this.FetchWebPages.ToList());
}
And presto! All webpages are displayed. Every change that the operator makes: add / remove / edit rows are automatically updated in the DisplayedWebPages.
If you want to access the currently selected WebPages:
private MyWebPage CurrentWebPage =>(MyWebPage)this.dataGrieViewWebPages.CurrentRow?.DataBoundItem;
private IEnumerable<MyWebPage> SelectedWebPages =>
this.dataGrieViewWebPages.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<MyWebPage>();
Now apparently whenever the operator clicks a cell, you want to do something with the Id of the WebPage that is displayed in the Row of the cell.
View: Displayed Cell and Row
ViewModel: React when operator clicks a cell
Model Action that must be done
React on Cell Click: get the Id
We've handled the View above. ViewModel is the event handler:
void OnDatGridViewCellClicked(object sender, DataGridViewCellEventArgs e)
{
// use the eventArgs to fetch the row, and thus the WebPage:
MyWebPage webPage = (MyWebPage)this.dataGridViewWebPages.Rows[e.RowIndow].DataBoundItem;
this.ProcessWebPage(webPage);
}
ProcessWebPage is typically a method in your Model class:
public void ProcessWebPage(MyWebPage webPage)
{
// Do what you need to do if the operator clicks the cell, for example:
int webPageId = webPage.Id;
...
}
Conclusion: advantages of separating model from view
By the way, did you see that all ViewModel methods are one-liners? Only your Model methods FetchWebPages and ProcessWebPage contain several lines.
Because you separated the View from the Model, changes to your Model or your View will be fairly simple:
If you want to store your data in Json format, or in a database instead of in an XML, your View won't change
If you don't want to react on cell click, but on Button OK click, then your Model won't change. Your model also doesn't have to change if you decide to show more or less columns
Because you separated your Model from your View, the Model can be unit tested without a form. You can also test the View with a Model filled with only test values.
I have matter on adding row to DataGridView in C#, I tried to add an String[] as DataGridView.Rows.Add argument but, always the same issue, now this is my final code, and It doesn't work again, always NullReferenceException:
{
ConnectDB con = new ConnectDB();
CrudDB db = new CrudDB();
try
{
DispoProf disp = new DispoProf(res.ID);
con.Connexion.Open();
List<DispoProf> liste = db.Find("dispoprof", disp, "", con.Connexion);
for (int i = 0; i < liste.Count; i += 1)
{
//string[] ligne = { liste[i].date, liste[i].heureDebut, liste[i].heureFin, null, null };
dataGridViewListerDV.Rows.Add(liste[i].date, liste[i].heureDebut, liste[i].heureFin, null, null);
}
}
catch(Exception ex)
{
Console.WriteLine("Exception :: {0} :: {1} :: {2}",ex.Message, ex.Source , ex.StackTrace);
}
finally
{
con.Connexion.Close();
}
}
And It throw a NullReferenceException at
dataGridViewListerDV.Rows.Add(liste[i].date, liste[i].heureDebut, liste[i].heureFin, null, null);
Are you sure that every liste[i] is not null, and that every starting hour and every finish hour of every liste[i] is not null.? What do you want to display if any of these values is null?
Alas you forgot to tell us the return value of db.Find(...), but I am pretty sure that either liste[i] equals null, or properties heureDebut et heureFin are nullable Datetime
If you've defined your cells such that you can display nullable Datetimes, consider to change your code:
var itemToDisplay = liste[i];
if (itemToDisplay != null)
{
dataGridViewListerDV.Rows.Add(itemToDisplay.date,
itemToDisplay.heureDebut, itemToDisplay.heureFin, null, null);
}
else
{
// decide what to do if item equals null
}
Also, if HeureDebut / HeureFine may be null, consider to change your DataGridViewColumns such that they can show nullable DateTime instead of DateTime.
There is room for improvement
First time users of DataGridViews tend to tinker directly with the Rows and Cells in the DataGridView. By doing this, you intertwine data (your model) with the way this data is displayed (your view).
For quite some time now, there is a tendency to keep those two separated. If you keep your model separated from your view, you can easily change the view, without having to change your model, for instance, if you want to show your data in a Graph, instead of a table, or if you want to save the data in an XML file, instead of a table, your model doesn't have to change. Similarly, it is much easier to unit test your model, if you don't need a Form to display it.
A third reason to keep the model separated from the view, is that it gives you the freedom to change your DataGridView without having to change your model: you can Add / Remove columns, change the way how DateTimes are displayed, show a different color for negative values: all these changes can be done without having to change your Model.
To keep your Model and your View separated, you need an adapter class in between that converts your model to the way that you want it displayed. This adapter is quite often called the ViewModel.
If you will be using WPF instead of Forms in a few years, you will see that this separation between model and view is almost enforced, by using a different language to describe the view (XAML).
But Forms also supports this separation.
First you need to define the class that will be displayed in one row. Something like this:
class WorkingHours
{
public DateTime Date {get; set;}
public TimeSpan? StartTime {get; set;}
public TimeSpan? EndTime {get; set;}
}
This way, it is certain that StartTime and EndTime are on the same day. If you have night shifts, consider:
class WorkingHours
{
public DateTime? StartTime {get; set;}
public DateTime? EndTime {get; set;}
}
But then you have problems: what Date to show if you haven't got a StartTime. Before dislaying your model, get your model straight, so that your properties are well defined: what values are always available, what values are nullable, can they ever be out-of-range?
Using visual Studio Designer, you probably have defined columns. Your columns have a property DataPropertyName, which tells you what to show:
columnDate.DataPropertyName = nameof(WorkingHours.Date);
columnStartTime.DataPropertyName = nameof(WorkingHours.StartTime);
columnFinishTime.DataPropertyName = nameof(WorkingHours.EndTime);
If your StartTime and EndTime may be null, consider to add how to show null values: Red background? or only a '-', maybe show nothing?
See: because you separate your model and your view, that changing the view does not influence your model!
We need a method to fetch your data. This is your method in your question:
private IEnumerable<WorkingHours> GetWorkingHours(...)
{
using (var dbConnection = new ConnectedDb(...))
{
... // Create DbCommand, ExecuteQuery and use DbReader to fill WorkingHours
}
}
Note: This is the only place that will change if in future you decide to change how you fetch your data, like use entity framework or Dapper, or read the working hours from an XML file? Or change the database layout: again: Model change does not influence your view.
Now that we are able to fetch the displayed data, Displaying is one statement:
this.dataGridView1.DataSource = GetWorkingHours(...).ToList();
Et voila! All fetched data is instantly displayed.
However, this is display only. Changes are not monitored. If you want to know about changes: adding / removing / changing rows, the data should be in an object that implements IBindingList, like BindingList<T>
For this, we need one line of code:
private BindlingList<WorkingHours> DisplayedWorkingHours
{
get => (BindingList<WorkingHours>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
So to display your data:
void InitDisplayedData()
{
this.DisplayedWorkingHours = new BindingList<WorkingHours>(this.GetWorkingHours().ToList());
}
Now every change made by the operator is automatically updated in the bindingList. You don't have to read Rows nor Cells, just wait until the operator indicates he finished editing the data, for instance by clicking a button:
private void OnButtonOk_Clicked(object sender, ...)
{
IReadOnlyCollection<WorkingHours> editedWorkingHours = this.DisplayedWorkingHours;
// Detect which items are added / removed / changed and process the changes:
this.ProcessEditedWorkingHours(editedWorkingHours);
}
Again: did you see, that because I separate the actual data processing from how the data is displayed, all model functionality can be tested without the form. If you ever change how the data is displayed, your model does not have to change, if you ever change your model, the display does not have to change.
If you need to process selected rows, consider to add functionality for this:
private WorkingHours CurrentWorkingHours =>
(WorkingHours)this.dataGridView1.CurrentRow?.DataBoundItem;
private IEnumerable<WorkingHours> SelectedWorkingHours =>
this.dataGridView1.SelectedRows.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<WorkingHours>();
}
Conclusion
By separating your model from your view, it is easier to change either the view or the model, without having to change the other. It is easier to unit test the model, without the view, and if problems occur, you can debug the view without a real database.
The ViewModel adapter between the Model and the View consists usually of a few one-liner methods.
Simple comme bonjour!
I'm autogenerating columns based on a set. After that, I'm adding one of my own, manually, like this.
private void OnAutoGeneratedColumns(Object sender, EventArgs eventArgs)
{
DataGridColumn column = new DataGridTextColumn { ... };
DataGrid grid = sender.Get<DataGrid>();
grid.Columns.Add(column);
}
It adds the column and I can see the header being set according to the code. However, I'm not sure how to add any data to it. Checking the list of properties didn't offer me a clue. How should one do that?
The additional column is to be rendered based on the information from the same line in the grid (e.g. if the autogenerated columns are FirstName and LastName, the manually added one would be FullName and consist of the concatenation of them).
One approach I can think of is to inherit the class that is being used to autogenerate the columns, extend it with a new property and put it in the constructor of my view model like so. Then I could bind to the latter instead of the former.
BasicView = new ListCollectionView(basicThings);
ExtendedView = new ListCollectionView(extendedThings);
class ExtendedThing : BasicThing
{
public String FullName { get { return FirstName + LastName; } }
}
But that seems like a rather major intrusion into the code, which makes me suspect I'm heading in the wrong direction...
Situation:
I am attempting to bind a BindingList<string[]> constructed from a LINQ to SQL query to a DataGridView.
Problem:
I either cannot make modification to the DataGridView after items are generated -or- I get a bunch of unwanted fields in my DataGridView (it depends on which iteration of my code I use) I have googled as hard as I can and tried implementing most of the solutions I have found online to no avail.
I know that string has no public property for its actual value. I am having a difficult time determining how to retrieve that (I believe is part of the problem).
C#
int item = (from p in CurrentConversion.Companies[lbCompanies.SelectedIndex].Modules
where p.ModuleName.Equals(clbModules.SelectedItem)
select p.ModuleId)
.FirstOrDefault();
BindingList<string[]> Data = new BindingList<string[]>((
from p in CurrentConversion.Companies[lbCompanies.SelectedIndex].QuestionAnswers
where p[2].Equals(item)
select new string[] { p[0].ToString(), p[3].ToString() })
.ToList());
dgvQuestions.DataSource = Data;
dgvQuestions.Refresh();
Unwanted Behavior:
This occurs after binding
Question:
Why is this happening?
How do I fix it?
Additional Information:
I am not sure what additional information may be need but I will supply what is requested.
Also if I switch to my other code iteration:
int item = (from p in CurrentConversion.Companies[lbCompanies.SelectedIndex].Modules where p.ModuleName.Equals(clbModules.SelectedItem) select p.ModuleId).FirstOrDefault();
var Data = new BindingList<object>((from p in CurrentConversion.Companies[lbCompanies.SelectedIndex].QuestionAnswers where p[2].Equals(item) select new {Question = p[0].ToString(), Answer = p[3].ToString() }).Cast<object>().ToList());
dgvQuestions.DataSource = Data;
dgvQuestions.Refresh();
dgvQuestions.Columns[1].ReadOnly = false;
I can see the data properly but I cannot edit the column I would like to.
You are binding to a list of string arrays, and you are getting the properties form the array. Most likely you want something like the following:
var Data = new BindingList<object>((
from p in CurrentConversion.Companies[lbCompanies.SelectedIndex].QuestionAnswers
where p[2].Equals(item)
select new {
Val1 = p[0].ToString(),
Val2 = p[3].ToString()
}).ToList());
The reason you're seeing those fields in the Grid is that you're binding each row to a string[]. So it is automatically displaying the properties of string[] as the columns. There is no built-in logic for the grid to parse an array and use the contents of the array as columns.
In order to get the DataGrid to display your data correctly, you should bind it to a custom type, and it will use the public properties of the type as columns.
Due to some reason I cannot change the query so I have to do this in C#.
I have a class:
public class myClass
{
int id { get; set; }
string name { get; set; }
DateTime sDate { get; set; }
bool status { get; set; }
}
The data I am getting is fetched in this list. Now what I want is to remove those properties from a list that has null values. I may sound insane but you read it right. I thought of creating another list with only the selected properties, but any of the above properties can be null. So I have to devise a mechanism to filter my list based on this.
For more clarity consider the following example.
List<myClass> lstClass = some data source.
After getting the data the generic list(lstClass) looks like this.Consider the result set in a table:
Id Name Sdate status
1 a null null
2 b null null
3 c null false
Can i some how make my list look like this after removing the property sdate.
So the new list that I want to create should have only three properties.
Id Name status
1 a null
2 b null
3 c false
Any ideas? Can I do this using Linq?
PS: This has nothing to do with presentation. I don’t have a grid where I am not able to hide columns that Is not what I am looking for.
Assuming you have a generic list of myClass instances, you can create an anonymous type with only the needed properties:
List<myClass> list = ...;
var reducedList = list.Select(e => new {e.id, e.name, e.status}).ToList();
// note: call to ToList() is optional
foreach (var item in reducedList)
{
Console.WriteLine(item.id + " " + item.name + " " + item.status);
//note: item does not have a property "sDate"
}
I'm not sure you should solve your issue in the Data, but rather it's a presentation problem.
In which control do you want to display it ? Let's say you display it in DataGrid with AutoGenerateColumns=True, then you can 1) loop on columns/properties 2) for each column/property see if all property values for all rows are null and if so set column's visibility to Collapsed.
If you generate your columns by yourself it's even simpler : only add columns when content is not null for all rows.
If your DB content is dynamic, you might want to bind each row's visibility to a property that would state wether all rows are null or not for that property. Depending on how generic you want your code to be, the code might be very different, and in case you want to have generic solution, using Reflection to retrieve/get/set properties might be of some use.