Related
I've been trying to finish a project of mine studying C# for now and have been encountered with a problem.
The task is to create ill or healthy animals and separate them to different arrays.
Everything worked fine until I tried to merge between an array to a ListView.
What I want to do is to use the array's information and to insert it into the list's columns, which I don't know how to do.
It's purpose is to make each array communicate with the list, when you add or remove animals from any array, it deletes the specific animal from the list, and same thing about adding animals.
This is what I've been trying to do but it is not a good job. I need to collect the info from the array and not from the textboxes.
private void btnAddStack_Click(object sender, EventArgs e)
{
if (txtCode.Text == "Example: 1234")
{
MessageBox.Show("Fill in animal's code in order to proceed", "Error");
txtCode.Focus();
return;
}
if (txtName.Text == "Example: Lion")
{
MessageBox.Show("Fill in animal's name in order to proceed", "Error");
txtName.Focus();
return;
}
if (txtWeight.Text == "Example: 100")
{
MessageBox.Show("Fill in animal's weight in order to proceed", "Error");
txtWeight.Focus();
return;
}
if (rbFemale.Checked == false && rbMale.Checked == false)
{
MessageBox.Show("Fill in animal's gender in order to proceed", "Error");
return;
}
if (rbNoMarine.Checked == false && rbYesMarine.Checked == false)
{
MessageBox.Show("Choose if the animal is marine or not in order to proceed", "Error");
return;
}
btnTakeCareSt.Enabled = true;
service.addNewAnimal(int.Parse(txtCode.Text), txtName.Text, float.Parse(txtWeight.Text), CheckMarine(), CheckUrgent(), CheckGender());
lvStack.Items.Add(new ListViewItem(new[] {txtName.Text, txtCode.Text, txtWeight.Text, BoolToString(), CheckMarine().ToString()}));
}
The size of the Array is fixed, you cannot add or remove its items dynamically. Maybe using List is a better choice.
Here I have a class Student for test, and you can define a List to store its instances.
/// <summary>
/// Student class for test
/// </summary>
class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
Define a list in Form class:
// Define a list to store students
List<Student> students = new List<Student>() { new Student { Name = "A", Age = 10 },
new Student { Name = "B", Age = 9 },
new Student { Name = "C", Age = 14 } };
Then you can refer to the following code to store the items in list to ListView.
private void Form1_Load(object sender, EventArgs e)
{
listView1.View = View.Details;
this.listView1.Columns.Add("Name", 120, HorizontalAlignment.Left);
this.listView1.Columns.Add("Age", 120, HorizontalAlignment.Left);
this.listView1.BeginUpdate();
for (int i = 0; i < students.Count; i++)
{
ListViewItem lvi = new ListViewItem();
lvi.Text = students[i].Name;
lvi.SubItems.Add(students[i].Age.ToString());
this.listView1.Items.Add(lvi);
}
this.listView1.EndUpdate();
}
To add a new item, please refer to:
private void btnAdd_Click(object sender, EventArgs e)
{
// Add a new student named "D" and age is 17;
string name = "D";
int age = 17;
// Add new student to list and ListView
Student student = new Student() { Name = name, Age = age };
students.Add(student);
ListViewItem lvi = new ListViewItem();
lvi.Text = student.Name;
lvi.SubItems.Add(student.Age.ToString());
this.listView1.Items.Add(lvi);
}
As to remove a specific item, you can use:
private void btnRemove_Click(object sender, EventArgs e)
{
// Remove the student whose name is "B"
string name = "B";
// Remove the data in list
var found = students.Find(x => x.Name == name);
if (found != null) students.Remove(found);
// Remove the item in ListView
ListViewItem foundItem = this.listView1.FindItemWithText(name, true, 0);
if (foundItem != null)
{
listView1.Items.Remove(foundItem);
}
}
I am trying to output from Array list to a Listbox. My problem is I think is I do not know how to connect the Class to the Generic array list a made? The end result should look like this:
And the information should be then sorted like so: all the information enters the first list box, and then the above 18 goes to adults, and the below 18 to kids. My class looks like this:
namespace Patients
{
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public String Password { get; set; }
public Person() //Constructor
{
Age = 0;
Password = "";
}
public Person (string name, int age, string password) //Parameters
{
this.Name = name;
this.Age = age;
this.Password = password;
}
public override string ToString() //
{
return Name + Age.ToString() + Password; //outputs as a string
// return Name + " (" + Age + " years) " + Password ;
}
}
}
namespace Patients
{
public partial class WebForm1 : System.Web.UI.Page
{
public static void Page_Load(object sender, EventArgs e)
{
}
public void ButtonAdd_Click(object sender, EventArgs e)
{
Person p = new Person();
List<string> People = new List<string>();
People.Add(TextBoxName.Text);
People.Add(TextBoxAge.Text);
People.Add(TextBoxPassword.Text);
foreach (object Person in People)
{
ListBoxAll.Items.Add(p.Name + p.Age.ToString() + p.Password);
}
if (p.Age > 18)
{
ListBoxAdults.Items.Add(p.Name + p.Age.ToString() + p.Password);
}
else
{
ListBoxKids.Items.Add(p.Name + p.Age.ToString() + p.Password);
}
}
}
}
I think your problem is, that you don't set the Properties. In Fact you don't need a List at all, but you can use a List to keep hold of your patients. It's still not necessary though:
namespace Patients
{
public partial class WebForm1 : System.Web.UI.Page
{
// Define Property and initialize List
public List<Person> patients{ get; } = new List<Person>();
public static void Page_Load(object sender, EventArgs e)
{
}
public void ButtonAdd_Click(object sender, EventArgs e)
{
// Use the Constructor with Parameters
Person p = new Person(TextBoxName.Text, TextBoxAge.Text, TextBoxPassword.Text);
// Add your patient to your List
patients.Add(p);
// Use the ToString() of your Person
ListBoxAll.Items.Add(p.ToString());
if (p.Age > 18)
{
ListBoxAdults.Items.Add(p.ToString());
}
else
{
ListBoxKids.Items.Add(p.ToString());
}
}
}
}
Looks like you are mixing and matching a bit.
Try something like this.
Person p = new Person();
p.Name = TextBoxName.Text;
p.Age= TextBoxAge.Text;
p.Password= TextBoxPassword.Text;
ListBoxAll.Items.Add(p);
A few tricks that are nice to us, first off you can declare defaults for properties like so:
public string Name { get; set; } = "Steve";
public int Age { get; set; } = 1;
public String Password { get; set; } = "password";
However, it should also be noted that "" is the default for strings already and 0 is the default for non-nullable int, so you don't even need to worrying about those default values.
Declaring Age = 0; in the constructor is basically a waste of time in this case. (If it was a nullable int however the default is null)
Next up, since you are okay with defaults, you don't need to declare properties in the constructor like you are.
You can completely remove the constructor and just do the following:
var myPerson = new Person { Name = "Steve", Age = 18, Password = "Foo" };
Next up, you are losing all your existing people as soon as you exit the scope of the button click.
Instead you'll want to declare two lists of people outside the scope of the click method (that way they persist), something like "Adults" and "Children"
Then perhaps make a method called "PopulateLists" that would do the following:
Clear all list boxes
Add to each box the list of each groups names that apply (you can make an IQueryable by using Linq and Select statements on your list)
When you click the button, you should make a new person, assign it to the right list, then call PopulateLists()
Here's the info you need to get started:
Linq selection to get list of properties (in this case Im going to turn a List of People into a List of Ages, you can do the same with names though)
var ages = People.Select(p => p.Age);
The .Items property of a ListBox works the same as a list, it just visually shows itself. It's a list of strings specifically.
So for example you can do things like...
MyListBox.Items.Clear();
MyListBox.Items.Add(...);
MyListBox.Items.AddRange(...);
etc etc.
That should get you started!
So my program is going to end up being fairly large and I don't want a whole lot of code that could just be shortened. Here is one instance I am looking for some tips on:
private void bookComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
string books = null;
// sets books to the clicked item
books = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
// Loads string to list box and image to selectedPictureBox when programming is selected
if (books == "Programming")
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile("programming.png");
bookListBox.Items.Add("Visual Basic");
bookListBox.Items.Add("Java");
bookListBox.Items.Add("C#");
}
// Loads string to list box and image to selectedPictureBox when Networking is selected
else if (books == "Networking")
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile("networking.png");
bookListBox.Items.Add("LAN Networks");
bookListBox.Items.Add("Windows Networking");
bookListBox.Items.Add("More About Networking");
}
// Loads string to list box and image to selectedPictureBox when Web is selected
else if (books == "Web")
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile("html.png");
bookListBox.Items.Add("Web Programming");
bookListBox.Items.Add("JavaScript");
bookListBox.Items.Add("ASP");
}
}
The code works fine but I was just hoping to get some tips on shortening this code if necessary, any input is appreciated.
Assuming you can use C# 7's new Tuples:
private Dictionary<string, (string image, List<string> books)> books = new Dictionary<string, (string image, List<string> books)>
{
{ "Programming", ("programming.png", new List<string> { "Visual Basic", "Java", "C#"} ) },
{ "Networking", ("networking.png", new List<string> {"LAN Networks", "Windows Networking", "More About Networking"}) },
{ "Web", ("html.png", new List<string> {"Web Programming", "Javascript", "ASP"}) }
};
private void bookComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
// sets books to the clicked item
string book = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
if (books.Keys.Contains(book))
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile(books[book].image);
foreach(var b in books[book].books)
{
bookListBox.Items.Add(b);
}
}
}
But a class is likely even better:
public class BookGroup
{
public string ImagePath {get;set;}
public List<string> Books {get;}
public BookGroup(string imagePath, params string[] books)
{
ImagePath = imagePath;
Books = new List<string>(books.Length);
Books.AddRange(books);
}
}
Which isn't all that different to use right now, but it formalizes some things that might make this code easier to work with down the road, and it's possible if you can't use Tuples yet. I might also have a Book class by itself, just for fun, but for now:
private Dictionary<string, BookGroup> books = new Dictionary<string, BookGroup>
{
{ "Programming", new BookGroup("programming.png", "Visual Basic", "Java", "C#")},
{ "Networking", new BookGroup("networking.png","LAN Networks", "Windows Networking", "More About Networking") },
{ "Web", new BookGroup("html.png", "Web Programming", "Javascript", "ASP") }
};
private void bookComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
// sets books to the clicked item
string book = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
if (books.Keys.Contains(book))
{
bookListBox.Items.Clear();
BookGroup bg = books[book];
selectedPictureBox.Image = Image.FromFile(bg.ImagePath);
foreach(var b in bg.Books)
{
bookListBox.Items.Add(b);
}
}
}
Regardless, I'd definitely have a way to load these from a text file... likely a csv, or maybe even a small in-process database, so that I could update this listing without having to recompile or distribute new program code. And, with that in mind, in order to fit this data in a single structure in a single file, I'd likely also repeat the image and type with each book, so that my csv data looks like this:
Topic,Image,Title
Programming,programming.png,"Visual Basic"
Programming,programming.png,"Java"
Programming,programming.png,"C#"
Networking,networking.png,"LAN Networks"
Networking,networking.png,"Windows Networking"
Networking,networking.png,"More About Networking"
Web,html.png,"Web Programming"
Web,html.png,"Javascript"
Web,html.png,"ASP"
That changes the whole character of the code. I'm a bit biased, but I'd likely use this CSV Parser, and again assuming Tuples I'd produce something like this:
private List<(string Topic, string ImagePath, string Title)> books;
//In the form load code:
books = EasyCSV.FromFile("bookData.csv").Select(b => (b[0], b[1], b[2])).ToList();
//and finally, in the original selectindexchanged method:
private void bookComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
string topic = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
var items = books.Where(b => b.Topic == topic).ToArray();
if(items.Length > 0)
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile(items[0].ImagePath);
bookListBox.Items.AddRange(items);
}
}
Make objects and use databindings.
public class Book
{
public BookType BookType { get; set; }
public string Name { get; set; }
public string Image { get; set; }
}
public enum BookType
{
Programming,
Networking,
Web,
}
public partial class Form1 : Form
{
private readonly List<Book> _books = new List<Book>
{
new Book { Image = "programming.png", BookType = BookType.Programming, Name = "VB" },
new Book { Image = "programming.png", BookType = BookType.Programming, Name = "Java" },
new Book { Image = "programming.png", BookType = BookType.Programming, Name = "C#" },
new Book { Image = "networking.png", BookType = BookType.Networking, Name = "LAN Networks" },
new Book { Image = "networking.png", BookType = BookType.Networking, Name = "Windows Networking" },
new Book { Image = "networking.png", BookType = BookType.Networking, Name = "More About Networking" },
new Book { Image = "html.png", BookType = BookType.Web, Name = "Web Programming" },
new Book { Image = "html.png", BookType = BookType.Web, Name = "Javascript" },
new Book { Image = "html.png", BookType = BookType.Web, Name = "ASP" },
};
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var bookTypes = _books.GroupBy(b => b.BookType).Select(g => g.Key).ToList();
this.cboBookTypes.DataSource = bookTypes;
}
private void cboBookTypes_SelectedIndexChanged(object sender, EventArgs e)
{
var bookType = (BookType)this.cboBookTypes.SelectedItem;
var books = _books.Where(b => b.BookType == bookType).ToList();
var img = books.First().Image;
this.imgBook.Image = Image.FromFile(img);
this.lstBooks.DataSource = books;
this.lstBooks.DisplayMember = "Name";
}
}
If you are talking about the length of the code, I would suggest using switch-case-break-default construct
Switch the books variable.
This wont improve the performance though
I think you should create a class that represents book category. Then, you could simply iterate through all the category lists and extract the necessary information, like this:
string books = null;
books = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
for (int i = 0; i < categories.Count; i++) {
if (books == categories[i].Name) {
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile(categories[i].ImagePath);
for (int j = 0; j < categories[i].Items.Count; j++) {
bookListBox.Items.Add(categories[i].Items[j]);
}
}
}
I would suggest to keep all the data in a configuration object and then iterate through that data when performing checks and assignments.
I would create a separate class to hold data for each book: name, picture file name and check box items string array.
Then I would create a list of that object and assign all the data manually on form initialization.
After that, in SelectedIndexChanged event handler, I would iterate (for loop) on each item and check if the book name matched. If it did, then I would use data from that object and then break; the loop.
I do not have visual studio, so giving you the points/suggestions to improve on.
switch should be preferred over if-elseif.
bookListBox.Items.Clear(); and selectedPictureBox.Image out of if block. Use a variable to set the image file name.
Create a class to represent a book list:
public class BookList
{
public string ImageName { get; set; }
public List<string> Items { get;set; }
}
Then create a dictionary to hold these items:
Dictionary<string, BookList> bookLists = new Dictionary<string, BookList>
{
{
"Programming",
new BookList { ImageName = "programming.png", Items = new List<string> { ... } }
}
...
};
Then modify your if statements to:
if (bookLists.ContainsKey(books))
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile(bookLists[books].ImageName);
foreach (var b in bookLists[books].Items)
{
bookListBox.Items.Add(b);
}
}
I have been trying to figure this out in c#. I just started doing C# so I would appreciate your help.
I want to copy a list of Objects to a list in another object.
It looks something like this.
class Person
{
public String fName;
public String lName;
public List<House> housesOwned = new List<House>();
public Student(String FName, String LName)
{
this.fName = FName;
this.lName = LName;
}
}
class House
{
public String Address;
public House (String ad1){
this.Address1 = ad1;
}
}
Now, in my Form I created a list of objects type House (it has 2 objects of type House to be precise), which are the two houses that the person owns.
Something like : List<House> housesList = new List<House>;
Basically what I am trying to do is to copy the List<House> housesList created in the form to the List<House> housesOwned which is the list in the object Person. This will happen when pressing the submit button. So far I got this:
List<Person> person = new List<Person>(); // declared at the beginning of the form
.....
private void submit_Click(object sender, EventArgs e)
{
person.Add(new Person(personName.Text, personLName.Text));
//I do not know what comes next to copy the list housesList to the list housesOwned
MessageBox.Show("Done!");
}
I want the objects houses to be copied containing their addresses. Thank you very much for all your help.
Keep the instance of the new Person you created, then add the houseList to that instance.
private void submit_Click(object sender, EventArgs e)
{
var newPerson = new Person(personName.Text, personLName.Text);
newPerson.housesOwned.AddRange(houseList);
person.Add(newPerson);
MessageBox.Show("Done!");
}
Try this.
List<Person> people = new List<Person>();
var person = new Person(personName.Text, personLName.Text);
person.housesOwned.AddRange(housesList);
people.Add(person);
I have a grid, and I'm setting the DataSource to a List<IListItem>. What I want is to have the list bind to the underlying type, and disply those properties, rather than the properties defined in IListItem. So:
public interface IListItem
{
string Id;
string Name;
}
public class User : IListItem
{
string Id { get; set; };
string Name { get; set; };
string UserSpecificField { get; set; };
}
public class Location : IListItem
{
string Id { get; set; };
string Name { get; set; };
string LocationSpecificField { get; set; };
}
How do I bind to a grid so that if my List<IListItem> contains users I will see the user-specific field? Edit: Note that any given list I want to bind to the Datagrid will be comprised of a single underlying type.
Data-binding to lists follows the following strategy:
does the data-source implement IListSource? if so, goto 2 with the result of GetList()
does the data-source implement IList? if not, throw an error; list expected
does the data-source implement ITypedList? if so use this for metadata (exit)
does the data-source have a non-object indexer, public Foo this[int index] (for some Foo)? if so, use typeof(Foo) for metadata
is there anything in the list? if so, use the first item (list[0]) for metadata
no metadata available
List<IListItem> falls into "4" above, since it has a typed indexer of type IListItem - and so it will get the metadata via TypeDescriptor.GetProperties(typeof(IListItem)).
So now, you have three options:
write a TypeDescriptionProvider that returns the properties for IListItem - I'm not sure this is feasible since you can't possibly know what the concrete type is given just IListItem
use the correctly typed list (List<User> etc) - simply as a simple way of getting an IList with a non-object indexer
write an ITypedList wrapper (lots of work)
use something like ArrayList (i.e. no public non-object indexer) - very hacky!
My preference is for using the correct type of List<>... here's an AutoCast method that does this for you without having to know the types (with sample usage);
Note that this only works for homogeneous data (i.e. all the objects are the same), and it requires at least one object in the list to infer the type...
// infers the correct list type from the contents
static IList AutoCast(this IList list) {
if (list == null) throw new ArgumentNullException("list");
if (list.Count == 0) throw new InvalidOperationException(
"Cannot AutoCast an empty list");
Type type = list[0].GetType();
IList result = (IList) Activator.CreateInstance(typeof(List<>)
.MakeGenericType(type), list.Count);
foreach (object obj in list) result.Add(obj);
return result;
}
// usage
[STAThread]
static void Main() {
Application.EnableVisualStyles();
List<IListItem> data = new List<IListItem> {
new User { Id = "1", Name = "abc", UserSpecificField = "def"},
new User { Id = "2", Name = "ghi", UserSpecificField = "jkl"},
};
ShowData(data, "Before change - no UserSpecifiedField");
ShowData(data.AutoCast(), "After change - has UserSpecifiedField");
}
static void ShowData(object dataSource, string caption) {
Application.Run(new Form {
Text = caption,
Controls = {
new DataGridView {
Dock = DockStyle.Fill,
DataSource = dataSource,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false
}
}
});
}
As long as you know for sure that the members of the List<IListItem> are all going to be of the same derived type, then here's how to do it, with the "Works on my machine" seal of approval.
First, download BindingListView, which will let you bind generic lists to your DataGridViews.
For this example, I just made a simple form with a DataGridView and randomly either called code to load a list of Users or Locations in Form1_Load().
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Equin.ApplicationFramework;
namespace DGVTest
{
public interface IListItem
{
string Id { get; }
string Name { get; }
}
public class User : IListItem
{
public string UserSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Location : IListItem
{
public string LocationSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void InitColumns(bool useUsers)
{
if (dataGridView1.ColumnCount > 0)
{
return;
}
DataGridViewCellStyle gridViewCellStyle = new DataGridViewCellStyle();
DataGridViewTextBoxColumn IDColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn NameColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn DerivedSpecificColumn = new DataGridViewTextBoxColumn();
IDColumn.DataPropertyName = "ID";
IDColumn.HeaderText = "ID";
IDColumn.Name = "IDColumn";
NameColumn.DataPropertyName = "Name";
NameColumn.HeaderText = "Name";
NameColumn.Name = "NameColumn";
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField";
DerivedSpecificColumn.HeaderText = "Derived Specific";
DerivedSpecificColumn.Name = "DerivedSpecificColumn";
dataGridView1.Columns.AddRange(
new DataGridViewColumn[]
{
IDColumn,
NameColumn,
DerivedSpecificColumn
});
gridViewCellStyle.SelectionBackColor = Color.LightGray;
gridViewCellStyle.SelectionForeColor = Color.Black;
dataGridView1.RowsDefaultCellStyle = gridViewCellStyle;
}
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = false;
Random rand = new Random();
bool useUsers = rand.Next(0, 2) == 0;
InitColumns(useUsers);
if(useUsers)
{
TestUsers();
}
else
{
TestLocations();
}
}
private void TestUsers()
{
List<IListItem> items =
new List<IListItem>
{
new User {Id = "1", Name = "User1", UserSpecificField = "Test User 1"},
new User {Id = "2", Name = "User2", UserSpecificField = "Test User 2"},
new User {Id = "3", Name = "User3", UserSpecificField = "Test User 3"},
new User {Id = "4", Name = "User4", UserSpecificField = "Test User 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
}
private void TestLocations()
{
List<IListItem> items =
new List<IListItem>
{
new Location {Id = "1", Name = "Location1", LocationSpecificField = "Test Location 1"},
new Location {Id = "2", Name = "Location2", LocationSpecificField = "Test Location 2"},
new Location {Id = "3", Name = "Location3", LocationSpecificField = "Test Location 3"},
new Location {Id = "4", Name = "Location4", LocationSpecificField = "Test Location 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
}
}
}
The important lines of code are these:
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField"; // obviously need to bind to the derived field
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
dataGridView1.AutoGenerateColumns = false; // Be specific about which columns to show
and the most important are these:
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
If all items in the list are known to be of the certain derived type, just call ConvertAll to cast them to that type.
You'll need to use a Grid template column for this. Inside the template field you'll need to check what the type of the object is and then get the correct property - I recommend creating a method in your code-behind which takes care of this. Thus:
<asp:TemplateField HeaderText="PolymorphicField">
<ItemTemplate>
<%#GetUserSpecificProperty(Container.DataItem)%>
</ItemTemplate>
</asp:TemplateField>
In your code-behind:
protected string GetUserSpecificProperty(IListItem obj) {
if (obj is User) {
return ((User) obj).UserSpecificField
} else if (obj is Location) {
return ((Location obj).LocationSpecificField;
} else {
return "";
}
}
I tried projections, and I tried using Convert.ChangeType to get a list of the underlying type, but the DataGrid wouldn't display the fields. I finally settled on creating static methods in each type to return the headers, instance methods to return the display fields (as a list of string) and put them together into a DataTable, and then bind to that. Reasonably clean, and it maintains the separation I wanted between the data types and the display.
Here's the code I use to create the table:
DataTable GetConflictTable()
{
Type type = _conflictEnumerator.Current[0].GetType();
List<string> headers = null;
foreach (var mi in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
if (mi.Name == "GetHeaders")
{
headers = mi.Invoke(null, null) as List<string>;
break;
}
}
var table = new DataTable();
if (headers != null)
{
foreach (var h in headers)
{
table.Columns.Add(h);
}
foreach (var c in _conflictEnumerator.Current)
{
table.Rows.Add(c.GetFieldsForDisplay());
}
}
return table;
}
When you use autogeneratecolumns it doesnt automatically do this for you?
My suggestion would be to dynamically create the columns in the grid for the extra properties and create either a function in IListItem that gives a list of available columns - or use object inspection to identify the columns available for the type.
The GUI would then be much more generic, and you would not have as much UI control over the extra columns - but they would be dynamic.
Non-checked/compiled 'psuedo code';
public interface IListItem
{
IList<string> ExtraProperties;
... your old code.
}
public class User : IListItem
{
.. your old code
public IList<string> ExtraProperties { return new List { "UserSpecificField" } }
}
and in form loading
foreach(string columnName in firstListItem.ExtraProperties)
{
dataGridView.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = columnName, HeaderText = columnName );
}
If you are willing to use a ListView based solution, the data-bindable version ObjectListView will let you do this. It reads the exposed properties of the DataSource and creates columns to show each property. You can combine it with BindingListView.
It also looks nicer than a grid :)