I am currently struggling with the GUI of my application. I have a hard time figuring out whether the ListBox or ListView is more "suitable" for multi-column representation of data.
I prefer "clean" code that is not too confusing to figure out, as spaghetti code and hacking methods can lead to confusion.
How do both ListBox and ListView handle multiple columns?
There's certainly nothing wrong with a DataGridView in this scenario.
Sample:
class Car
{
public string Make { get; set; }
public string Model { get; set; }
public int Year { get; set; }
}
A function to load the data to the DataGridView
private void LoadData()
{
List<Car> cars = new List<Car>()
{
new Car() { Make = "Subaru", Model = "Impreza", Year = 2005 },
new Car() { Make = "Ford", Model = "Mustang", Year = 1984 }
};
dataGridView1.DataSource = cars;
}
Of course, from here things can get more complicated, but if you simply want to display data in a tabular fashion... it's pretty simple.
Check this out
https://stackoverflow.com/a/227355/988830
Though listbox is used for single column and listview is used for mutlicolumn, the answer is it all depends.
Sometimes you may need multicolumn list where you need to add different types of children. You cannot bind them using listview so its better to use listbox in such scenarios. But if you want to sort them by using header, do use listview because it is simple.
In conclusion, I would say if you just have multi column data and nothing more better to use listview else if you want to do fancy stuff like buttons, treeview, expander etc. ListBox is really cool.
Thanks,
Omkar
A DataGridView is nice if you want to be able to edit data straight from the grid, like a spreadsheet. A listview in detail mode is great for simple presentation of lists of data columns. A DataGridView will also be easier to sort, as far as I know.
Generally I do something like this:
private void UpdateListView()
{
mListView.Items.Clear();
foreach (Item item in mItems)
{
ListViewItem listViewItem =
new ListViewItem(item.Value1.ToString()) { Tag = item; }
listViewItem.SubItems.Add(item.Value2.ToString());
listViewItem.SubItems.Add(item.Value3.ToString());
mListView.Items.Add(listViewItem);
}
}
The columns will need to have been defined in the designer, including column header text and column widths.
With the Tag = item; part you will be able to access the selected object with:
if (mListView.SelectedIndices.Count <= 0)
return;
Item selectedItem = mListView.SelectedItems[0].Tag as Item;
if (selectedItem == null)
return;
// do something with selectedItem
ListView is much better for multi-column representation of data. However it seems to get more complicated/ugly code than a simple ListBox.
Still its much better for many reasons, resizeable columns and all that.
I don't think ListBox has multiple columns so you'd have to hack something ugly in.
http://www.xtremedotnettalk.com/showthread.php?t=93443
Related
I would like learn something.
I have a GridControl which has a gridview inside with some columns. And there's one column lets name "jobs". in my DataList(which i'm populating rows with) i have another list which will hold some infos about jobs and i want to show them in one column. But there's the problem. I want to split the cell of "jobs" . how can i do that?
the model and the list is like below:
//this is my job class
public class JobInfo{
public string Name;
public bool IsDone;
}
//this is my work class
public class Work{
public string Company;
public string Personel;
public List<JobInfo> Jobs;
}
in my cs file I will hold a List<Work> object. I can bound it to grid view but i want to show job info like that cell contains 4 or 5 cells or how many jobs that list has.
I tried some paint to be as clear as much
http://imgur.com/eEB9krO
Any help appreciated. Thanks
You can not have columns inside columns in a GridView.
How about just adding it into one cell like this?
private void gridView_CustomColumnDisplayText(object sender, DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventArgs e)
{
if (e.Column.FieldName == "Jobs")
{
var jobs = e.Value as List<JobInfo>;
e.DisplayText = jobs != null ?
string.Join(" | ", jobs.Select(j => string.Format("Name: {0}, Is Done: {1}", j.Name, j.IsDone))) :
string.Empty;
}
}
There is no way to do exactly what you need. But maybe it is an option to use a repositoryItem. You could use a popupcontaineredit. Then you build a popupcontainercontrol with a new Grid. If someone click a row or on the dropdown of your popupcontaineredit you can popup your popupcontainercontrol and populate the list of jobs in the uppoping grid.
I hope you understand what i mean.
regards.
My apologies in advance, I might have difficulty in making myself clear. I have searched, but the discussions were far to complicated for me to follow, and I just started dwelling in C#.
Project: C# WindowsForm / .NET 4.5 / Visual Studio 2012
Challenge:
I want to add items from a listbox to another listbox (I can do it easily with Lists and foreach loops), and make the end listbox show specific item depending on selections made in listbox2.
Explanation:
The selected items are to incorporate a group that I create in yet another listbox, so that if I select a handful of items in listbox1, I send them to listbox3, but they only should appear when I select a specific item in listbox2.
Imagine selecting games from a list, adding it to the "Nintendo" group in a new list, so they don't get mixed with the Sega ones when Sega is selected in listbox2.
I can add values in all listboxes, copy the ones I want from 1 to 3, but I am at a loss on how to make the selection respect the selection on 2.
I've read about databinding and etc, but the examples gives were too complicated (and maybe a bit of language barrier was present), is there a resource that can provide the simplest solution to a really small project?
Care to enlighten a fool using layman terms, please?
Thanks
EDIT:
Nice of you (whoever you were) to downvote my question. Fair enough. You could at least tell me what the problem was, or where the question was answered so I could resolve my problem. Wouldn't that be nice? I am a starter in C#, so it's only natural that the first question may seem ridiculous/lazy...
DataBinding would be the way to go. The main component to focus on is BindingSource. You could also investigate DataSets for your underlying data because they would give you some flexibility with filtering. But, if this is a small application, and if you're just learning, the following example might be a good start:
Drag a BindingSource for each of your listboxes onto your form. Then, connect each of the ListBox's DataSource properties to a corresponding BindingSource.
Here is an example code behind that shows how to bind your underlying data to each of the BindingSources which are in turn already bound to the listboxes:
namespace WindowsFormsApplication1
{
public class Game
{
public string Name { get; set; }
public string Group { get; set; }
}
public class Group
{
public string Description { get; set; }
}
public partial class Form1 : Form
{
List<Game> _allGames;
public Form1()
{
InitializeComponent();
_allGames = new List<Game>
{
new Game { Name = "Alpha", Group = "" },
new Game { Name = "Bravo", Group = "One" },
new Game { Name = "Charlie" , Group = "One"},
new Game { Name = "Delta", Group = "Two" }
};
bindingSource1.DataSource = _allGames;
listBox1.DisplayMember = "Name";
listBox1.ValueMember = "Name";
var groups = new List<Group>
{
new Group { Description = "One" },
new Group { Description = "Two" },
new Group { Description = "Three" }
};
bindingSource2.DataSource = groups;
listBox2.DisplayMember = "Description";
listBox2.ValueMember = "Description";
}
private void listBox2_SelectedIndexChanged(object sender, EventArgs e)
{
var group = listBox2.SelectedValue.ToString();
bindingSource3.DataSource = _allGames.Where(x => x.Group == group);
listBox3.DisplayMember = "Name";
}
}
}
All this code is doing is binding data to the BindingSource and telling each ListBox which property of the underlying data to display. This example ignores the mechanism that assign each item from listBox1 to the group in listBox2 because I assume you know how to do that or can figure that out.
The event handler for when listBox2 changes just gets which selection was made and creates a list of items from listBox1 that match that item, and then displays those items in listBox3.
For the sake of simplicity, lets assume the items bound to your listboxes are strings. Then you can use a dictionary to hold your group assignment (you have to hold it somewhere):
Dictionary<string, List<string>> listOfGroups = new Dictionary<string, List<string>>();
Where key is a name of group, and list of strings holds items in group.
Then, somewhere, you create a group to which you will be assigning values
void CreateGroup(string groupName)
{
listBox2.Items.Add(groupName);
if (!listOfGroups.ContainsKey(groupName)
listOfGroups.Add(groupName, new List<string>());
}
Then, you manage items in groups and add/remove them when, for example, selection on listBox2 changes:
string item = (string)listBox1.SelectedItem;
if(!listOfGroups.ContainsKey((string)listBox2.SelectedItem))
listOfGroups.Add((string)listBox2.SelectedItem,new List<string>());
((List<string>)listOfGroups[(string)listBox2.SelectedItem]).Add(item);
listBox3.Items.Add(item);
listBox1.Items.Remove(item);
And finaly:
private void listBox2_SelectedIndexChanged(object sender, EventArgs e)
{
listBox3.Items.Clear();
if (!listOfGroups.ContainsKey((string)listBox2.SelectedItem))
listOfGroups.Add((string)listBox2.SelectedItem, new List<string>());
listBox3.Items.AddRange(listOfGroups[(string)listBox2.SelectedItem].ToArray());
List<string> list = listOfAllItems.Where(a => !listBox3.Items.Contains(a)).ToList();
listBox1.Items.Clear();
listBox1.Items.AddRange(list.ToArray());
}
That's only an idea, though. If you're using DataSet, maybe you can just use linq (instead of a dictionary) to select items you want to display after selection in listbox2 changes.
You can save some kind of Dictionary, key is element in first ListBox first, and value is the item of the second ListBox.
I'm having trouble figuring out how to add items to a ListBox in WinForms.
I have tried:
list.DisplayMember = "clan";
list.ValueMember = sifOsoba;
How can I add ValueMember to the list with an int value and some text for the DisplayMember?
list.Items.add(?)
Btw. I can't use ListBoxItem for any reasons.
ListBoxItem is a WPF class, NOT a WinForms class.
For WPF, use ListBoxItem.
For WinForms, the item is a Object type, so use one of these:
1. Provide your own ToString() method for the Object type.
2. Use databinding with DisplayMemeber and ValueMember (see Kelsey's answer)
list.Items.add(new ListBoxItem("name", "value"));
The internal (default) data structure of the ListBox is the ListBoxItem.
In WinForms, ValueMember and DisplayMember are used when data-binding the list. If you're not data-binding, then you can add any arbitrary object as a ListItem.
The catch to that is that, in order to display the item, ToString() will be called on it. Thus, it is highly recommended that you only add objects to the ListBox where calling ToString() will result in meaningful output.
You might want to checkout this SO question:
C# - WinForms - What is the proper way to load up a ListBox?
DisplayMember and ValueMember are mostly only useful if you're databinding to objects that have those properties defined. You would then need to add an instance of that object.
e.g.:
public class MyObject
{
public string clan { get; set; }
public int sifOsoba { get; set; }
public MyObject(string aClan, int aSif0soba)
{
this.clan = aClan;
this.sif0soba = aSif0soba;
}
public override string ToString() { return this.clan; }
}
....
list.Items.Add(new MyObject("hello", 5));
If you're binding it manually then you can use the example provided by goggles
The way I do this - using the format Event
MyClass c = new MyClass();
listBox1.Items.Add(c);
private void listBox1_Format(object sender, ListControlConvertEventArgs e)
{
if(e.ListItem is MyClass)
{
e.Value = ((MyClass)e.ListItem).ToString();
}
else
{
e.Value = "Unknown item added";
}
}
e.Value being the Display Text
Then you can attempt to cast the SelectedItem to MyClass to get access to anything you had in there.
Also note, you can use anything (that inherits from object anyway(which is pretty much everything)) in the Items Collection.
If you just want to add a string to it, the simple answer is:
ListBox.Items.Add("some text");
You have to create an item of type ListBoxItem and add that to the Items collection:
list.Items.add( new ListBoxItem("clan", "sifOsoba"));
If you are adding integers, as you say in your question, this will add 50 (from 1 to 50):
for (int x = 1; x <= 50; x++)
{
list.Items.Add(x);
}
You do not need to set DisplayMember and ValueMember unless you are adding objects that have specific properties that you want to display to the user. In your example:
listbox1.Items.Add(new { clan = "Foo", sifOsoba = 1234 });
Ok, this has been a head scratcher for me. I have a ListBox I am binding to a linq query like so:
private IQueryable<Feed> _feeds;
public IQueryable<Feed> Feeds
{
get
{
if (_feeds == null)
{
var feedsQuery = from f in _db.Feed orderby f.Title select f;
_feeds = feedsQuery;
}
return _feeds;
}
}
public Options()
{
InitializeComponent();
this.DataContext = Feeds;
}
(For the record I've also tried List, instead of IQueryable)
Everything shows up great and I have a databound form that allows you to edit a record and all of those changes work just fine, the modified data shows up in the list.
The problem comes with I add an item. Nothing shows up in the list. The data goes into the database fine, but the only way to see the data is closing and restarting my app. I'm using the code below as an example:
Feed feed = new Feed()
{
ID = Guid.NewGuid(),
Url = "http://www.test.com",
Title = "Test"
};
_db.Feed.InsertOnSubmit(feed);
_db.SubmitChanges();
_db.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
(with or without the _db.Refresh nothing happens)
What's going on?
You are doing everything right, you jus need to use ObservableCollection. This will notify the ListBox about any changes in the list and refresh it automatically.
From MSDN
In many cases the data that you work
with is a collection of objects. For
example, a common scenario in data
binding is to use an ItemsControl
such as a ListBox, ListView, or
TreeView to display a collection of
records.
P.S. you don't need a db refresh
Unless notified otherwise, the ListBox only iterates once over its ItemsSource. Your query is only being run once.
The query object doesn't know when the database changes (and Refresh doesn't help; see below)--it's up to you to know (or anticipate) that and to rerun relevant queries at the appropriate times.
Stan R mentions ObservableCollection. That's fine, but simply storing the result of your query in an ObservableCollection won't solve the problem unless you do some work to update the collection yourself when the database changes. This means rerunning the query and manually adding new items and removing deleted items from the collection. (You could alternatively just rerun the query and set the entire result back in to the ListBox, but that means a whole new set of items will be created--not very performant, and maybe not what you want for other reasons.)
As an aside, your call to DataContext.Refresh is probably not doing what you think it is. From the docs:
This method is useful after an optimistic concurrency error to bring items into a state for another attempt. It updates the state of the primitive fields and properties on the objects.
Okay. I'm not positive this is 100% the correct way to use the ObservableCollection, but this seems to work:
private ObservableCollection<Feed> _feeds;
public ObservableCollection<Feed> Feeds
{
get
{
if (_feeds == null)
{
var feedsQuery = from f in _db.Feed orderby f.Title select f;
_feeds = new ObservableCollection<Feed>();
foreach (var item in feedsQuery)
{
_feeds.Add(item);
}
}
return _feeds;
}
}
And add my item:
Feed feed = new Feed()
{
ID = Guid.NewGuid(),
Url = "http://www.test.com",
Title = "Test"
};
_db.Feed.InsertOnSubmit(feed);
_db.SubmitChanges();
// manually update the list
Feeds.Add(feed);
It took me a little while to figure out I had to update the list manually (thanks Ben), but it all seems to work. Sorting would be nice, but I'll worry about that another time.
So lets say I have these classes:
public class Person
{
public string Name { get; set; }
}
public class PersonCollection : ObservableCollection<Person> { }
And lets say I have a ListView whose ItemsSource is bound to a PersonCollection. Now lets say I have this code:
public void AddPeople()
{
Person p = new Person() { Name = "Someone" };
MyPersonCollection.Add(p);
MyPersonCollection.Add(p);
MyPersonCollection.Add(p);
}
So now I have a ListView with three items in which all three items are references to the SAME object. So now I select lets say items with index 0 and 2 in the ListView.
The ListView.SelectedItems property will say I have ONE item selected since both visually selected items are the SAME object.
So how can I get the visually selected items so I can remove the items at indices 0 and 2, without removing the item at index 1?
In WinForms there is the ListBox.SelectedIndices property that would be useful here, but we don't have that in WPF, unfortunately...
You could iterate through the ListViewItems using ItemContainerGenerator.ContainerFromIndex, check ListViewItem.IsSelected and then remove them by index. However, this doesn't play well with virtualization because ContainerFromIndex could return null if you scroll away from the item and it gets virtualized.
The code would look something like this:
for(int ixPerson = myListView.Items.Count - 1; ixPerson >= 0; ixPerson--)
{
ListViewItem personItem = myListView.ItemContainerGenerator.ContainerFromIndex(ixPerson);
if (personItem.IsSelected)
{
mySourcePersonCollection.RemoveAt(ixPerson);
}
}
There are cases where this makes sense, adding people to a queue where appearing more than once is desirable for instance. For this case it seems like WPF is designed poorly. Is it possible to manually iterate between all items in the collection and check their selection state?
I think there's something wrong with your model! Whatever it is you are trying to achieve, I would try and find a more robust way of doing it.