I have two ComboBoxes envelopeList and datasetList. Consider the following line of code:
envelopeList.DataBindings.Add("DataSource", datasetList, "SelectedValue");
The intended functionality is to update envelopeList.DataSource to be datasetList.SelectedValue whenever the selection is changed. However, if datasetList is empty this throws an ArgumentException saying "Additional information: Complex DataBinding accepts as a data source either an IList or an IListSource."
I don't understand why this happens. When datasetList is empty datasetList.SelectedValue returns null and envelopeList.DataSource = null does not throw any exception. This doesn't throw any exceptions either: envelopeList.DataSource = datasetList.SelectedValue;, nor does this: envelopeList.DataSource = new BindingSource(datasetList, "SelectedValue");, even when datasetList is empty.
Doing the binding after datasetList has at least one item works as intended, until it becomes empty in which case envelopeList.DataSource isn't updated. The DataSourceChanged event isn't even fired. (Though in my case that noticed by the user since the DataSource will be emptied when the item in datasetList is deleted).
To make this work I have to execute the following code after datasetList has been populated for the first time:
if(doonce && !(doonce = false))
envelopeList.DataBindings.Add("DataSource", datasetList, "SelectedValue");
It's a very ugly way to do it and I would much rather be able to do this during initialization.
Some potentially important information.
Both ComboBoxes are actually my own inheriting type AdvancedComboBox. This is the relevant functionality within:
public class AdvancedComboBox : ComboBox, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected override void OnSelectedValueChanged(EventArgs e)
{
base.OnSelectedValueChanged(e);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedValue")); //I don't know why, but it works even if I remove this line.
}
}
(I have other uses for the PropertyChanged event, even if I apparently don't actually need it for the SelectedValue property.)
datasetList.DataSource is bound to an IBindingList containing DatasetPresenter objects.
DatasetPresenter has a property Areas that return an IBindingList with the objects that I want envelopeList to show.
I run datasetList.ValueMember = "Areas" before doing the binding.
The question
How do I make envelopeList.DataBindings.Add("DataSource", datasetList, "SelectedValue"); work even when datasetList is empty or achive a similar result?
I prefer solutions that I only have to execute during initialization of the ComboBoxes and/or code that I can put inside the AdvancedComboBox class so that it remains self-containing.
Bonus: Why doesn't it work when datasetList is empty? Even though datasetList.SelectedValue returns null and envelopeList.DataSource = null is okay.
Well, I found a solution, but it's not pretty.
I created this class specifically for this purpose:
private class PlaceHolder
{
public string Name { get; }//This property is the DisplayMember of both ComboBoxes.
public List<PlaceHolder> Areas { get { return new List<PlaceHolder>(0); } }//This property is the ValueMember of datasetList.
}
And during initialization I run this code.
datasetList.DataSource = new List<PlaceHolder>() { new PlaceHolder() };
envelopeList.DataBindings.Add("DataSource", datasetList, "SelectedValue");
datasetList.DataSource = myController.Datasets;//This is the intended data source.
It's ugly and I'm still looking for better solutions.
This question is a result of the fix to this problem. After getting the sort behavior to properly sort the ObservableCollection, I now notice that the first time the collection is added to, the CustomSorter handler fires with only the first item in it, and that same item is passed in as both parameters to the method. That is producing a duplicate item in the list.
Here are the pertinent parts of the view model code:
public ObservableCollection<PermissionItemViewModel> PermissionItems { get; private set; }
private void FetchRoleData()
{
PermissionItems.Clear();
if (SelectedRole != null)
{
using (var context = new myDataContext(new myDbFactory().GetConnectionString()))
{
foreach (PermissionsEnum permission in Enum.GetValues(typeof(PermissionsEnum)))
PermissionItems.Add(new PermissionItemViewModel(permission, SelectedRole[permission]));
}
}
}
All subsequent manipulations of that collection do not do this...it only happens the first time through the FetchRoleData method. Why?
EDIT:
Some additional information. The CustomSort property is set when the CollectionViewSource fires its Filter event (the only event it has AFAIK). I couldn't come up with any better trigger to get it set. The OnAttached override is too soon, as the View member of the CollectionViewSource is not set yet by that point. Catch 22, I guess. That is happening immediately after that first collection item is added. If this is due to the order in which things are being set, then are there any recommendations for a change?
I don't know how or where you're setting up the filter handler. Here's an example of how to set a custom sort on a CollectionViewSource when its View property changes. That's when you want to do it. This assumes that it's in the resources for a Window (or at least someplace where the Window can find it). But once you have cvs, wherever it comes from and however you got your mitts on it, the rest is the same.
public MainWindow()
{
InitializeComponent();
var cvs = FindResource("MyCollectionViewSource1") as CollectionViewSource;
var dpdView = DependencyPropertyDescriptor.FromProperty(
CollectionViewSource.ViewProperty, typeof(CollectionViewSource));
dpdView.AddValueChanged(cvs, (s, e) =>
{
if (cvs.View is ListCollectionView lvc)
{
lvc.CustomSort = new CustomSorter();
}
});
}
I'm baffled by your claim that the first item in the collection is being duplicated. No code you've shown, and no code I've given you, could have that effect. You'll have to share code that demonstrates that issue.
Is this documentation still valid or am I missing something?
http://doc.xceedsoft.com/products/XceedWpfToolkit/Xceed.Wpf.Toolkit~Xceed.Wpf.Toolkit.PropertyGrid.PropertyGrid~SelectedObjects.html
PropertyGrid control does not appear to have SelectedObjects or SelectedObjectsOverride members. I'm using the latest version (2.5) of the Toolkit against .NET Framework 4.0.
UPDATE
#faztp12's answer got me through. For anyone else looking for a solution, follow these steps:
Bind your PropertyGrid's SelectedObject property to the first selected item. Something like this:
<xctk:PropertyGrid PropertyValueChanged="PG_PropertyValueChanged" SelectedObject="{Binding SelectedObjects[0]}" />
Listen to PropertyValueChanged event of the PropertyGrid and use the following code to update property value to all selected objects.
private void PG_PropertyValueChanged(object sender, PropertyGrid.PropertyValueChangedEventArgs e)
{
var changedProperty = (PropertyItem)e.OriginalSource;
foreach (var x in SelectedObjects) {
//make sure that x supports this property
var ProperProperty = x.GetType().GetProperty(changedProperty.PropertyDescriptor.Name);
if (ProperProperty != null) {
//fetch property descriptor from the actual declaring type, otherwise setter
//will throw exception (happens when u have parent/child classes)
var DeclaredProperty = ProperProperty.DeclaringType.GetProperty(changedProperty.PropertyDescriptor.Name);
DeclaredProperty.SetValue(x, e.NewValue);
}
}
}
Hope this helps someone down the road.
What i did when i had similar problem was I subscribed to PropertyValueChanged and had a List filled myself with the SelectedObjects.
I checked if the contents of the List where of the same type, and then if it is so, I changed the property in each of those item :
PropertyItem changedProperty = (PropertyItem)e.OriginalSource;
PropertyInfo t = typeof(myClass).GetProperty(changedProperty.PropertyDescriptor.Name);
if (t != null)
{
foreach (myClass x in SelectedItems)
t.SetValue(x, e.NewValue);
}
I used this because i needed to make a Layout Designer and this enabled me change multiple item's property together :)
Hope it helped :)
Ref Xceed Docs
I have a Silverlight application which interacts with a WCF service. It periodically receives new items to add to a list from this service, and each new element is added to the end of an ObservableCollection (collection.Add() for each new element).
The items themselves don't change once they are received, and the items' class inherits INotifyPropertyChanged, however when I add new items (received from WCF), the DataGrid doesn't update.
I am also using a custom formatter for the DataGrid binding, but I don't think this is a problem as the initial set of items appear correctly (when the ItemsSource is first set).
I would have expected the new elements to appear, as I have confirmed that the ObservableCollection is emitting the correct add event. Since ObservableCollection inherits from INotifyCollectionChanged, shouldn't it update the DataGrid?
The only solution I have found so far is:
dataGrid.ItemsSource = null;
dataGrid.ItemsSource = collection;
Any ideas on how to get it updating? This method blocks the UI for a noticable amount of time.
Thanks
UPDATE: Code
The elements are expanded and extracted in the WCF callback event:
// The ItemWrapper allows the Binding converter to be passed the entire trade object, rather than just each property.
ObservableCollection<ItemWrapper<ExpandedTrade>> pastTrades = new ObservableCollection<ItemWrapper<ExpandedTrade>>();
....
// Extract and expand data - MinimalTrade is the data sent through WCF
var convertedTrades = from MinimalTrade t in e.trades
select new ItemWrapper<ExpandedTrade>(
new ExpandedTrade(t,
usernames.ContainsKey(t.UserToId) ? usernames[t.UserToId] : null, potentialWealth != null ? potentialWealth.CurrentWealth : null)); // Get name, otherwise null.
// Data now expanded (to show full information like usernames
// pastTrades is an observableCollection
foreach (var trade in convertedTrades)
{
pastTrades.Add(trade);
}
OnNewMyTradeHistory(pastTrades);
The OnNewMyTradeHistory event then does this:
if (tradeHistory.ItemsSource == null) tradeHistory.ItemsSource = trades;
This only sets ItemsSource once (to the ObservableCollection) and the add events are firing, but nothing is happening on the UI side.
The WCF callbacks might be happening in another thread.
I found the solution!
I had implemented the Equals, GetHashCode and ToString methods in both ItemWrapper and ExpandedTrade:
ItemWrapper.cs: (Calls the equivalent methods in the child class)
public override bool Equals(object obj)
{
if(obj is T) return Quote.Equals(obj);
if (obj is ItemWrapper<T>) return Quote.Equals(((ItemWrapper<T>)obj).Quote);
return this == obj;
}
public override int GetHashCode() { return Quote.GetHashCode(); }
public override string ToString() { return Quote.ToString(); }
ExpandedTrade.cs:
public override bool Equals(object obj)
{
if (obj == null) return false;
ExpandedQuote q = obj as ExpandedQuote;
if (q == null) return false;
return q.Id == Id;
}
public override int GetHashCode() { return Id; }
After removing these methods, it worked. I'd imagine that the DataGrid was testing for equality somewhere, and somehow something was returning an incorrect test. The IDs are unique, but by using the default test of equality by reference, it now works.
Tell me if this flow is correct:
DataGrid.ItemsSource == null;
[Update]
Create new observable collection: CollectionA
Get items and add them to CollectionA
[Event]
DataGrid.ItemsSource == null -> ItemsSource = CollectionA
[Update]
Create new observable collection: CollectionB
Get items and add them to CollectionB
[Event]
DataGrid.ItemsSource != null -> Do Nothing
=> DataGrid.ItemsSource == CollectionA?
Or is pastTrades a field which is only initialized once? Brackets and method boundaries would help.
What is the use of ObservableCollection in .net?
ObservableCollection is a collection that allows code outside the collection be aware of when changes to the collection (add, move, remove) occur. It is used heavily in WPF and Silverlight but its use is not limited to there. Code can add event handlers to see when the collection has changed and then react through the event handler to do some additional processing. This may be changing a UI or performing some other operation.
The code below doesn't really do anything but demonstrates how you'd attach a handler in a class and then use the event args to react in some way to the changes. WPF already has many operations like refreshing the UI built in so you get them for free when using ObservableCollections
class Handler
{
private ObservableCollection<string> collection;
public Handler()
{
collection = new ObservableCollection<string>();
collection.CollectionChanged += HandleChange;
}
private void HandleChange(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (var x in e.NewItems)
{
// do something
}
foreach (var y in e.OldItems)
{
//do something
}
if (e.Action == NotifyCollectionChangedAction.Move)
{
//do something
}
}
}
An ObservableCollection works essentially like a regular collection except that it implements
the interfaces:
INotifyCollectionChanged,
INotifyPropertyChanged
As such it is very useful when you want to know when the collection has changed. An event is triggered that will tell the user what entries have been added/removed or moved.
More importantly they are very useful when using databinding on a form.
From Pro C# 5.0 and the .NET 4.5 Framework
The ObservableCollection<T> class is very useful in that it has the ability to inform external objects
when its contents have changed in some way (as you might guess, working with
ReadOnlyObservableCollection<T> is very similar, but read-only in nature).
In many ways, working with
the ObservableCollection<T> is identical to working with List<T>, given that both of these classes
implement the same core interfaces. What makes the ObservableCollection<T> class unique is that this
class supports an event named CollectionChanged. This event will fire whenever a new item is inserted, a current item is removed (or relocated), or if the entire collection is modified.
Like any event, CollectionChanged is defined in terms of a delegate, which in this case is
NotifyCollectionChangedEventHandler. This delegate can call any method that takes an object as the first parameter, and a NotifyCollectionChangedEventArgs as the second. Consider the following Main()
method, which populates an observable collection containing Person objects and wires up the
CollectionChanged event:
class Program
{
static void Main(string[] args)
{
// Make a collection to observe and add a few Person objects.
ObservableCollection<Person> people = new ObservableCollection<Person>()
{
new Person{ FirstName = "Peter", LastName = "Murphy", Age = 52 },
new Person{ FirstName = "Kevin", LastName = "Key", Age = 48 },
};
// Wire up the CollectionChanged event.
people.CollectionChanged += people_CollectionChanged;
// Now add a new item.
people.Add(new Person("Fred", "Smith", 32));
// Remove an item.
people.RemoveAt(0);
Console.ReadLine();
}
static void people_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// What was the action that caused the event?
Console.WriteLine("Action for this event: {0}", e.Action);
// They removed something.
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
Console.WriteLine("Here are the OLD items:");
foreach (Person p in e.OldItems)
{
Console.WriteLine(p.ToString());
}
Console.WriteLine();
}
// They added something.
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
// Now show the NEW items that were inserted.
Console.WriteLine("Here are the NEW items:");
foreach (Person p in e.NewItems)
{
Console.WriteLine(p.ToString());
}
}
}
}
The incoming NotifyCollectionChangedEventArgs parameter defines two important properties,
OldItems and NewItems, which will give you a list of items that were currently in the collection before the event fired, and the new items that were involved in the change. However, you will want to examine these lists only under the correct circumstances. Recall that the CollectionChanged event can fire when
items are added, removed, relocated, or reset. To discover which of these actions triggered the event,
you can use the Action property of NotifyCollectionChangedEventArgs. The Action property can be
tested against any of the following members of the NotifyCollectionChangedAction enumeration:
public enum NotifyCollectionChangedAction
{
Add = 0,
Remove = 1,
Replace = 2,
Move = 3,
Reset = 4,
}
Explanation without Code
For those wanting an answer without any code behind it (boom-tish) with a story (to help you remember):
Normal Collections - No Notifications
Every now and then I go to NYC and my wife asks me to buy stuff. So I take a shopping list with me. The list has a lot of things on there like:
Louis Vuitton handbag ($5000)
Clive Christian’s Imperial Majesty Perfume ($215,000 )
Gucci Sunglasses ($2000)
hahaha well I"m not buying that stuff. So I cross them off and remove them from the list and I add instead:
12 dozen Titleist golf balls.
12 lb bowling ball.
So I usually come home without the goods and she's never pisssssssed off the thing is that she doesn't know about what i take off the list and what I add onto it; she gets no notifications.
The ObservableCollection - notifications when changes made
Now, whenever I remove something from the list: she get's a notification.
The observable collection works just the same way. If you add or remove something to or from it: someone is notified.
And when they are notified, then bunker down or run for cover! Of course, the consequences are customisable via an event handler.
Silly story, but hopefully you'll remember the concept now.
One of the biggest uses is that you can bind UI components to one, and they'll respond appropriately if the collection's contents change. For example, if you bind a ListView's ItemsSource to one, the ListView's contents will automatically update if you modify the collection.
EDIT:
Here's some sample code from MSDN:
http://msdn.microsoft.com/en-us/library/ms748365.aspx
In C#, hooking the ListBox to the collection could be as easy as
listBox.ItemsSource = NameListData;
though if you haven't hooked the list up as a static resource and defined NameItemTemplate you may want to override PersonName's ToString(). For example:
public override ToString()
{
return string.Format("{0} {1}", this.FirstName, this.LastName);
}
it is a collection which is used to notify mostly UI to change in the collection , it supports automatic notification.
Mainly used in WPF ,
Where say suppose you have UI with a list box and add button and when you click on he button an object of type suppose person will be added to the obseravablecollection and you bind this collection to the ItemSource of Listbox , so as soon as you added a new item in the collection , Listbox will update itself and add one more item in it.
class FooObservableCollection : ObservableCollection<Foo>
{
protected override void InsertItem(int index, Foo item)
{
base.Add(index, Foo);
if (this.CollectionChanged != null)
this.CollectionChanged(this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, item, index);
}
}
var collection = new FooObservableCollection();
collection.CollectionChanged += CollectionChanged;
collection.Add(new Foo());
void CollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
{
Foo newItem = e.NewItems.OfType<Foo>().First();
}
ObservableCollection Caveat
Mentioned above (Said Roohullah Allem)
What makes the ObservableCollection class unique is that this
class supports an event named CollectionChanged.
Keep this in mind...If you adding a large number of items to an ObservableCollection the UI will also update that many times. This can really gum up or freeze your UI.
A work around would be to create a new list, add all the items then set your property to the new list. This hits the UI once. Again...this is for adding a large number of items.