Updating the listbox after adding a new item - c#

I am using WPF and C#
I have a button that opens a window, which also includes a button that adds an User object item to a listbox and I want the listbox index to be updated after insertion. I understood that the solution is about using observable class of INotifyCollectionChanged but actually I dont know how and where to use them. Can you help me on determining what and where to implement, register, fire etc.
Edit:
I succeeded this by Quartermeister's help where my User object is collected in a list, but now I want to do the same thing where my object is collected in a dictionary

The easiest way is to use System.Collections.ObjectModel.ObservableCollection<T> as your list. This implements INotifyCollectionChanged and INotifyPropertyChanged for you.
You would have a property on your DataContext object of this type, and use data binding on ListBox.ItemsSource to bind it to that property. The ListBox will automatically update its list of elements when the collection changes.
In the class that is your DataContext:
public class MyClass
{
public ObservableCollection<string> Items { get; set; }
}
In Xaml:
<ListBox ItemsSource="{Binding Items}">
</ListBox>
It sounds like you also want an observable dictionary, but unfortunately there is not one built into the framework. You could try using Dr. Wpf's implementation of ObservableDictionary from his post "Can I bind my ItemsControl to a dictionary?".

Implementing an observable dictionary is hard. It's much simpler to maintain an parallel observable collection, one that contains the dictionary's values. Bind the view to that collection, and make sure that any code which adds or removes values to/from the dictionary updates the both the dictionary and the parallel collection.
If you really wanted to go crazy, you could implement a subclass of ObservableCollection to hold your objects, and make that class maintain the dictionary, e.g.:
public class KeyedObject
{
public string Key { get; set; }
public object Value { get; set; }
}
public class ObservableMappedCollection : ObservableCollection<KeyedObject>
{
private Dictionary<string, KeyedObject> _Map;
public ObservableMappedCollection(Dictionary<string, KeyedObject> map)
{
_Map = map;
}
protected override void InsertItem(int index, KeyedObject item)
{
base.InsertItem(index, item);
_Map[item.Key] = item;
}
protected override void RemoveItem(int index)
{
KeyedObject item = base[index];
base.RemoveItem(index);
_Map.Remove(item.Key);
}
protected override void ClearItems()
{
base.ClearItems();
_Map.Clear();
}
protected override void SetItem(int index, KeyedObject item)
{
KeyedObject oldItem = base[index];
_Map.Remove(oldItem.Key);
base.SetItem(index, item);
_Map[item.Key] = item;
}
}
There are a bunch of potential problems in the above, mostly having to do with duplicate key values. For instance, what should SetItem do if you're adding an object whose key is already in the map? The answer really depends on your application. Issues like that also hint at why there isn't an observable dictionary class in the framework.

Related

Data Binding with a Sorted List

My UI has a ListBox which is bound to a Collection. Right now this happens to be an ObservableCollection
My objective is to add objects to this Collection via the UI, and have the ListBox dynamically update, all while maintaining a sorted Collection.
I am aware that there is some SortedView that I can use in WPF. But that is not what I want - I need the actual Collection to remain sorted because my business logic requires a sorted collection.
One way that I thought of, is to create my own Collection class which uses a SortedList internally, and implements the INotifyCollectionChanged interface and produces NotifyCollectionChangedEventArgs event when the internal list changes. Sounds like a lot of work!
Is there a simple solution that I've missed?
Depending on your exact needs, the simplest approach is to keep your ObservableCollection, but wrap in in a new property of type ICollectionView:
public class MyViewModel {
private CollectionViewSource _collectionViewSource;
public ICollectionView MyCollectionView => _collectionViewSource.View;
public MyViewModel(ObservableCollection<MyDataItem> dataItems) {
_collectionViewSource = new CollectionViewSource() { Items = dataItems };
//Add sorting here using _collectionViewSource.SortDescriptions.Add(...)
}
You can use the wrapper property to extract a sorted list as needed.
Okay so I ended up inheriting from ObservableCollection, and overriding the Add() method.
This did the trick for me. Now my list is always sorted, and the ObservableCollection is the one that Notifies the UI of changes.
public class MyCollection : ObservableCollection<Int32>
{
public new void Add(Int32 x)
{
base.Add(x);
var oldList = new ObservableCollection<Int32>(this.OrderBy(c=>c));
Clear();
foreach(var i in oldList)
{
base.Add(i);
}
}
}
I'm a beginner with C#, any feedback on the code is appreciated.

Sorting a listbox relative to another

I know that you can set up a ListBox to sort automatically. Is there a way to "catch" the sorting so that when the ListBox swaps the position of two items so that I can do the same reordering on another list box? I want sort one list box by by value but keep those values at the same relative index locations compared to another ListBox somewhere else.
I could write a routine to bubble sort the list so that I could make the changes myself, but it I am wondering if there is a more automated since I will likely have to do this at a few different places in the program.
Unfortunately, the Sorted property does not use the IComparable interface implementation just sorts based on the result of ToString of the items. But instead of setting the Sorted property, you can use a sorted data source (a List<>, for example).
Create a wrapper class for the items in the ListBox and implement the IComparable<T> interface on it. Populate a List<> with these ListBoxItem instances, then call the Sort method on the list. Thus you will able to dispatch the CompareTo calls.
public partial class Form1 : Form
{
private class ListBoxItem<T> : IComparable<ListBoxItem<T>>
where T : IComparable<T>
{
private T item;
internal ListBoxItem(T item)
{
this.item = item;
}
// this makes possible to cast a string to a ListBoxItem<string>, for example
public static implicit operator ListBoxItem<T>(T item)
{
return new ListBoxItem<T>(item);
}
public override string ToString()
{
return item.ToString();
}
public int CompareTo(ListBoxItem<T> other)
{
return item.CompareTo(other.item); // here you can catch the comparison
}
}
public Form1()
{
InitializeComponent();
var items = new List<ListBoxItem<string>> { "Banana", "Apple"};
items.Sort();
listBox1.DataSource = items;
}

c# PropertyGrid restricting edits on List<T> items

I have a app that makes use of the PropertyGrid in C#/.NET
the PropertGrid holds onto the MyAppObject class/object shown below..
class MyAppObject
{
private List<MyObject> oItems;
public List<MyObject> Items
{
get { return this.oItems; }
}
}
And so far it works well, nice and simple. I want the property grid to allow users to view the items, which it does well, however when you select the property in the PropertyGrid the dialog also allows to add more List<MyObject> items.
I do not want this, I only want to have the ability to show the items, not edit them.
I thought by not providing the setter (set { this.oItems = value; }):
then it wouldnt allow the add button.
Hope this makes sense, The screenshots shows the dialog, and I circled the buttons I want to remove.
thanks
If you expose it as a read-only list, it should do what you need:
[Browsable(false)]
public List<MyObject> Items
{
get { return this.oItems; }
}
// this (below) is the one the PropertyGrid will use
[DisplayName("Items")]
public ReadOnlyCollection<MyObject> ReadOnlyItems
{
get { return this.oItems.AsReadOnly(); }
}
Note that the members of individual objects (MyObject instances) will still be editable, unless you decorate them as [ReadOnly(true)].
As you note, the setter is not necessary to add/remove/edit items. That is because the grid still has full access to the .Add, .Remove and indexer (list[index]) operations.
This is a slightly tricky one; the solution involves building with the full .NET Framework (since the client-only framework doesn't include System.Design). You need to create your own subclass of CollectionEditor and tell it what to do with the temporary collection after the UI is finished with it:
public class MyObjectEditor : CollectionEditor {
public MyObjectEditor(Type type) : base(type) { }
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
return ((MyObject)context.Instance).Items;
}
}
Then you have to decorate your property with the EditorAttribute:
[Editor(typeof(MyObjectEditor), typeof(UITypeEditor))]
public List<MyObject> Items{
// ...
}
Reference: What's the correct way to edit a collection in a property grid
Alternative:
return new ReadOnlyCollection(oItems);
OR
return oItems.AsReadOnly();

WPF: Implementing and binding (datagrid) to a custom collection

I have a custom collection that I am passing to a WPF client, which is binding the collection to a datagrid using AutoGenerateColumns="True". The datagrid, however, is displaying empty rows (albeit the right number of empty rows). What am I doing wrong? Following is some sample code. For now I've omitted everything having to do with INotifyPropertyChanged and INotifyCollectionChanged because, well, I first want to have some data showing up in the grid.
I should also mention that I've tried implementing the above two interfaces, but they seem to have nothing to do with this issue.
(You might not actually want to look at the sample code as there's absolutely nothing interesting about it. The collection implementation is just wrapping an inner List.)
Some random POCO:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Simple collection implementation:
public class MyCollection<T> : IList<T>
{
private List<T> list = new List<T>();
public MyCollection()
{
}
public MyCollection(IEnumerable<T> collection)
{
list.AddRange(collection);
}
#region ICollection<T> Members
public void Add(T item)
{
list.Add(item);
}
public void Clear()
{
list.Clear();
}
public bool Contains(T item)
{
return list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
list.CopyTo(array, arrayIndex);
}
public int Count
{
get { return list.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
return list.Remove(item);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return list.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region IList<T> Members
public int IndexOf(T item)
{
return list.IndexOf(item);
}
public void Insert(int index, T item)
{
list.Insert(index, item);
}
public void RemoveAt(int index)
{
list.RemoveAt(index);
}
public T this[int index]
{
get { return list[index]; }
set { list[index] = value; }
}
#endregion
}
The XAML:
<Window x:Class="TestWpfCustomCollection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid AutoGenerateColumns="True"
HorizontalAlignment="Stretch"
Name="dataGrid1" VerticalAlignment="Stretch"
ItemsSource="{Binding}"
/>
</Grid>
</Window>
The window's code-behind:
public MainWindow()
{
InitializeComponent();
MyCollection<Person> persons = new MyCollection<Person>()
{
new Person(){FirstName="john", LastName="smith"},
new Person(){FirstName="foo", LastName="bar"}
};
dataGrid1.DataContext = persons;
}
By the way, if you change the code-behind to use a List<Person> instead of the MyCollection<Person>, everything works as expected.
EDIT:
The above code is not taken from the real situation. I have only posted it to show what I am doing in order to test my problem and to make it easier to replicate it. The actual custom collection object is quite complex and I cannot post it here. Again, I'm just trying to understand the basic concept behind what needs to be done in order for a datagrid to properly bind to a custom collection and automatically generate columns for the underlying objects.
Apparently, in order for AutoGenerateColumns to work in a WPF DataGrid, your collection has to implement IItemProperties, although I've found that wrapping my collection in a (windows forms) BindingList does the trick as well (it actually wraps your collection, unlike the ObservableCollection which just copies your collections' members into itself).
What is your MyCollection<T> type adding over List<T>? Either way, I would use an ObservableCollection instead (so that the UI is notified of additions/removals), and implement INotifyPropertyChanged on your Person class so that the UI is notified of changes to that type's property values.
EDIT
I don't think it's particularly trivial binding to your own custom collection type. You'll need to implement INotifyPropertyChanged and INotifyCollectionChanged on that type. This article might be of some use - http://www.e-pedro.com/2009/04/creating-a-custom-observable-collection-in-wpf/
Another option if you are using MVVM, is to use your custom collection type on your model, and use a standard ObservableCollection on your view model and populate your view models collection from your model, then bind your grid to your view models collection.
I think you using your knowlege of Windows Forms by writing
dataGrid1.DataContext = persons;
In WPF DataGrid is connected to the collection (say List) very simple:
dataGrid1.ItemsSource = persons;

Item Collection option for a User Control

As you can see in the pic below, for a ListView Control you can add Items using the Properties pane.
How do I enable this kind of stuff for my UserControl?
I'm not getting anything when I search Google, but I'm probably not using the correct terms.
Does anybody know?
Thanks
You need to create a class that defines the object type that the collection ids composed of. A listView has ListViewItem objects. A TabControl has TabPage objects. Your control has objects which are defined by you. Let's call it MyItemType.
You also need a wraper class for the collection. A simple implementation is shown below.
public class MyItemTypeCollection : CollectionBase
{
public MyItemType this[int Index]
{
get
{
return (MyItemType)List[Index];
}
}
public bool Contains(MyItemType itemType)
{
return List.Contains(itemType);
}
public int Add(MyItemType itemType)
{
return List.Add(itemType);
}
public void Remove(MyItemType itemType)
{
List.Remove(itemType);
}
public void Insert(int index, MyItemType itemType)
{
List.Insert(index, itemType);
}
public int IndexOf(MyItemType itemType)
{
return List.IndexOf(itemType);
}
}
Finally you need to add a member variable for the collection to your user control and decorate it properly:
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public MyItemTypeCollection MyItemTypes
{
get { return _myItemTypeCollection; }
}
and you now have a simple interface that allows you to browse and edit the collection. Leaves a lot to be desired still but to do more you will have to learn about custom designers which can be difficult to understand and implement.

Categories

Resources