A control has an ItemsSource Property of type IEnumerable. If I try to add items to Items collection when ItemsSource is set I get an error "Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead."
A method for Removing Items is present here:
WPF - Best way to remove an item from the ItemsSource
However I cannot find a method to (based on the same interface) to add a new Item. The AddNew method does not take any arguments. From the sample at : http://msdn.microsoft.com/en-us/library/system.componentmodel.ieditablecollectionview.canaddnew.aspx I felt this to be the correct code:
IEditableCollectionView items = paneToDropInto.Items;
if (items.CanAddNew)
{
object newitem = items.AddNew();
newitem = contentToTransfer;
items.CommitNew();
}
However it does not work. It does add the new item. But it is a blank Item. Note contentToTransfer.
Figured it out. As pointed out by Tom and Djerry +1 both (thanks). I am just re referencing the new item which will not cause the original new item generated by AddNew to be saved (very stupid of me).
However there is another interface I can use (and did use):
IEditableCollectionViewAddNewItem items = paneToDropInto.Items;
if (items.CanAddNewItem)
{
object newitem = items.AddNewItem(contentToTransfer);
}
Related
I am trying to add search functionality to a ListBox that will scroll down to the item being searched for.
I have a ListBox that is bound to an ObservableCollection that I have filled with a bunch of RecipeNameDTO objects. I can easily find an object in the Items collection with a simple search.
string searchItem = tbSearchString.Text;
var recipenameitem = lbRecipeNames.Items.Cast<DTO.RecipeNameDTO>().Where(u => u.RecipeName.ToLower().Contains(searchItem.ToLower())).FirstOrDefault();
I can reproducibly find items with this method.
However, if I then try to find the object's container using ContainerFromItem, the method returns a null unless the object is visible in the ListBox when I execute the method:
ListBoxItem lbi = (ListBoxItem)lbRecipeNames.ItemContainerGenerator.ContainerFromItem(recipenameitem);
I am certain (I think) that the actual object exists in the ItemsCollection before I execute ContainerFromItem because I use a non null result from the search I documented in the beginning of this post. Also, I can scroll down the ListBox and find the object I'm searching for.
It must be something with the way the ListBox caches the objects in the ItemsCollection that is preventing ContainerFromItem from returning the container. Is there a fix to my code (or understanding of the problem)?
Michael
I took Andy's suggestion and made my ListBox IsSynchronizedWithCurrent = True and then used the following code to set the current item and scroll it into view:
string searchItem = tbSearchString.Text;
CollectionViewSource cvs = (CollectionViewSource)this.FindResource("cvsRecipeName");
ObservableCollection<DTO.RecipeNameDTO> itemsCollection = (ObservableCollection<DTO.RecipeNameDTO>)cvs.Source;
List<DTO.RecipeNameDTO> recipenameitems = itemsCollection.Where(u => u.RecipeName.ToLower().Contains(searchItem.ToLower())).ToList();
if (recipenameitems.Count > 0) { cvs.View.MoveCurrentTo(recipenameitems[0]);}
lbRecipeNames.ScrollIntoView(lbRecipeNames.SelectedItem);
I'm sure I could modify this to make it more flexible, but here's the first fix.
I'm not sure what I'm missing here, but I'm having trouble getting a checkbox to bind to a list properly. The rest of the properties of the list bind just fine, but the checkbox is having issues. Here's what I have:
In the class that serves as the template for each object in the list I have:
Property Process As New CheckBox
In the MainWindow_Loaded Event I have:
Dim ProcessCol As new DataGridCheckBoxColumn
ProcessCol.Header = "P?"
ProcessCol.IsReadOnly = False
...
InputGrid.ItemsSource = InputData 'Which is a list of my Order Allocation objects which contains the checkbox property
...
Dim ProcessBinding As New Binding("Process")
ProcessBinding.Mode = BindingMode.TwoWay
ProcessCol.Binding = ProcessBinding
...
InputGrid.Columns.Add(ProcessCol)
When I try to populate this collection and look at the items I get checkbox = nothing. I'm not sure what I'm missing here... I know I couldn't be too far off...
Edit: I changed the property to "new CheckBox" and now I get an initialzied checkbox object in the list item as "System.Windows.Controls.CheckBox Content: IsChecked:False which in this case should have been true. So maybe a step closer, but still not there.
I found an answer here: WPF: CheckBox in DataGrid
Technically not an answer to my original question, but it works. Rather than a checkbox property in my class I now have a boolean property. The column is still created as a checkbox column. It works.
If a column can be bound to a chebox property of a list I'd still be interested in hearing that, but for me this solution works.
So I have this combobox, which ItemsSource is set to a List of objects.
What I want to do after that is change the Display value of one specific Item (the first on the list), because this label does not suit the context in some situations but does in others.
So far I found that you could add an item using the Add method, insert one using the Insert method, and remove one using RemoveAt.
But how do you update one ? I know I could use RemoveAt and Insert afterwards, but it would be a bit labor intensive, plus I would have to recreate the object with all its values...
Any ideas?
Either replace an item in your "List of objects":
myList[0] = new MyObject("Another display text").
For this (and Add, Remove etc) to work, your list would have to be an ObservableCollection<T> or any other that implements INotifyCollectionChanged.
Or modify the item itself:
myList[0].DisplayText = "Another display text";
For this to work, your MyObject class would have to implement INotifyPropertyChanged, you'd have to raise the PropertyChanged event when the DisplayText property is set, and you'd have to set the DisplayMemberPath or the ItemTemplate of your ComboBox correctly.
Take a look at the MVVM pattern for more information.
May be it's a silly (or more than trivial) kinda question, but it seems i just don't know the answer. Here's the case -
I assigned a UserList as the ItemsSource of a combobox. So what i did essentially is assigning a reference type to another.
I cleared the UserList. So now i get the Count of the ItemsSource 0 as well.
I still get the items present in my combobox. And i also can cast the SelectedItem of the combobox to a User object.
Here's the complete code -
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public partial class MainWindow : Window
{
private List<User> _userList;
public MainWindow()
{
InitializeComponent();
_userList = new List<User>()
{
new User() {Id = 1, Name = "X"},
new User() {Id = 2, Name = "Y"},
new User() {Id = 3, Name = "Z"}
};
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.comboBox1.ItemsSource = _userList;
this.comboBox1.DisplayMemberPath = "Name";
}
private void button1_Click(object sender, RoutedEventArgs e)
{
_userList.Clear();
/* ItemsSource is cleared as well*/
IEnumerable userList = this.comboBox1.ItemsSource;
/*I can still get my User*/
User user = this.comboBox1.SelectedItem as User;
}
}
So, where the items are coming from? What actually happens under-the-hood when i make such binding? Does the control have some kind of cache? It's a royal pain to realize not having such basic ideas. Can anybody explain the behind-the-scene detail?
EDIT : I wrote the code in WPF, but i have the same question for WinForms Combobox.
EDIT : Doesn't a combobox display its items from it's in-memory Datasource? When that datasource contains 0 items, how does it display the items?
When you set an ItemsSource of any ItemsControl it copies the ref to the list into its Items property. Then it subscribes to the OnCollectionChanged event, and creates a CollectionView object. So, on the screen you can see that collectionView.
as I have found in source code ItemCollection holds two lists:
internal void SetItemsSource(IEnumerable value)
{
//checks are missed
this._itemsSource = value;
this.SetCollectionView(CollectionViewSource.GetDefaultCollectionView((object) this._itemsSource, this.ModelParent));
}
How could you get SelectedItem?
This is my assumption from quick look into the source code:
ItemsControl has a collection of "views" and each View sholud store a ref to the item (User instance), because it has to draw data on the screen. So, when you call SelectedItem it returns a saved ref.
Upd about references
Assume there is an User instance. It has the adress 123 in memory. There is a list. It stores references. One of them is 123.
When you set an ItemsSource ItemsControl saves a reference to the list, and creates a Views collection. Each view stores a references to an item. One view stores an address 123.
Then you cleared a list of users. Now list doesn't contains any references to Users. But in memory there is an adrress 123 and there is an instance of User by this adress. Garbage Collector doesn't destroy it, because View has a reference to it.
When you get SelectedItem it returns User instance from the 123 adress.
var user = new User();
var list = new List<User>();
list.Add(user);
list.Clear();
Console.WriteLine(list.Count()); //prints 0 - list is empty
Console.WriteLine(user == null); //prints false. - user instance is sill exists;
In answer to your comment to #GazTheDestroyer ("... why it doesn't get cleared, and how it holds the items?")
In WPF, when you set the ItemsSource property of an ItemsControl, the control will wrap the list of items in a CollectionView, which is a collection type optimised for use by the UI framework. This CollectionView is assigned to the Items property of the control and is what the display-drawing code actually works from. As you see, this collection is entirely separate of the object you originally assigned to ItemsSource, and so there is no propogation of changes from one to the other. This is why the items are still in the control when you clear the original list: the control is ignoring the original list, and has its own list that contains your objects.
It's for this reason that an ItemsSource value needs to raise events - specifically INotifyCollectionChanged.NotifyCollectionChanged - so that the control knows to refresh the Items list. ObservableCollection implements this interface and raises the correct event, and so the functionality works as expected.
It's hugely important to note that this is nothing like what happens in WinForms, which is why I've been pressing you for the clarification.
EDIT: To clarify, there is no "deep copy." The code that is happening is similar in principle to the following:
private List<object> myCopy;
public void SetItemsSource(List<object> yourCopy)
{
myCopy = new List<object>();
foreach (var o in yourCopy)
{
myCopy.Add(o);
}
}
Once this code has run, there's only one copy of every item in your list. But each of the items is in both of the lists. If you change, clear or otherwise manipulate yourCopy, myCopy knows nothing about it. You cannot "destroy" any of the objects that are within the list my clearing yourCopy - all you do is release your own reference to them.
Assuming you are using WPF:
List<User> doesn't fire any event that the UI will recognise to refresh itself. If you use ObservableCollection<User> instead, your code will work.
The key difference is that ObservableCollection implements INotifyCollectionChanged, which allows the UI to recognise that the content of the collection has changed, and thus refresh the content of the ComboBox.
(Note that this does not work in WinForms. In WinForms you can set the DataSource property of the control, but the same ObservableCollection trick does not work here.)
When you set a collection reference to ItemsControl, all the combo gets is a reference, that it knows is enumerable.
It will enumerate the reference and display the items. Whether it does a deep copy or shallow copy is irrelevant, all it has is a reference (memory address effectively).
If you change your collection in some way, the combo has no way of knowing unless you tell it somehow. The reference (address) hasn't changed, everything looks the same to the combo. You seem to be thinking that the object is somehow "live" and the combo can watch the memory changing or something? This isn't the case. All it has is a reference that it can enumerate over. The contents can change but without some trigger the combo doesn't know that, and so will sit doing nothing.
ObservableCollection is designed to overcome this. It implements INotifyCollectionChanged that fires events when it changes, so the Combo knows that it must update its display.
I have a combobox and set the itemssource to a list of strings:
private List<string> list = new List<string>();
...
ComboBox cbo = new ComboBox();
cbo.ItemsSource = list;
The combobox gets filled successful on startup.
But when the list is changed, the combobox doesn't update its items, BUT only when I expanded the items before, else the combobox gets updated...
Also weird: when I trace the count of items of the combobox, the count is the correct updated number, but the displayed items aren't.
Somebody has an idea, what's going on here?
EDIT: See solution in comments to post of Robert
I guess you need to use ObservableCollection instead of List, because ComboBox needs to be notified whenever the collecion changes.
Change List into ObservableCollection.
Use ObservalbeCollection. It has inbuilt mechanism to notify and change in the number of items.