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!
Related
I have a use case where I am showing data from database in DataGridView, but when I add new rows on the UI (there is no code for adding new row, when I click the button I get all rows from DataGridView - old ones from database and new ones added on the UI), I need to know what rows are added from the UI. Is there a way to know it without checking in database and comparing it? I would like to know it from the UI. I have tried with ISNewRow() but it is true only for the last row.
When using a DataGridView people tend to fiddle with rows and cells directly. Although this is possible, it is way more easy to use data binding via property DataSource.
Using the visual studio designer, you have added a DataGridView and the columns that you want to show. Although it is possible to define which column should show which property, it is easier to do this in the constructor.
Alas you forgot to tell us what items you want to show, so let's assume you want to show several properties of a Product:
class Product
{
public int Id {get; set;}
public string Description {get; set;}
public decimal Price {get; set;}
public int Stock {get; set;}
...
}
Suppose you want to show these values as columns in your DataGridView. In the constructor you'll write:
public MyForm()
{
InitializeComponent(); // Creates the DataGridView and the columns
// define which column should show which property
this.ColumnId.DataPropertyName = nameof(Product.Id);
this.ColumnDescription.DataPropertyName = nameof(Product.Description);
this.ColumnPrice.DataPropertyName = nameof(Product.Price);
this.ColumnStock.DataPropertyName = nameof(Product.Stock);
... // etc, for all Product properties that you want to show
}
Somewhere, you'll have a method to fetch the products from the database:
private IEnumerable<Product> FetchProductsToDisplay()
{
... // TODO: implement; out of scope of this question
}
To display Products: assign them via a BindingList<Product> to the DataSource of the DataGridView:
private BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.DataGridView1.DataSource;
set => this.DataGridView1.DataSource = value;
}
At loading form, or after some other event, you need to fill the datagridview with the Products:
private void InitializeTableProducts()
{
IEnumerable<Product> productsToDisplay = this.FetchProductsToDisplay();
this.DisplayedProducts = new BindingList<Product>(productsToDisplay.ToList());
}
This is enough to display the Products. The operator can add / remove Products, and edit Cells, according to the editability you defined in the columns and the datagridview. If allowed he can even rearrange the rows and columns.
When the operator has finished editing, he notifies the software for instance by pressing the OK button:
private void OnButtonOk_Clicked(object sender, ...)
{
this.ProcessEditedProducts();
}
private void ProcessEditedProducts()
{
ICollection<Product> editedProducts = this.DisplayedProducts;
this.ProcessProducts(editedProducts);
}
In ProcessProducts, you'll have to find out which Products are added / removed / changed. I assume you'll recognize an edited product by a zero Id.
To find out if a Product is removed: it will have an Id in the original database, but not in the editedProducts.
Changed Products are more difficult: you need to compare by value. For this you need to create an IEqualityComparer<Product>.
I'll assume you know the techniques how to check if the Product is added / removed / changed.
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 a typed dataset object that fetches a table from my sql-server database. As far as I know, sql-server does not support TimeSpan type. How can I manage to add that new column in my C# Code ?
At the moment, I have a column which holds the ticks, so basically, in the database, I will store the amount of ticks, which will then be transformed as a TimeSpan and vice-versa (TimeSpan will be edit in the application and will then transformed into ticks to be then pushed to the database). My issue is that since it's a typed database that will be used along with a DevExpress Control (Gantt Control), I do not know how to add the new TimeSpan column and use it ?
The only solution I see is to create a new datatable based on the first one, copy the data in it, work with it and once user wants to update, push it back to my typed dataset and use sprocs to make the changes and then revert, but it does not seem like the best solution... Any ideas ?
Here is the code I use to generate a new property within my row, the issue is that I can't see my column.
public TimeSpan TimeSpanDuration
{
get => IsDurationNull() ? new TimeSpan(Duration) : TimeSpan.Zero;
set => Duration= value.Ticks;
}
Nothing stopping you adding properties to a typed dataset; it is, after all, merely what visual studio has done for you. Open your dataset, double click the datatable and you will be transported to code view with a prefilled partial class YourTablename. Right click YourTableName and choose Goto Definition to find the table inside the YourDataSet.Designer.cs file
Have a read; it's just a bunch of code that establishes named properties to the base DataTable and its columns. Use the dropdown at the top middle of the code window to find the partial class for your table's Rows. Here's how my Person table's Birthdate column looks on a row (the property that gets the birthdate-boxed-as-object from the datarow's item array, and casts it to a datetime so I don't have to):
public System.DateTime Birthdate {
get {
try {
return ((global::System.DateTime)(this[this.tablePerson.BirthdateColumn]));
}
catch (global::System.InvalidCastException e) {
throw new global::System.Data.StrongTypingException("The value for column \'Birthdate\' in table \'Person\' is DBNull.", e);
}
}
set {
this[this.tablePerson.BirthdateColumn] = value;
}
}
Nothing stopping me declaring a partial class PersonRow of my own in my own code (don't do it in the designer file - it will be lost - do it in the file you were transported to when you double clicked the datatable in the designer) that has a property like:
public int AgeInYears {
get {
try {
return (DateTime.Now - ((global::System.DateTime)(this[this.tablePerson.BirthdateColumn]))).Years;
}
catch (global::System.InvalidCastException e) {
throw new global::System.Data.StrongTypingException("The value for column \'Birthdate\' in table \'Person\' is DBNull. so the age cannot be calculated", e);
}
}
set { //do we really want to allow setting via years??
this[this.tablePerson.BirthdateColumn] = DateTime.Now.AddYears(-value);
}
}
Have a column that matches how SQLS stores the data (int64?) and build another set of properties to virtualize that column into how you want it (TimeSpan.FromMilliseconds?)
I have tens of different views and on some, the data that comes from database, must be manipulated before shown to user.
for example:
var students = _repository.Students.Select(c => { c.StartDate = FixDateToCurrentYear(c.StartDate ); c.EndDate = FixDateToCurrentYear(c.EndDate); return c; }).ToList();
So basically I now have a variable, where Students StartDate and EndDate have been fixed by some logic, which is not relevant at the moment.
The problem here is that, whenever somewhere a SaveChanges() function is called, then the changed dates for students are also persisted to database.
I only want to use the changed dates for the logic in specific view. How could I handle this?
I have heard about .AsNoTracking(); but this must be put directly to DbSet item, but I want to handle it on controller side, is it possible?
Pull the students from the db first and the do a select to an anonymous type. EF won't know how to track changes on that.
var students =
_repository.Students.ToList()
.Select(s =>
new { StartDate = FixDateToCurrentYear(c.StartDate),
EndDate = FixDateToCurrentYear(c.EndDate),
Student = s });
The quickest thing I can think of is to perform your fix at the point you display it to the user, e.g. in Razor:
#FixDateToCurrentYear(student.StartDate)
In this way, you're not actually changing the value, you're just changing how it's displayed.
If you need to do more than display, you will need to create a partial buddy class, add a new property and modify its get method:
public partial class Student
{
DateTime StartDateComputed {
get
{
return FixDateToCurrentYear(StartDate);
}
set;
}
}
Now, use StartDateComputed instead of StartDate and the same for EndDate.
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}