I have an object like so
public class Person{
public int ID { get; set; }
public string Name{ get; set; }
public string Surname{ get; set; }
public List<int> AltIDs { get; set; }
public List<string> AltNames{ get; set; }
}
I am setting data into the DataGridView like
dataGridView1.DataSource = null;
dataGridView1.DataSource = persons; // persons is List<Person>
However I only see 3 columns in the DataGridView that is of ID, Name and Surname, the properties with List<int> and List<string> seem to be ignored.
Is there a way to get theses properties showing up in the DataGridView? Probably like comma seperated values.
most probably the persons is null
you just change your code like this code
first Solution :
your model :
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public List<int> AltIDs { get; set; }
public List<string> AltNames { get; set; }
}
PersonViewModel :
public class PersonViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int AltID { get; set; }
public string AltName { get; set; }
}
formLoad :
private void Form1_Load(object sender, EventArgs e)
{
List<Person> people = new List<Person>()
{new Person(){ ID=1,Name="a",Surname="a",AltIDs=new List<int>(){1,2,3,4 },AltNames=new List<string>(){"a","b","c" } },
new Person(){ ID=2,Name="b",Surname="b",AltIDs=new List<int>(){10,20,30,40 },AltNames=new List<string>(){"a","b","c" }},
new Person(){ ID=3,Name="c",Surname="c",AltIDs=new List<int>(){100,200,300,400 },AltNames=new List<string>(){"a","b","c" }},
new Person(){ ID=4,Name="d",Surname="d",AltIDs=new List<int>(){1000,2000,3000,4000 },AltNames=new List<string>(){"a","b","c" }},
new Person(){ ID=3,Name="e",Surname="e",AltIDs=new List<int>(){10000,20000,30000,40000 },AltNames=new List<string>(){"a","b","c" }}
};
List<PersonViewModel> pwm = new List<PersonViewModel>();
foreach (var person in people)
{
foreach(var id in person.AltIDs)
{
foreach (var name in person.AltNames)
pwm.Add(new PersonViewModel() { ID = person.ID, Name = person.Name, Surname = person.Surname, AltID = id, AltName = name });
}
}
dgv.DataSource = pwm;
}
Result :
Second Solution :
your model :
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public List<int> AltIDs { get; set; }
public List<string> AltNames { get; set; }
}
PersonViewModel :
public class PersonViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string AltID { get; set; }
public string AltName { get; set; }
}
FormLoad :
private void Form1_Load(object sender, EventArgs e)
{
List<Person> people = new List<Person>()
{new Person(){ ID=1,Name="a",Surname="a",AltIDs=new List<int>(){1,2,3,4 },AltNames=new List<string>(){"a","b","c" } },
new Person(){ ID=2,Name="b",Surname="b",AltIDs=new List<int>(){10,20,30,40 },AltNames=new List<string>(){"a","b","c" }},
new Person(){ ID=3,Name="c",Surname="c",AltIDs=new List<int>(){100,200,300,400 },AltNames=new List<string>(){"a","b","c" }},
new Person(){ ID=4,Name="d",Surname="d",AltIDs=new List<int>(){1000,2000,3000,4000 },AltNames=new List<string>(){"a","b","c" }},
new Person(){ ID=3,Name="e",Surname="e",AltIDs=new List<int>(){10000,20000,30000,40000 },AltNames=new List<string>(){"a","b","c" }}
};
List<PersonViewModel> pwm = new List<PersonViewModel>();
StringBuilder sbIds;
StringBuilder sbNames;
foreach (var person in people)
{
sbIds = new StringBuilder();
sbNames = new StringBuilder();
person.AltIDs.ForEach(c=> sbIds.Append(c.ToString()).Append(","));
person.AltNames.ForEach(c=> sbNames.Append(c).Append(","));
pwm.Add(new PersonViewModel() { ID = person.ID, Name = person.Name, Surname = person.Surname, AltID = sbIds.ToString().TrimEnd(','), AltName = sbNames.ToString().TrimEnd(',') });
}
dgv.DataSource = pwm;
}
Result :
When DataGridView is bound to DataSource, it auto generates columns for it for the properties with primitive datatypes such as int, string, double etc.
For the datatypes which are collections, DataGridViewComboBoxColumn is used. This type of column is not autogenerated. You need to add such columns manually in the GridView columns collection.
For your use case following is the solution.
Add DataGridView to the form and add columns to it manually from the Form's designer view by click on Add Column. You will have to add 3 DataGridViewTextBoxColumn and 2 DataGridViewComboBoxColumn.
After adding columns, the columns would look as following.
Now while assigning DataSource to the DataGridView you need to write following code. Here IdColumn, NameColumn, and SurnameColumn are the names give to columns when they were created in above steps.
dataGridView1.AutoGenerateColumns = false;
IdColumn.DataPropertyName = "ID";
NameColumn.DataPropertyName = "Name";
SurnameColumn.DataPropertyName = "Surname";
dataGridView1.DataSource = persons;
With the above code you will see Id, Name and Surname columns populated for persons in the collection but the dropdown lists in last two columns are empty.
To populate the dropdown list columns you need to add event handler for CellClick event of the DataGridView. And write following code there.
Here AltIdsColumn and AltNamesColumn are the names given to column when they created manually in earlier steps.
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
var altIdIndex = dataGridView1.Columns["AltIdsColumn"].Index;
var altNameIndex = dataGridView1.Columns["AltNamesColumn"].Index;
if (altIdIndex == e.ColumnIndex || altNameIndex == e.ColumnIndex)
{
var altIdsCell = (DataGridViewComboBoxCell)dataGridView1.Rows[e.RowIndex].Cells[altIdIndex];
var altNamesCell = (DataGridViewComboBoxCell)dataGridView1.Rows[e.RowIndex].Cells[altNameIndex];
if (altIdsCell.DataSource == null || altNamesCell.DataSource == null)
{
var person = dataGridView1.Rows[e.RowIndex].DataBoundItem as Person;
if (person != null)
{
altIdsCell.DataSource = person.AltIDs;
altNamesCell.DataSource = person.AltNames;
}
}
}
}
With this code, the dropdown list of AldIDs and AltNames columns will be populated when you click on those dropdown lists on individual rows.
I hope this will help you resolve your issue.
From what I can tell, this appears as a fairly basic “Master/Slave” type UI. In the grid, there is a “Person” object with the ID, Name and Surname. Obviously from your question, the AltIDs and AltNames Lists are NOT displayed in the grid.
This is due to the fact that the grid has a problem trying to add a “list of multiple” values into a “single” cell. As suggested, it is possible for you to “combine” the values into a “single” string and use that. However, this is extra work on your part and may create more work if the cell is edited.
One issue in this example is that, the AltIDs list for each Person could and will have a different number of elements. One possible solution is to simply ADD theses as new rows where the Person info (ID, Name and Surname) are duplicated. This will work but IMHO not very user friendly. The combo box option will also work, however again it may be confusing to users since a combo box “usually” indicates that the users selects a “single” value from many.
It is also possible to “flatten” each person object and have a column for each AltID in the list. This will work but the possibility of large gaps in the grid are likely. Again, not very user friendly. This is all doubled by the fact that there is another list AltNames that we have to take into account.
Given this, it appears clear that YOU are going to have to do extra work UNLESS you use an advanced third-party grid OR add more grids to the picture… in this case three (3) total. One for the person, another for the IDs and a third for the Names. Proper arrangement of the grids may be a little work, however, with three grids, it will make all the issues described above… go away. In addition the coding will be much easier.
It would be such that the user “selects” a Person from the first grid, then the second grid list all the AltID values and the third grid lists all the AltName values. If the “same” data source is used for each grid then… when the user selects a different Person in the person grid, the AltID grid and AltName grid will “automatically” update/refresh with the proper values. This will also make CRUD operations on any grid/value much easier.
The only problem I seen in the current Person class is that the two Lists are of “primitive” types. Used this way, the lists won’t display properly, the grid wants a CLASS. Therefore, you need to make a wrapper class for the AltID and AltName lists. Then change the list values in the Person class. Something like…
public class AltID_C {
public int AltID { get; set; }
}
public class AltName_C {
public string AltName { get; set; }
}
public class Person {
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public List<AltID_C> AltIDs { get; set; }
public List<AltName_C> AltNames { get; set; }
}
With this, all that is needed is to set each grid to the “same” data source, then set the AltID grids DataMember to the “AltIDs” property and set the AltNames grids DataMember to the “AltNames` property. Something like…
List<Person> AllPersons;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
AllPersons = GetRandomData(25);
dgvPerson.DataSource = AllPersons;
dgvAltID.DataSource = AllPersons;
dgvAltID.DataMember = "AltIDs";
dgvAltNames.DataSource = AllPersons;
dgvAltNames.DataMember = "AltNames";
}
Additional code below to complete the example.
private List<Person> GetRandomData(int numberOfPersons) {
List<Person> listOPeople = new List<Person>();
Person curPerson;
Random rand = new Random();
for (int i = 0; i < numberOfPersons - 1; i++) {
curPerson = new Person() {
ID = rand.Next(1, 1000),
Name = "Name_" + i + 1,
Surname = "Sname_" + i + 1,
AltIDs = GetRandomNumberOfInts(rand),
AltNames = GetRandomNumberOfStrings(rand)
};
listOPeople.Add(curPerson);
}
return listOPeople;
}
private List<AltID_C> GetRandomNumberOfInts(Random rand) {
List<AltID_C> listOInts = new List<AltID_C>();
int numberOfInts = rand.Next(0, 10);
for (int i = 0; i < numberOfInts - 1; i++) {
listOInts.Add(new AltID_C { AltID = rand.Next(1, 10000) });
}
return listOInts;
}
private List<AltName_C> GetRandomNumberOfStrings(Random rand) {
List<AltName_C> listOStrings = new List<AltName_C>();
int numberOfStrings = rand.Next(0, 10);
for (int i = 0; i < numberOfStrings - 1; i++) {
listOStrings.Add(new AltName_C { AltName = "RandString: " + rand.Next(1, 1000) });
}
return listOStrings;
}
Hope that helps.
I want to get all these values from table Trouble and paste into Combobox.
I have this code,but i get System.ObjectDisposedException error
var items = db.Trouble.Where(u => u.id_Проблемы > 0).Select(u => u.id_Проблемы);
id_Trouble_box.Items.Add(items);
You should force Immediately excution by .ToArray() or ToList()
var items = db.Trouble.Where(u => u.id_Проблемы > 0).Select(u => u.id_Проблемы).ToArray();
id_Trouble_box.Items.Add(items);
Read the following thread to have a better understanding.
LINQ performance - deferred v/s immediate execution
Add a .ToArray() at the end. Perhaps you have it adding items outside a using statement.
If you want to select them all then you don't need a Where criteria:
var items = db.Trouble();
and to add to a combo you could set the DataSource:
var items = db.Trouble();
id_Trouble_box.DataSource = items.ToList();
id_Trouble_box.DisplayMember = "columnToShow"; // display column
id_Trouble_box.ValueMember = "id_Проблемы"; // id to get on selection as a value
EDIT: For those who don't understand why this answer fixes the error:
string defaultConString = #"server=.\SQLExpress;Database=Northwind;Trusted_Connection=yes;";
void Main()
{
Form f = new Form();
ComboBox cb = new ComboBox { Top = 10, Left = 10 };
f.Controls.Add(cb);
using (var ctx = new MyContext(defaultConString))
{
var items = ctx.Customers.Where(c => c.ContactName.Contains("a")).Select(c => c.CompanyName);
cb.Items.Add(items);
}
f.Show();
}
public class MyContext : DbContext
{
public MyContext(string connectionString)
: base(connectionString)
{ }
public DbSet<Customer> Customers { get; set; }
}
public class Customer
{
[Key]
public string CustomerId { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
// ...
// public virtual List<Order> Orders { get; set; }
}
I solved the problem this way
var items = db.Trouble.Where(u => u.id_Проблемы > 0).Select(u => u.id_Проблемы).ToArray();
for(int i = 0; i < items.Length; i++)
{
id_Trouble_box.Items.Add(items[i]);
}
I have a business object that returns a list of objects. This list is then being used as a datasource for a datagridview on a winform. I load my list, set it to the datasource, refresh and view. One of the columns is present but has no values. I see the header but no data. The column is for TruckColorId. Id shows the correct value. Debugging the code I can see that there are objects in the list with all the properties I expect to see correctly with data, including the field in question.
If I select a row and do a foreach loop of the selected row all columns have value but the TruckColorId.
public class TruckColor : EditBase
{
public int TruckColorId { get; set; }
public override int Id
{
get { return TruckColorId; }
set { TruckColorId = value; }
}
}
public class EditBase
{
public virtual int Id { get; set; }
public string ShortCode { get; set; }
public string Description { get; set; }
public IList<EditBase> GetAll()
{
return new List<EditBase>()
{
new TruckColor
{
Description = "abc",
Id = 1,
ShortCode = "A"
},
new TruckColor
{
Description = "abcd",
Id = 2,
ShortCode = "B"
},
new TruckColor
{
Description = "abcde",
Id = 3,
ShortCode = "C"
},
};
}
}
And here is the code in my form to load the trucks:
public void InitTrucks()
{
TruckColor truck = new TruckColor();
var trucks = truck.GetAll();
if (trucks.Count() > 0)
{
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = trucks;
dataGridView1.Refresh();
}
}
try this
var trucks = truck.GetAll();
if (trucks.Count() > 0)
{
List<TruckColor> RealTrucks = trucks.Select(x=>(TruckColor)x).ToList();
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = RealTrucks;
dataGridView1.Refresh();
}
I have the following test:
public class ListingEventTest
{
public ListingEventTest()
{
Artists = new List<ArtistTest>();
}
public List<ArtistTest> Artists { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public double Popularity { get; set; }
}
public class ArtistTest
{
public string Id { get; set; }
public Stat Stats { get; set; }
}
public class Stat
{
public double Popularity { get; set; }
}
public class ArtistsWithStats_ByName : AbstractIndexCreationTask<ListingEventTest>
{
public ArtistsWithStats_ByName()
{
Map = listingEvents => from listingEvent in listingEvents
let artists = LoadDocument<ArtistTest>(listingEvent.Artists.Select(x => x.Id))
select new
{
Popularity = artists.Average(x => x.Stats.Popularity),
listingEvent.Name
};
}
}
[TestFixture]
public class IndexCanDoSums
{
[Test]
public async void WhenListingEventArtistsHaveStatsIndexReturnsPopularity()
{
var store = new EmbeddableDocumentStore
{
UseEmbeddedHttpServer = true,
Configuration =
{
RunInUnreliableYetFastModeThatIsNotSuitableForProduction = true,
RunInMemory = true,
}
}.Initialize();
IndexCreation.CreateIndexes(typeof(ArtistsWithStats_ByName).Assembly, store);
using (var session = store.OpenAsyncSession())
{
for (int i = 0; i < 10; i++)
{
var le = new ListingEventTest
{
Name = "test-" + i
};
await session.StoreAsync(le);
for (int j = 0; j < 2; j++)
{
var artist = new ArtistTest
{
Stats = new Stat
{
Popularity = 0.89d
}
};
await session.StoreAsync(artist);
le.Artists.Add(artist);
}
await session.SaveChangesAsync();
}
}
Thread.Sleep(2000);
using (var session = store.OpenAsyncSession())
{
var query = session
.Advanced.AsyncLuceneQuery<ListingEventTest>("ArtistsWithStats/ByName");
var result = await query.ToListAsync();
result.First().Popularity.Should().NotBe(0);
}
}
}
When I query this index Popularity is always 0.
Any ideas?
Some funny things going on here.
First, you are storing ArtistTest under the ListingEventTest document, not as separate documents, so in your index there is no need to call LoadDocument, you could just do:
from listingEvent in listingEvents
from artist in listingEvent.Artists
select ...
Second, a Map-only index is a lot like a SQL index where you're just calling out the columns you want to be able to query on. Here, you're doing a calculation on a set of buried properties and you have a top-level property where you want to store that information, but how that ends up working is that your calculated property value goes into the Lucene index (so you could query by Popularity if you want) but the data that is returned is straight from the unaltered document. The map defines what goes into Lucene, which points to the document id, and then the document store returns the documents as the results.
This could be modified somewhat by calling Store(x => x.Popularity) in the index's constructor, which would store the value to be recalled later, but honestly offhand I'm not sure if your calculated value or the document's value (which is zero) would win.
Given that, it becomes pretty confusing to have a document property for the sole purpose of trying to fill it during indexing, which is why it's usually a better option to have a class that represents the mapped state, and then implementing AbstractIndexCreationTask<TDocument, TReduceResult> where the TReduceResult class would only contain the result of your mapping, namely the Name and Popularity columns.
Then when you query from, you can use .ProjectFromIndexFieldsInto<T>() to get your results from the stored index results, not from the document store.
What I've got are two classes which each contain Lists of Classes with propperties of different types. The first list is an updated version of the second and i need to find all differences (deleted/added classes in lists and updated classes).
public class ClassOfKb
{
public List<Data> KbData {get;set;}
public List<Info> KbInfo {get;set;}
}
class Data
{
public Guid ID {get;set}
public byte[] file {get;set}
public string name {get;set}
}
class Info
{
public Guid ID {get;set}
public string text {get;set}
public DateTime date {get;set}
}
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
first KbA and KbB will be filled from the same DataSet, then i delete, add and modify some of KbA Child-Classes.
now i need to compare KbA with KbB to find out where the differences are. i need the ID of deleted or added classes in KbA and the exact changes of modified Child-Classes properties. How would i do this? Preffered with Linq.
I suggest that create two comparers one for Data and one for Info
class DataComparer : IEqualityComparer<Data>
{
public bool Equals(Data x, Data y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Data d)
{
//logic to return a hash code
}
}
class InfoComparer : IEqualityComparer<Info>
{
public bool Equals(Info x, Info y)
{
//logic to compare x to y and return true when they are equal
}
public int GetHashCode(Info i)
{
//logic to return a hash code
}
}
The you can use Intersect and Except LINQ methods
IEnumerable<Data> DataInAandNotInB = KbA.KbData.Except(KbB.KbData,new DataComparer());
IEnumerable<Info> InfoInAandInB = KbA.KbInfo.Intersect(KbB.KbInfo,new InfoComparer ());
For simplicity, I skipped comparison of the byte array and DateTime data membes, only left the IDs and the string data members, but to add them you will need some small modification.
The test is very-very basic, but shows all three of the changes options:
static void Main(string[] args)
{
ClassOfKb KbA = new ClassOfKb();
ClassOfKb KbB = new ClassOfKb();
// Test data --------
Data data1 = new Data() { ID = Guid.NewGuid(), name = "111" };
Data data2 = new Data() { ID = Guid.NewGuid(), name = "222" };
Data data2_changed = new Data() { ID = data2.ID, name = "222_changed" };
Data data3 = new Data() { ID = Guid.NewGuid(), name = "333" };
Info info1 = new Info() { ID = Guid.NewGuid(), text = "aaa" };
Info info2 = new Info() { ID = Guid.NewGuid(), text = "bbb" };
Info info2_changed = new Info() { ID = info2.ID, text = "bbb_changed" };
Info info3 = new Info() { ID = Guid.NewGuid(), text = "ccc" };
KbA.KbData.Add(data1);
KbA.KbData.Add(data2);
KbA.KbInfo.Add(info1);
KbA.KbInfo.Add(info2);
KbB.KbData.Add(data2_changed);
KbB.KbData.Add(data3);
KbB.KbInfo.Add(info2_changed);
KbB.KbInfo.Add(info3);
// end of test data ---------
// here is the solution:
var indexes = Enumerable.Range(0, KbA.KbData.Count);
var deleted = from i in indexes
where !KbB.KbData.Select((n) => n.ID).Contains(KbA.KbData[i].ID)
select new
{
Name = KbA.KbData[i].name,
KbDataID = KbA.KbData[i].ID,
KbInfoID = KbA.KbInfo[i].ID
};
Console.WriteLine("deleted:");
foreach (var val in deleted)
{
Console.WriteLine(val.Name);
}
var added = from i in indexes
where !KbA.KbData.Select((n) => n.ID).Contains(KbB.KbData[i].ID)
select new
{
Name = KbB.KbData[i].name,
KbDataID = KbB.KbData[i].ID,
KbInfoID = KbB.KbInfo[i].ID
};
Console.WriteLine("added:");
foreach (var val in added)
{
Console.WriteLine(val.Name);
}
var changed = from i in indexes
from j in indexes
where KbB.KbData[i].ID == KbA.KbData[j].ID &&
(//KbB.KbData[i].file != KbA.KbData[j].file ||
KbB.KbData[i].name != KbA.KbData[j].name ||
//KbB.KbInfo[i].date != KbA.KbInfo[j].date ||
KbB.KbInfo[i].text != KbA.KbInfo[j].text
)
select new
{
Name = KbA.KbData[j].name,
KbDataID = KbA.KbData[j].ID,
KbInfoID = KbA.KbInfo[j].ID
};
Console.WriteLine("changed:");
foreach (var val in changed)
{
Console.WriteLine(val.Name);
}
Console.ReadLine();
}
}
public class ClassOfKb
{
public List<Data> KbData = new List<Data>();
public List<Info> KbInfo = new List<Info>();
}
public class Data
{
public Guid ID { get; set; }
public byte[] file { get; set; }
public string name { get; set; }
}
public class Info
{
public Guid ID { get; set; }
public string text { get; set; }
public DateTime date { get; set; }
}