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.
Related
I have two files. One file with a class called Wood. I have created some objects with this class. These Objects include the attribute WoodType and a few others. I filled the objects into an array called woodObjects. The second file is a Form where I have a ListView.
So my goal was, to add all objects with their attributes to the ListView. I did it like that:
String[] row = { (listView1.Items.Count + 1).ToString(), WoodType, condition, isDry};
ListViewItem item = new ListViewItem(row);
listView1.Items.Add(item);
Success.
Now I have to update the attribute WoodType of one object in the ListView. I created a button "change Type" for that. I thought of the user clicking the row where the object ist he wants to change, and then clicking the button "change Type" to change WoodType it.
To realize this, I wanted to take the index of the row, and than changing the value of "WoodType" with its set Accessor with something like the following:
woodObjects[indexOfRowFromListView].WoodType = "Oak Wood";
Yes, I know, this would never work like that. So my questions are: How can I select the correct index and how do I change the value of the attribute?
*I also thought of deleting the objects, but saving its values so I can create a new objects with the same values except WoodType.
I appreciate every help!
I think the cleanest solution to your problem would involve writing a new ListViewItem implementation. Something like this:
public class WoodObjectListItem : System.Windows.Forms.ListViewItem
{
public Wood WoodObject { get; }
public WoodObjectListItem(int rowNumber, Wood woodObject)
: base(new string[] { rowNumber.ToString(), woodObject.WoodType, woodObject.Condition, woodObject.IsDry })
{
WoodObject = woodObject;
}
public void ChangeType()
{
WoodObject.WoodType = "Oak Wood";
}
}
Then you could add your woodObjects to the list view like:
listView1.Items.Add(new WoodObjectListItem(listView1.Items.Count + 1, woodObject));
And when the button is clicked you could do:
if (listView1.SelectedItem is WoodObjectListItem woodObjectListItem)
woodObjectListItem.ChangeType();
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 have a ListView bound to an instance of data
ObservalbeCollection<ActivityItem> ActivityItems
<ListView
x:Name="ActivityItemsList"
ItemsSource="{Binding ActivityItems}"
ItemTemplate="{StaticResource Herke80ItemTemplate}"
Header="{Binding DateFilterListBox.SelectedItem}" />
When I run a filter, I want to select the ListViewItem bound to the ActivityItem within the ListView, and change its visibility depending on the filter selected.
I was doing this by keeping another ObservableCollection instance, meaning the data instance was duplicated. I then removed or added items accordingly. This took up a lot of loading time. So I figured I'd try keep to one binding, and disable or enable the UI elements.
foreach (ActivityItem activityItem in ActivityItemsList.Items)
{
if (activityItem == null) continue;
var index = ActivityItemsList.Items.IndexOf(activityItem);
(ActivityItemsList.Items[index] as ListViewItem).Visibility = Visibility.Collapsed;
int startComparer = DateTime.Compare(activityItem.Start, selectedStartDate);
int endComparer = DateTime.Compare(selectedEndDate, activityItem.End);
if (OverdueToggleSwitch.IsOn)
{
(ActivityItemsList.Items[index] as ListViewItem).Visibility = Visibility.Visible;
}
else
{
if (startComparer >= 0 && endComparer >= 0)
{
(ActivityItemsList.Items[index] as ListViewItem).Visibility = Visibility.Visible;
}
}
}
ex is a NullReferenceException, due to the fact that the ListViewItem is not actually a ListViewItem but an ActivityItem.
What is an alternative or the correct way of doing this?
I think you have over complicated it. If you use Snoop you can see that when you bind a ListView to a collection via ItemSource it ends up populated with a collection of ListViewItems where each ListViewItem has two things.
a DataContext set to the data (e.g. ActivityItem)
a ContentPresenter filled with the controls defined in your DataTemplate
This means that in theory you can browse both collections
from x in listView.Items select x as ListViewItem
or
from x in listView.Items select x.DataContext as ActivityItem
However if you simply want a filtered list then can I suggest changing your binding,
<ListView
x:Name="ActivityItemsList"
ItemsSource="{Binding FilteredItems}"
and
public class MyViewModel :INotifyPropertyChanged
{
public IEnumerable<ActivityItem> AllItems {get;set;} //needs to NotifyPropertyChanged(FilteredItems)
public Func<ActivityItem, bool> Filter { get;set;} //needs to NotifyPropertyChanged(FilteredItems)
public IEnumerable<ActivityItem> FilteredItems { get { return from x in AllItems where Filter(x) select x; }}
}
If you are still struggling with performance issues then have a quick read through Bea Costa's series on TreeView performance part one, part two and part three. It might help you to get WPF doing all the hard work for you.
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
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 });