wpf ComboBox Async binding to items source problem - wpf

i have a mvvm app that the main window is a tab control.
i use the itemssource to bind items to the combo box,
everything works fine until i go to another tab and for some reason the selected item of the combo box getting the null value, any ideas ?
the binding is twoway updatesource onpropertychanged and the property is type of observablecollection

I had same problem before. And the solution is that make sure Itemsource attribute of comboBox in XAML has not been declared before SelectedValue attribute. It should work then.

It is just necessary to work on a ObservableCollection and to load only as AddRange (not use 'Add')
When we load the data as:
foreach (object item in items)
{
MyList.Add(item); // Where MyList is a ObservableCollection
}
Then after adding of the first element the ObservableCollection call OnCollectionChanged.
Then the ComboBox will try to select the SelectedValue and return 'null' when can't find this element.
It can solve a problem:
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
namespace System.Collections.ObjectModel
{
/// <summary>
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
/// <summary>
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
/// </summary>
public ObservableCollectionEx()
: base() { }
/// <summary>
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
/// </summary>
/// <param name="collection">collection: The collection from which the elements are copied.</param>
/// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception>
public ObservableCollectionEx(IEnumerable<T> collection)
: base(collection) { }
/// <summary>
/// Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
/// </summary>
public void AddRange(IEnumerable<T> collection)
{
//
// Add the items directly to the inner collection
//
foreach (var data in collection)
{
this.Items.Add(data);
}
//
// Now raise the changed events
//
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}

I have an mvvm app with almost exactly the same scenario. The main window has a tab control. There is a tab containing a combobox. The combobox itemsource is bound to an IList (in the view model) and the Selected value is bound to a property in the view model implementing INotifyPropertyChanged.
<ComboBox ItemsSource="{Binding AllowedJudges}"
SelectedValue="{Binding SelectedJudge, UpdateSourceTrigger=PropertyChanged}" >
When selecting another tab, the view model's property bound to the SelectedValue mysteriously gets set to null. I'm able to handle it by not allowing the SelectedValue-bound property to be set to null:
public Judge SelectedJudge
{
get { return selectedJudge; }
set
{
if(selectedJudge==value || value==null) return;
selectedJudge = value;
OnPropertyChanged("SelectedJudge");
updateViewData();
}
}
However, it's not clear to me why a tab pane becoming invisible implies a value in a combobox there becomes deselected....

If for some Reason the BindingSource of the ItemsSource does no longer contain the SeletedItem (because it's re-initialized or whatever), then the SelectedItem can be reset to default, i.e. null.
From your example I can't tell why this should happen, but that maybe because you just missed adding some more environmental code.

It`s may be problem of your viewmodel ierarchy.
For example, if both ends of your Binding are dependency properties and ownertype property is not tied to a particular class (for example, set the parent class), this dependency property will be used by all the inheritors together. Bad design

Related

How To Accommodate ItemsSource and Items

Controls such as the ListBox have an ItemsSource property which you can bind to
<ListBox x:Name="ListBoxColours" ItemsSource="{Binding Colours}" />
It also has an Items property however, which can be used to add items in the code behind
ListBoxColours.Items.Add("Red");
I'm creating a CustomControl which has a ListBox in. I've exposed the ItemSource in my control to allow the user to bind the items to a property in their ViewModel.
<ListBox
x:Name="PART_ListBox"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=local:TextBoxComboControl}}"
SelectionMode="Single" />
...
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource", typeof(IEnumerable), typeof(TextBoxComboControl), new PropertyMetadata(default(IEnumerable)));
public IEnumerable ItemsSource
{
get { return (IEnumerable) GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
...
<local:TextBoxComboControl ItemsSource="{Binding Colours}" />
I would like to add the ability for a user to add items in the code behind as well though, incase they don't want to use a binding. I was wondering how the ItemSource / Items properties interact with eachother. In order to allow them to use either peoprty, I would have to bind the ListBox items to both properties within my control.
The ListBox derives from Selector which derives from ItemsControl .
If you look at the source code to ItemsControl:
https://referencesource.microsoft.com/#PresentationFramework/Framework/System/Windows/Controls/ItemsControl.cs,54366ee76d2f1106
http://www.dotnetframework.org/default.aspx/4#0/4#0/DEVDIV_TFS/Dev10/Releases/RTMRel/wpf/src/Framework/System/Windows/Controls/ItemsControl#cs/1458001/ItemsControl#cs
You can see:
/// <summary>
/// ItemsSource specifies a collection used to generate the content of
/// this control. This provides a simple way to use exactly one collection
/// as the source of content for this control.
/// </summary>
/// <remarks>
/// Any existing contents of the Items collection is replaced when this
/// property is set. The Items collection will be made ReadOnly and FixedSize.
/// When ItemsSource is in use, setting this property to null will remove
/// the collection and restore use to Items (which will be an empty ItemCollection).
/// When ItemsSource is not in use, the value of this property is null, and
/// setting it to null has no effect.
/// </remarks>
[Bindable(true), CustomCategory("Content")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IEnumerable ItemsSource
{
get { return Items.ItemsSource; }
set
{
if (value == null)
{
ClearValue(ItemsSourceProperty);
}
else
{
SetValue(ItemsSourceProperty, value);
}
}
}
If you look at the Items property which is of type ItemsCollection ... it can be in one of two modes (ItemsSource mode or "direct" mode).
/// <summary>
/// ItemCollection will contain items shaped as strings, objects, xml nodes,
/// elements, as well as other collections. (It will not promote elements from
/// contained collections; to "flatten" contained collections, assign a
/// <seealso cref="System.Windows.Data.CompositeCollection"/> to
/// the ItemsSource property on the ItemsControl.)
/// A <seealso cref="System.Windows.Controls.ItemsControl"/> uses the data
/// in the ItemCollection to generate its content according to its ItemTemplate.
/// </summary>
/// <remarks>
/// When first created, ItemCollection is in an uninitialized state, neither
/// ItemsSource-mode nor direct-mode. It will hold settings like SortDescriptions and Filter
/// until the mode is determined, then assign the settings to the active view.
/// When uninitialized, calls to the list-modifying members will put the
/// ItemCollection in direct mode, and setting the ItemsSource will put the
/// ItemCollection in ItemsSource mode.
/// </remarks>
There's an internal member called _isUsingItemsSource which gets set/cleared to track which mode it is in - this makes various method/properties behave differently depending on the mode.
"Items" are accessed via a CollectionView (which is kept in the _collectionView member) - this either points to an InnerItemCollectionView which wraps access to the direct items , or a CollectionView created with CollectionViewSource.GetDefaultCollectionView when SetItemsSource is called due to an "itemssource" being set.
You are most probably deriving from Control so you need to provide the similar behaviour. Maybe you can derive from ItemsControl to gain that behaviour....depends on your control of course if that is suitable.

wpf mvvm : Why duplicating model collections in view models when binding to a treeview? how to avoid it?

I have a 2-levels hierarchy in my model composed of constellations and entities, hosted in a root object, and I want to show them in a TreeView.
Root object
L Constellations
L Entities
I have my RootObjectViewModel exposing my root object. In that, I fully agree with Bryan Lagunas in MVVM best practices, that the model object should be exposed by the view model instead of doing facade. Excellent video by the way, really worth the time.
I read everywhere that the TreeView.ItemsSource should be mapped to a collection of viewmodels, that is, for my collection of Constellation, my RootObjectViewModel should provide a collection of ConstellationViewModel.
My concern is that if my collections of constellations, and entities within, are live, that is, if some items are added, changed (their order) or removed, I have to manually reflect those changes in my ViewModels' collections.
I would find it more elegant to map the ItemsSource to, say, the collection of Constellation itself, so that any change in the model is reflected without duplication, and then have some converter or so to map the TreeViewItems.DataContext to a ConstellationViewModel based on the constellation model.
If this is not clear enough, I can clarify this with some sample code.
Did anyone face the same question and/or try to implement this ?
Thanks you in advance for your help.
Cedric
It depends. If your model has exactly the properties the view needs, and the view can directly alter them when the user clicks around, it's fine to expose the model.
But if your model is, for example, read only and require calls to a service to apply changes, you have to wrap it in a view model to provide the view with writeable properties.
Got it working !
It is not possible out-of-the-box, and here's why:
It is possible to use model collections as items source, and to use a converter to get the appropriate view model it the components inside the TreeViewItem. But there isn't any way to interfere with the creation of TreeViewItem to apply the converter to its DataContext. Which means that the TreeViewItem's properties can't be binded to the ViewModel.
In other words :
if you want to stick with the standard behavior of the TreeView and don't have to deal with the TreeViewItems properties, if either your collections don't change or can implement ICollectionChanged, and if your models don't change or can implement IPropertyChanged, it is fine to go with the model's collections.
If any of these conditions is broken, then you will have to go with building ViewModel's collections and sync them with model's collection.
Now, I implemented a collection type named ConvertingCollection<Tin, Tout> that uses an original collection as an input and syncs its own contents with this input. This is just a basic class with many many ways of improvement, but it works. All you have to do is use this collection as a VM property, set the original collection and converter, and bind the ItemsSource to this collection.
Here's the full code:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Globalization;
using System.Windows.Data;
namespace TreeViewPOC
{
class ConvertingCollection<Tin, Tout> : ObservableCollection<Tout>
{
private IValueConverter _converter;
private bool _isObservableCollection;
private IEnumerable<Tin> _originalCollection;
private Dictionary<Tin, Tout> _mapping = new Dictionary<Tin, Tout>();
public ConvertingCollection(IValueConverter converter)
{
// save parameters
_converter = converter;
}
public ConvertingCollection(IEnumerable<Tin> originalCollection, IValueConverter converter)
{
// save parameters
_converter = converter;
OriginalCollection = originalCollection;
}
#region Properties
public IEnumerable<Tin> OriginalCollection
{
get
{
return _originalCollection;
}
set
{
if (!value.Equals(_originalCollection))
{
// manage older collection
if (_originalCollection != null && _isObservableCollection)
{
(_originalCollection as ObservableCollection<Tin>).CollectionChanged -= originalCollection_CollectionChanged;
this.Clear();
}
_originalCollection = value;
// setup original collection information.
_isObservableCollection = _originalCollection is INotifyCollectionChanged;
if (_originalCollection != null && _isObservableCollection)
{
(_originalCollection as INotifyCollectionChanged).CollectionChanged += originalCollection_CollectionChanged;
foreach (Tin item in _originalCollection)
{
AddConverted(item);
}
}
}
}
}
#endregion
/// <summary>
/// Indicates the time in milliseconds between two refreshes.
/// </summary>
/// <notes>
/// When the original collection isn't observable, it must be explored to reflect changes in the converted collection.
/// </notes>
// TODO
//public int RefreshRate { get; set; } = 1000;
/// <summary>
/// Flushes the collection.
/// </summary>
public new void Clear()
{
_mapping.Clear();
base.Clear();
}
#region Events management
private void originalCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (Tin item in e.NewItems)
{
AddConverted(item);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (Tin item in e.OldItems)
{
RemoveConverted(item);
}
break;
}
}
#endregion
#region Helpers
/// <summary>
/// Converts an item and adds it to the collection.
/// </summary>
/// <param name="item">The original item.</param>
private void AddConverted(Tin item)
{
Tout converted = (Tout) _converter.Convert(item, typeof(Tout), null, CultureInfo.CurrentCulture);
_mapping.Add(item, converted);
this.Add(converted);
}
/// <summary>
/// Removes a converted itemfrom the collection based on its original value.
/// </summary>
/// <param name="item">The original item.</param>
private void RemoveConverted(Tin item)
{
this.Remove(_mapping[item]);
_mapping.Remove(item);
}
#endregion
}
}
I created a project on Github named MVVMTreeViewPOC to POC the idea. It is working fine, and it is a good tool to show the limitations.
Cedric.

Breakpoint (and extra code) not being hit in set block of data-bound property, though property value is being updated

I am using a custom control derived from a listbox, but with added features. One of the key features is the addition of a bindable SelectedItems property on the control, so the view model can keep track of the multiple selections made in the control. The binding does work - when you select items in the control, the view model's property is updated. However, I would like to add INotifyDataErrorInfo validation to the view model, so I implemented the interface and added a call to my validation method in the set block of the data-bound property in the viewmodel. For some reason that set block is never being called, even though I am updating the control in the view, and am verifying that the view model's property value is actually being changed correctly to match the control.
I know that when I use binding with standard WPF controls, such as a TextBox, the set block of the source (view model) property is called when the target (view) property changes. Is there a reason it wouldn't be called here?
The custom control I am using is found here. This is my property on the viewmodel (I have the console output there just to ensure the code isn't being called):
private ObservableCollection<Car> _testListSelections;
public ObservableCollection<Car> testListSelections
{
get
{
return _testListSelections;
}
set
{
Console.WriteLine("Value changed.");
_testListSelections = value;
OnPropertyChanged("testListSelections");
Validate();
}
}
This is my XAML (note that I didn't need to use Mode=TwoWay here as I am using an ObservableCollection, but I did try specifying Mode=TwoWay and the set block still didn't get hit):
<src:MultiComboBox SelectionMode="Multiple"
VerticalAlignment="Center"
ItemsSource="{Binding testList}"
SelectedItems="{Binding testListSelections, ValidatesOnNotifyDataErrors=True}"/>
This is the SelectedItems property on the custom control (the author overrode the base read-only SelectedItems property in order to allow binding):
/// <summary>
/// The SelectedItems dependency property. Access to the values of the items that are
/// selected in the selectedItems box. If SelectionMode is Single, this property returns an array
/// of length one.
/// </summary>
public static new readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(BindableListBox),
new FrameworkPropertyMetadata(
(d, e) =>
{
// When the property changes, update the selected values in the selectedItems box.
(d as BindableListBox).SetSelectedItemsNew(e.NewValue as IList);
}));
/// <summary>
/// Get or set the selected items.
/// </summary>
public new IList SelectedItems
{
get
{
return GetValue(SelectedItemsProperty) as IList;
}
set { SetValue(SelectedItemsProperty, value); }
}
You should perform the validation in the OnCollectionChanged event of the list.
The SelectedItems list should be set only once, and then changes are made to the same list.
You can then check if the operation is Add, Remove or Reset, and perform validation accordingly.

What triggers UI to update when ItemsControl.ItemsSource changed?

I was just looking into the difference between BindingList and ObservableCollection following this question: Why NOT BindingList in WPF
As part of this, I tested binding the ItemsSource of an ItemsControl to various types, including List, Collection, ObservableCollection and BindingList.
What surprised me is that the interface updated when either the ObservableCollection or the BindingList were modified, but not when the others were. So what is WPF listening to that causes that update? It can't be the INotifyCollectionChanged event, as I previously thought, because BindingList does not implement that. Bemused.
Binding list looks like this:
public class BindingList<T> : Collection<T>, IBindingList, IList, ICollection, IEnumerable, ICancelAddNew, IRaiseItemChangedEvents
{
IRaiseItemChangedEvents indicates that the object class converts property change events to ListChanged events
.
BindingList itself has the ListChanged event which is what WPF must be listening to.
If fact it looks like IRaiseItemChangedEvents is ignored, but there's a BindingListCollectionView which contains
// subscribe to change notifications
private void SubscribeToChanges ()
{
if (InternalList.SupportsChangeNotification)
{
InternalList.ListChanged += new ListChangedEventHandler(OnListChanged);
}
}
and a constructor like
/// <summary>
/// Constructor
/// </summary>
/// <param name="list">Underlying IBindingList</param>
public BindingListCollectionView(IBindingList list)
: base(list)
{
InternalList = list;
I recommend you get hold of DotPeek and see for yourself.

ComboBox not changing when bound value of SelectedItem changes

I have a ComboBox that has its ItemSource and SelectedItem properties bound to a view model. And I have the following block of code that is the callback for a data query against a DomainContext:
/// <summary>
/// Stores (readonly) - Stores available for ship to store.
/// </summary>
public ObservableCollection<StoreEntity> Stores
{
get { return _stores; }
private set { _stores = value; RaisePropertyChanged("Stores"); }
}
/// <summary>
/// SelectedStore - Currently selected store.
/// </summary>
public StoreEntity SelectedStore
{
get { return _selectedStore; }
set { _selectedStore = value; RaisePropertyChanged("SelectedStore"); }
}
/// <summary>
/// When stores are completely loaded.
/// </summary>
/// <param name="a_loadOperations"></param>
protected void OnStoresLoaded(LoadOperation<StoreEntity> a_loadOperations)
{
Stores.AddRange(a_loadOperations.Entities);
SelectedStore = a_loadOperations.Entities.FirstOrDefault();
}
In this example, Stores is a ObservableCollection<StoreEntity> (AddRange is an extention method) and is bound to ItemSource, and SelectedStore is a StoreEntity and is bound to SelectedItem.
The problem here is that the ComboBox is not changing its selection to reflect the change in SelectedItem.
Edits:
I've even tried the following, though I think that a_loadOperation.Entities is already a realized set:
/// <summary>
/// When stores are completely loaded.
/// </summary>
/// <param name="a_loadOperations"></param>
protected void OnStoresLoaded(LoadOperation<StoreEntity> a_loadOperations)
{
var entities = a_loadOperations.Entities.ToArray();
Stores.AddRange(entities);
SelectedStore = entities.First();
}
Thanks
If you are trying to get a change to your viewmodel (specifically the SelectedStore property) to be reflected in your combo box, you could:
Confirm the binding worked. Check it was set up properly in the XAML, and check the Output window to see if there is a message saying the Binding failed
Confirm your DataContext is set properly (it probably is since you are getting the combo box `ItemsSource` from `Stores`
Subscribe to your PropertyChanged event and confirm that it is being raised when the property changes
If that doesn't work, it may be a bug with the ComboBox. I have seen cases where the order of specifying the properties in XAML makes the difference (ex: you should set ItemsSource first and SelectedItem second). I have also seen a binding fail until I added Mode=TwoWay (even though in your example you are trying to get the binding to update from your view model to your UI). Try confirming that your ComboBox XAML is like this:
<ComboBox ItemsSource="{Binding Stores}" SelectedItem="{Binding SelectedStore, Mode=TwoWay}" />
Order shouldn't matter since XAML is declarative, but I have personally seen it matter with ComboBoxes in Silverlight.
I had SelectedItem bound to Stores instead of SelectedStore. Ooops!

Resources