How to group DataGridView? - c#

My current DataGridView looks like this:
I'd like to achieve that each (column) "Fachanwendung" is grouping the data by displaying it as a Header. I've tried it with the DataGridViewGrouper like here: https://10tec.com/articles/datagridview-grouping-two-recipes.aspx
https://www.codeproject.com/Tips/995958/DataGridViewGrouper
Unfortunately, my DataSource and DataMembers are either null or "". Therefore I can't call it like this:
DataGridViewGrouper grouper = new DataGridViewGrouper(dataGridView);
grouper.SetGroupOn("Fachanwendung");
//grouper.SetGroupOn(this.dataGridView.Columns["fachanwendung"]);
Is there any other way to display (at least a text) over a specific row?

I created a DataTable which you can use as the DatSource of the DGV. See code below :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication98
{
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>() {
new Element() { Fachanwendung = "archiMap", Elementtyp = "Herstellerproduktversion", Elementname = "Java SE JRE 8", Date = DateTime.Parse("1/1/2017"), Quantity = 10},
new Element() { Fachanwendung = "archiMap", Elementtyp = "Herstellerproduktversion", Elementname = "Microsoft Window Server 2012 Standard", Date = DateTime.Parse("3/1/2017"), Quantity = 12},
new Element() { Fachanwendung = "Event Management System", Elementtyp = "Herstellerproduktversion", Elementname = "Apache Toimcat 8.0", Date = DateTime.Parse("6/1/2018"), Quantity = 5},
new Element() { Fachanwendung = "Event Management System", Elementtyp = "Herstellerproduktversion", Elementname = "Oracle Java JDK 7", Date = DateTime.Parse("12/1/2018"), Quantity = 5},
new Element() { Fachanwendung = "Event Management System", Elementtyp = "Herstellerproduktversion", Elementname = "Oracle Java JDK 8", Date = DateTime.Parse("1/1/2019"), Quantity = 5}
};
Dictionary<string, List<Element>> dict = elements.GroupBy(x => x.Fachanwendung, y => y)
.ToDictionary(x => x.Key, y => y.ToList());
DataTable dt = new DataTable();
dt.Columns.Add("Fachanwendung", typeof(string));
dt.Columns.Add("Elementtyp", typeof(string));
dt.Columns.Add("Elementname", typeof(string));
//add quarters
DateTime minDate = dict.SelectMany(x => x.Value.Select(y => y.Date)).Min(x => x.Date);
DateTime maxDate = dict.SelectMany(x => x.Value.Select(y => y.Date)).Max(x => x.Date);
DateTime startQuarter = new DateTime(minDate.Year, ((minDate.Month - 1) % 3) + 1, 1);
DateTime endQuarter = new DateTime(maxDate.Year, ((maxDate.Month - 1) % 3) + 1, 1);
for (DateTime date = startQuarter; date <= endQuarter; date = date.AddMonths(3))
{
dt.Columns.Add(date.Year.ToString() + " Q" + (((date.Month - 1)/ 3) + 1).ToString(), typeof(int));
}
foreach (KeyValuePair<string, List<Element>> rows in dict)
{
var groups = dict[rows.Key].GroupBy(x => new { Elementtyp = x.Elementtyp, Elementname = x.Elementname, Date = new DateTime(x.Date.Year, ((x.Date.Month - 1) % 3) + 1, 1) }).ToList();
foreach (var group in groups)
{
DataRow newRow = dt.Rows.Add();
newRow["Fachanwendung"] = rows.Key;
newRow["Elementtyp"] = group.Key.Elementtyp;
newRow["Elementname"] = group.Key.Elementname;
string quarter = group.Key.Date.Year.ToString() + " Q" + (((group.Key.Date.Month - 1) / 3) + 1).ToString();
int total = group.Sum(x => x.Quantity);
newRow[quarter] = total;
}
}
}
}
public class Element
{
public string Fachanwendung { get; set; }
public string Elementtyp { get; set; }
public string Elementname { get; set; }
public DateTime Date { get; set; }
public int Quantity { get; set; }
}
}

I got your requirement now basically you want to have a block for each group in the data grid view. This can be resolved using Nested grid view. Here is very good link which you can use https://www.codeproject.com/Articles/848637/Nested-DataGridView-in-windows-forms-csharp
Also if you don't want expand collapse functionality you can easily remove that. Let me know if you need help with that.

Related

remove duplicate values from a list with multiple properties

I have a list of MyClass:
class MyClass
{
public DateTime? DueDate;
public string Desc;
public Decimal Amount;
}
var sample = new List<MyClass>();
This is how sample data looks like :
DueDate Desc Amount
06-29-2015 ABC 100
06-29-2015 DEF 200
01-15-2015 ABC 100
01-15-2015 DEF 200
Output I want in this format
DueDate Desc Amount
06-29-2015 ABC 100
DEF 200
01-15-2015 ABC 100
DEF 200
So basically I would like to remove duplicate DueDate values but keeping its adjacent Desc & Amount field values
I tried this but it will remove values from adjacent column as well :
var test = sample.GroupBy(d => d.DueDate).Select(a => a.First()).ToList();
Any suggestions?
Here's how to "remove" (set to null) duplicate, adjacent DueDates from the sample list:
sample.GroupBy(d => d.DueDate).ToList()
.ForEach(g => g.Skip(1).ToList().ForEach(o => o.DueDate = null));
This is done by Group-ing by DueDate, and for each group, Skip-ing the first element, setting the remainder of the elements in the group DueDates to null.
Output with format:
Console.WriteLine("DueDate Desc Amount");
foreach (var item in sample)
{
var dateString = item.DueDate != null
? item.DueDate.Value.ToString("MM-dd-yyyy")
: string.Empty;
Console.WriteLine(dateString.PadRight(12) + item.Desc + " " + item.Amount);
}
Result:
DueDate Desc Amount
06-29-2015 ABC 100
DEF 200
01-15-2015 ABC 100
DEF 200
var finalData = data
.GroupBy(d=>d.DueDate)
.Select(g=>
new {
DueDate = g.Key,
Values = g.Select(d2=>new{d2.Desc, d2.Amount})})
The Final Structure would be
finalDate = [
{
DueDate:'06-29-1015',
Values:[{Desc:"ABC", Amount:100}, {Desc:"DEF", Amount:200}]
},
{...}
]
EDIT:-
var finalData = data
.GroupBy(d=>d.DueDate)
.Select(g=>
new {
DueDate = g.Key,
Values = g.Select(d2=>d2)
})
.ToDictionary(o=>o.DueDate, o=>o.Values)
What you want is a pivot table. this is how it is done :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.Load();
myClass.CreatePivotTable();
}
}
class MyClass
{
public static List<MyClass> samples = new List<MyClass>();
public DateTime dueDate { get; set; }
public string desc { get; set; }
public Decimal amount { get; set; }
public static DataTable dt = new DataTable();
public void Load()
{
samples = new List<MyClass>() {
new MyClass() { dueDate = DateTime.Parse("06-29-2015"), desc = "ABC", amount = 100},
new MyClass() { dueDate = DateTime.Parse("06-29-2015"), desc = "DEF", amount = 200},
new MyClass() { dueDate = DateTime.Parse("01-15-2015"), desc = "ABC", amount = 100},
new MyClass() { dueDate = DateTime.Parse("01-15-2015"), desc = "DEF", amount = 100}
};
}
public void CreatePivotTable()
{
string[] uniqueDescription = samples.Select(x => x.desc).Distinct().ToArray();
dt.Columns.Add("Due Date", typeof(DateTime));
foreach (string desc in uniqueDescription)
{
dt.Columns.Add(desc, typeof(decimal));
}
var groups = samples.GroupBy(x => x.dueDate);
foreach(var group in groups)
{
DataRow newRow = dt.Rows.Add();
newRow["Due Date"] = group.Key;
foreach (string col in uniqueDescription)
{
newRow[col] = group.Where(x => x.desc == col).Sum(x => x.amount);
}
}
}
}
}
I'd simply prefer that you loop through your records after you got them in the correct order. Just start with an empty variable and keep the last date in it. If the next value is the same, just don't plot it out. If you find another date value the next iteration, plot it and overwrite your variable for further iterations.
Yeah I know, Linq and Lambdas are cool and stuff (and I love them too) but in this case it seems to be appropriate to me.
var last = DateTime.MinValue;
foreach (var f in sample.OrderBy(x => x.DueDate))
{
if (f.DueDate.Equals(last))
Console.WriteLine("{0}\t{1}\t{2}", "SKIP DATE", f.Desc, f.Amount);
else
{
Console.WriteLine("{0}\t{1}\t{2}", f.DueDate.ToShortDateString(), f.Desc, f.Amount);
last = f.DueDate;
}
}
Based on your latest comments I have edited my answer.
As I am understanding, your requirements are:
Group by DueDate, and only allow the first of the group to have a
DueDate.
The results have to be the same structure.
If you want to remove the DueDate property from all i>0 items in a group then you need to make your property nullable: public DateTime? DueDate;. This way you can assign the value of null to subsequent items in the group.
//New list to hold our new items
var outputList = new List<MyClass>();
//Groups all the items together by DueDate
foreach(var grouping in samples.GroupBy(d => d.DueDate))
{
//Iterates through all items in a group (selecting the index as well)
foreach(var item in grouping.Select((Value, Index) => new { Value, Index }))
{
//If this is any item after the first one, we remove the due date
if(item.Index > 0)
{
item.Value.DueDate = null;
}
outputList.Add(item.Value);
}
}
Fiddle here.

GROUPBY and SUM on List items using LINQ

I have a List of type DailySummary
public class DailySummary
{
public string AffiliateID { get; set; }
public string TotalCalls { get; set; }
public string Date{ get; set; }
}
with following sample data:
List<DailySummary> DealerTFNDatesTable = new List<DailySummary>();
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/12/2016", TotalCalls = "10"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/13/2016", TotalCalls = "74"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="1", Date = "12/22/2016", TotalCalls = "63"});
DealerTFNDatesTable.Add(new DailySummary() { AffiliateID="0", Date = "12/12/2016", TotalCalls = "58"});
Now I want to retrieve Date and TotalCalls grouped by AffiliateID and assign in another list.
for(int i =0; i < DealerTFNDatesTable.Count; i++)
{
List<NewList> newList = new List<NewList>();
newList.Date = //Assign Dintinct dates WHERE AffiliateId = 0
newList.AffiliateID = //AffiliateID=0
newList.TotalCalls= //TotalCalls SUM GROUPBY DATE and AffiliateID = 0
//For Date '12/12/2016' it will be 68, For '12/13/2016' it will be 74 and so on
}
I'm sorry, I'm new to LINQ. Can someone help me or share resources where I can get a hint to achieve this?
This should work for grouping by AffilateID and Date and then getting the sum (though it's weird to store a number as a string for something like this, but whatever floats your boat).
var results = DealerTFNDatesTable
.GroupBy(x => new { x.AffiliateID, x.Date })
.Select(x => new DailySummary {
AffiliateID = x.First().AffiliateID,
Date = x.First().Date,
TotalCalls = x.Sum(y => Convert.ToInt32(y.TotalCalls)).ToString()
});
If you now look at the result, for example with this code, you get exactly the values you wanted:
foreach (var x in results) {
Console.WriteLine($"id = {x.AffiliateID}, date = {x.Date}, totalCalls = {x.TotalCalls}");
}
> id = 0, date = 12/12/2016, totalCalls = 68
> id = 0, date = 12/13/2016, totalCalls = 74
> id = 1, date = 12/22/2016, totalCalls = 63
First off,
Since DealerTFNDatesTable is a variable, you should use camel case. Thus it is dealerTFNDatesTable
Then to complete #andy his answer, as you also want to do a select. You can select it as follows:
var newVariable = from item in dealerTFNDatesTable
group item by new
{
item.Date,
item.AffiliateID,
}
into g
select new
{
Date = g.Key.Date,
Id = g.Key.AffiliateID,
Total = g.Sum(a => a.TotalCalls)
};
This will give you an IEnumerable, of which you can put the relevant parts in a list by doing var otherList = new List<object>(newVariable
.Where(a => a.Total > 0)); or simply add .ToList() after the select if you want the collection as-is.
Note that this is simply another notation than LINQ, the result is the same.
var results = DealerTFNDatesTable.GroupBy(T => new { T.AffiliateID })
Link

foreach loop data index as the key

I have been trying to make for each loop with the index as the key
this case i want to made a logic if the input user is match with index and i will show foreach all of the data which has index as the key
I made two class like this
class DataContainer
{
public DataContainer()
{
}
public int index { get; set; }
public List<DataValue> DataValue { get; set; }
}
class DataValue
{
public DataValue()
{
IntegerValues = new List<int>();
}
public string name { get; set; }
public List<int> IntegerValues { get; set; }
}
after that i try to make list of datacontainer like this
List<DataContainer> harakatSininilMabsutoh = new List<DataContainer>(){
new DataContainer{index = 2015 , DataValue = new List<DataValue>()
{
new DataValue{name = "first",IntegerValues = {9,55,18,11}},
new DataValue{name = "second" ,IntegerValues = {5,54,18,11}},
new DataValue{name = "third" ,IntegerValues = {40,26,14,11}},
new DataValue{name = "four" ,IntegerValues = {22,0,5,10}},
new DataValue{name = "fifth" ,IntegerValues = {46,44,17,0}},
}
},
new DataContainer{index = 2013 , DataValue = new List<DataValue>()
{
new DataValue{name = "first",IntegerValues = {26,49,8,11}},
new DataValue{name = "second" ,IntegerValues = {19,42,8,11}},
new DataValue{name = "third" ,IntegerValues = {55,3,12,11}},
new DataValue{name = "fourth" ,IntegerValues = {27,4,23,8}},
new DataValue{name = "fifth" ,IntegerValues = {43,22,7,1}},
}
},
new DataContainer{index = 2001, DataValue = new List<DataValue>()
{
new DataValue{name = "first",IntegerValues = {35,44,27,10}},
new DataValue{name = "second" ,IntegerValues = {24,41,27,10}},
new DataValue{name = "third" ,IntegerValues = {36,30,26,10}},
new DataValue{name = "fourth" ,IntegerValues = {59,24,8,6}},
new DataValue{name = "fifth" ,IntegerValues = {29,27,26,1}},
}
}
};
and then i made a logic like this
int years = (this is user input);
if(years == 2015)
{
///How to for each this which has index 2015
}
else if (years = 2013)
{
//how to foreach this which has index 2013
}
else if (years = 2001)
{
//how to foreach this which has index 2001
The simplest is by using LINQ FirstOrDefault like this
int userinput = 2015;
DataContainer requested = harakatSininilMabsutoh.FirstOrDefault(x => x.index == userinput);
if (requested == null) //FirstOrDefault of a class will return null if not found
return;
foreach (DataValue val in requested.DataValue)
Console.WriteLine(val.name + ": " + string.Join(", ", val.IntegerValues));
Edit 2:
If you only need all the integers, without name, without anything else, then you could either do this to get the List<List<int>>:
int userinput = 2015;
List<List<int>> intValuesOnly = harakatSininilMabsutoh
.FirstOrDefault(x => x.index == userinput)
.DataValue
.Select(y => y.IntegerValues)
.ToList();
//Do whatever you want with intValuesOnly. This is everything that you have in a list of lists
or do this to get List<int> (flattened):
int userinput = 2015;
List<int> intValuesOnly = harakatSininilMabsutoh
.FirstOrDefault(x => x.index == userinput)
.DataValue
.SelectMany(y => y.IntegerValues)
.ToList();
//Do whatever you want with intValuesOnly. This is everything that you have in single list
As FirstOrDefault may return null if the userinput is not found, note that, if you are not using C#6, you may want to consider two steps LINQ:
int userinput = 2015;
DataContainer requested = harakatSininilMabsutoh.FirstOrDefault(x => x.index == userinput);
if (requested == null) //FirstOrDefault of a class will return null if not found
return;
List<List<int>> intValuesOnly = requested
.Select(y => y.IntegerValues)
.ToList();
//Do whatever you want with intValuesOnly. This is everything that you have in a list of lists
Firstly, note that in this line you have tried to use a type name as a property name:
public List<DataValue> DataValue { get; set; }
I've renamed this property to 'DataValues' as shown:
public List<DataValue> DataValues { get; set; }
You have a list ('harakatSininilMabsutoh'), each element of which is a DataContainer. Each DataContainer in the list has two properties: an index and a list of 'DataValues' (NB renamed from 'DataValue' in your post).
The looping logic you want will therefore be something like this:
var matchingYears = harakatSininilMabsutoh.Where(h => h.index == years);
foreach (var dataContainer in matchingYears)
{
foreach (var item in dataContainer.DataValues)
{
// Action required on each DataValue:
Debug.Print(item.name + " " + string.Join(",", item.IntegerValues));
}
}
You'll need to add the following 'using' statement to your class, since 'Where' is a LINQ extension method:
using System.Linq;
If you know that there will be exactly one matching year, you could add First() and remove the outer foreach loop. If you know there will be at most one matching year (but there could be zero), you can still remove the outer foreach loop but you should use FirstOrDefault() instead and test for null.

Aggregate hours between two dates with IQueryable from DB

I have table with these columns:
id int, name string, startDate DateTime, endDate DateTime
I want to get from DB SUM of HOURS between these dates for all records.
I use IQueryable, but I don't know how correctly form the query..
public int GetSumOfHours(int? assignedProjectId)
{
var query = GetAll(); //GetAll() returns IQueryable<T>,
if (assignedProjectId.HasValue)
{
query = query.Where(solution => solution.AssignedProject.Id == assignedProjectId.Value);
}
// Get sum of hours ----> query = query...??
}
Thanks for HELP !
Try something like below. Get seconds and then sum the seconds.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataTable query = new DataTable();
query.Columns.Add("id", typeof(int));
query.Columns.Add("name", typeof(string));
query.Columns.Add("startDate", typeof(DateTime));
query.Columns.Add("endDate", typeof(DateTime));
query.Rows.Add(new object[] { 1, "John", DateTime.Parse("1/1/1 0:0:12"), DateTime.Parse("1/1/1 0:1:12") });
query.Rows.Add(new object[] { 1, "John", DateTime.Parse("1/1/1 0:3:12"), DateTime.Parse("1/1/1 0:5:12") });
query.Rows.Add(new object[] { 2, "Bob", DateTime.Parse("1/1/1 0:0:12"), DateTime.Parse("1/1/1 0:1:12") });
query.Rows.Add(new object[] { 2, "Bob", DateTime.Parse("1/1/1 0:0:12"), DateTime.Parse("1/1/1 0:1:12") });
var totalSeconds = query.AsEnumerable()
.Where(x => x.Field<int>("id") == 1)
.Select(x => (x.Field<DateTime>("endDate") - x.Field<DateTime>("startDate")).TotalSeconds).Sum();
}
}
}
​
You can use some math and the Sum() method:
public class hours
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
...
List<hours> allHrs = new List<hours>{
new hours{Start = DateTime.Now.AddHours(-3.2), End = DateTime.Now.AddHours(-2)},
new hours{Start = DateTime.Now.AddHours(-3.9), End = DateTime.Now.AddHours(-2.03)},
new hours{Start = DateTime.Now.AddHours(-3.8), End = DateTime.Now.AddHours(-2.9)}
};
//Project a new collection with the math done for number of minutes in each row
var mins = from h in allHrs
select new {nbrMinutes = (h.End - h.Start).Minutes};
//Sum all rows, divide by 60 to get hours
Double total = mins.Sum (s => s.nbrMinutes / 60.0 );
Console.WriteLine(total);
You could modify CrowCoder's example to sum the hours directly using TimeSpan and methods Subtract and TotalHours:
{
DateTime d = DateTime.Today;
List<hours> exampleHours = new List<hours>{
new hours{Start = d.AddHours(-3.2), End = d.AddHours(-2)},
new hours{Start = d.AddHours(-3.9), End = d.AddHours(-2.03)},
new hours{Start = d.AddHours(-3.8), End = d.AddHours(-2.9)}
};
double totalHours =
(from h in exampleHours
select new {allHours = (h.End.Subtract(h.Start).TotalHours)})
.Sum(t => t.allHours);
Console.WriteLine(totalHours.ToString());
}

Efficiently adding info to a list. Linq Union?

I have some data which is queried from a database, but if there are no records for a particular financial year, it does not get returned in my list. I need all financial years returned, but the actual information (GrossEx, GST, GrossInc) should all be null.
public class FinancialData
{
public string FinancialYear { get; set; }
public string Type { get; set; }
public decimal? GrossEx { get; set; }
public decimal? GST { get; set; }
public decimal? GrossInc { get; set; }
}
How do I efficiently add FinancialYears, from 2004 to 2015 (with Gross, GST and GrossInc being null), to my list if they do not exist?
List<FinancialData> financialDataBucket = new List<FinancialData>();
FinancialData entry1 = new FinancialData { FinancialYear = "2012 - 2013", GrossEx = 1, GrossInc = 1, GST = 1 };
FinancialData entry2 = new FinancialData { FinancialYear = "2013 - 2014", GrossEx = 1, GrossInc = 1, GST = 1 };
financialDataBucket.Add(entry1);
financialDataBucket.Add(entry2);
I've tried doing a Union with a linq comparer, but for some reason it didn't work, and I can't figure out why? Is this the best way to solve my problem?
var merged = allFinancialYearsData.Union(financialDataBucket, new EqComparer());
public class EqComparer : IEqualityComparer<FinancialData>
{
public bool Equals( FinancialData x, FinancialData y )
{
return x.FinancialYear == y.FinancialYear;
}
public int GetHashCode( FinancialData obj )
{
return obj.GetHashCode ();
}
}
EDIT:
So, I'm thinking about either creating 10 different financial year objects and adding them to a list, OR
var currentYear = DateTime.Now.Year + (DateTime.Now.Month < 7 ? 0 : 1);
var earliestYear = 2005;
for (int i = earliestYear; i <= currentYear; i++) {
//Instantiate FinancialData here.....
financialDataItem.FinancialYear = (i-1) + " - " + i
}
Thanks!
You have to perform a group join. In code snippet below, allRecords create dummy FinancialData for "2005 - 2006" to "2014 - 2015".
var allRecords = Enumerable.Range(0, 10).Select(x => new FinancialData { FinancialYear = string.Format("{0} - {1}", 2005 + x, 2005 + x + 1) });
var myRecords = new[] { new FinancialData { FinancialYear = "2012 - 2013", GrossEx = 1, GrossInc = 1, GST = 1 }, new FinancialData { FinancialYear = "2013 - 2014", GrossEx = 1, GrossInc = 1, GST = 1 } };
var result = allRecords.GroupJoin(myRecords
, x => x.FinancialYear, y => y.FinancialYear, (x, y) => y.FirstOrDefault(u => u.FinancialYear == x.FinancialYear) ?? x);

Categories