Is there a Threadsafe Observable collection in .NET 4? - wpf

Platform: WPF, .NET 4.0, C# 4.0
Problem: In the Mainwindow.xaml i have a ListBox bound to a Customer collection which is currently an ObservableCollection< Customer >.
ObservableCollection<Customer> c = new ObservableCollection<Customer>();
This collection can be updated via multiple sources, like FileSystem, WebService etc.
To allow parallel loading of Customers I have created a helper class
public class CustomerManager(ref ObsevableCollection<Customer> cust)
that internally spawns a new Task (from Parallel extensions library) for each customer Source and adds a new Customer instance to the customer collection object (passed by ref to its ctor).
The problem is that ObservableCollection< T> (or any collection for that matter) cannot be used from calls other than the UI thread and an exception is encountered:
"NotSupportedException – This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread."
I tried using the
System.Collections.Concurrent.ConcurrentBag<Customer>
collection but it doesnot implement INotifyCollectionChanged interface. Hence my WPF UI won't get updated automatically.
So, is there a collection class that implements both property/collection change notifications and also allows calls from other non-UI threads?
By my initial bing/googling, there is none provided out of the box.
Edit: I created my own collection that inherits from ConcurrentBag< Customer > and also implements the INotifyCollectionChanged interface. But to my surprise even after invoking it in separate tasks, the WPF UI hangs until the task is completed. Aren't the tasks supposed to be executed in parallel and not block the UI thread?
Thanks for any suggestions, in advance.

There are two possible approaches. The first would be to inherit from a concurrent collection and add INotifyCollectionChanged functionality, and the second would be to inherit from a collection that implements INotifyCollectionChanged and add concurrency support. I think it is far easier and safer to add INotifyCollectionChanged support to a concurrent collection. My suggestion is below.
It looks long but most of the methods just call the internal concurrent collection as if the caller were using it directly. The handful of methods that add or remove from the collection inject a call to a private method that raises the notification event on the dispatcher provided at construction, thus allowing the class to be thread safe but ensuring the notifications are raised on the same thread all the time.
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows.Threading;
namespace Collections
{
/// <summary>
/// Concurrent collection that emits change notifications on a dispatcher thread
/// </summary>
/// <typeparam name="T">The type of objects in the collection</typeparam>
[Serializable]
[ComVisible(false)]
[HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
public class ObservableConcurrentBag<T> : IProducerConsumerCollection<T>,
IEnumerable<T>, ICollection, IEnumerable
{
/// <summary>
/// The dispatcher on which event notifications will be raised
/// </summary>
private readonly Dispatcher dispatcher;
/// <summary>
/// The internal concurrent bag used for the 'heavy lifting' of the collection implementation
/// </summary>
private readonly ConcurrentBag<T> internalBag;
/// <summary>
/// Initializes a new instance of the ConcurrentBag<T> class that will raise <see cref="INotifyCollectionChanged"/> events
/// on the specified dispatcher
/// </summary>
public ObservableConcurrentBag(Dispatcher dispatcher)
{
this.dispatcher = dispatcher;
this.internalBag = new ConcurrentBag<T>();
}
/// <summary>
/// Initializes a new instance of the ConcurrentBag<T> class that contains elements copied from the specified collection
/// that will raise <see cref="INotifyCollectionChanged"/> events on the specified dispatcher
/// </summary>
public ObservableConcurrentBag(Dispatcher dispatcher, IEnumerable<T> collection)
{
this.dispatcher = dispatcher;
this.internalBag = new ConcurrentBag<T>(collection);
}
/// <summary>
/// Occurs when the collection changes
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event on the <see cref="dispatcher"/>
/// </summary>
private void RaiseCollectionChangedEventOnDispatcher(NotifyCollectionChangedEventArgs e)
{
this.dispatcher.BeginInvoke(new Action<NotifyCollectionChangedEventArgs>(this.RaiseCollectionChangedEvent), e);
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event
/// </summary>
/// <remarks>
/// This method must only be raised on the dispatcher - use <see cref="RaiseCollectionChangedEventOnDispatcher" />
/// to do this.
/// </remarks>
private void RaiseCollectionChangedEvent(NotifyCollectionChangedEventArgs e)
{
this.CollectionChanged(this, e);
}
#region Members that pass through to the internal concurrent bag but also raise change notifications
bool IProducerConsumerCollection<T>.TryAdd(T item)
{
bool result = ((IProducerConsumerCollection<T>)this.internalBag).TryAdd(item);
if (result)
{
this.RaiseCollectionChangedEventOnDispatcher(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
return result;
}
public void Add(T item)
{
this.internalBag.Add(item);
this.RaiseCollectionChangedEventOnDispatcher(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
public bool TryTake(out T item)
{
bool result = this.TryTake(out item);
if (result)
{
this.RaiseCollectionChangedEventOnDispatcher(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
}
return result;
}
#endregion
#region Members that pass through directly to the internal concurrent bag
public int Count
{
get
{
return this.internalBag.Count;
}
}
public bool IsEmpty
{
get
{
return this.internalBag.IsEmpty;
}
}
bool ICollection.IsSynchronized
{
get
{
return ((ICollection)this.internalBag).IsSynchronized;
}
}
object ICollection.SyncRoot
{
get
{
return ((ICollection)this.internalBag).SyncRoot;
}
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return ((IEnumerable<T>)this.internalBag).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)this.internalBag).GetEnumerator();
}
public T[] ToArray()
{
return this.internalBag.ToArray();
}
void IProducerConsumerCollection<T>.CopyTo(T[] array, int index)
{
((IProducerConsumerCollection<T>)this.internalBag).CopyTo(array, index);
}
void ICollection.CopyTo(Array array, int index)
{
((ICollection)this.internalBag).CopyTo(array, index);
}
#endregion
}
}

Please take a look at the BindableCollection<T> from Caliburn.Micro library:
/// <summary>
/// A base collection class that supports automatic UI thread marshalling.
/// </summary>
/// <typeparam name="T">The type of elements contained in the collection.</typeparam>
#if !SILVERLIGHT && !WinRT
[Serializable]
#endif
public class BindableCollection<T> : ObservableCollection<T>, IObservableCollection<T> {
/// <summary>
/// Initializes a new instance of the <see cref = "Caliburn.Micro.BindableCollection{T}" /> class.
/// </summary>
public BindableCollection() {
IsNotifying = true;
}
/// <summary>
/// Initializes a new instance of the <see cref = "Caliburn.Micro.BindableCollection{T}" /> class.
/// </summary>
/// <param name = "collection">The collection from which the elements are copied.</param>
/// <exception cref = "T:System.ArgumentNullException">
/// The <paramref name = "collection" /> parameter cannot be null.
/// </exception>
public BindableCollection(IEnumerable<T> collection) : base(collection) {
IsNotifying = true;
}
#if !SILVERLIGHT && !WinRT
[field: NonSerialized]
#endif
bool isNotifying; //serializator try to serialize even autogenerated fields
/// <summary>
/// Enables/Disables property change notification.
/// </summary>
#if !WinRT
[Browsable(false)]
#endif
public bool IsNotifying {
get { return isNotifying; }
set { isNotifying = value; }
}
/// <summary>
/// Notifies subscribers of the property change.
/// </summary>
/// <param name = "propertyName">Name of the property.</param>
#if WinRT || NET45
public virtual void NotifyOfPropertyChange([CallerMemberName]string propertyName = "") {
#else
public virtual void NotifyOfPropertyChange(string propertyName) {
#endif
if(IsNotifying)
Execute.OnUIThread(() => OnPropertyChanged(new PropertyChangedEventArgs(propertyName)));
}
/// <summary>
/// Raises a change notification indicating that all bindings should be refreshed.
/// </summary>
public void Refresh() {
Execute.OnUIThread(() => {
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
});
}
/// <summary>
/// Inserts the item to the specified position.
/// </summary>
/// <param name = "index">The index to insert at.</param>
/// <param name = "item">The item to be inserted.</param>
protected override sealed void InsertItem(int index, T item) {
Execute.OnUIThread(() => InsertItemBase(index, item));
}
/// <summary>
/// Exposes the base implementation of the <see cref = "InsertItem" /> function.
/// </summary>
/// <param name = "index">The index.</param>
/// <param name = "item">The item.</param>
/// <remarks>
/// Used to avoid compiler warning regarding unverifiable code.
/// </remarks>
protected virtual void InsertItemBase(int index, T item) {
base.InsertItem(index, item);
}
#if NET || WP8 || WinRT
/// <summary>
/// Moves the item within the collection.
/// </summary>
/// <param name="oldIndex">The old position of the item.</param>
/// <param name="newIndex">The new position of the item.</param>
protected sealed override void MoveItem(int oldIndex, int newIndex) {
Execute.OnUIThread(() => MoveItemBase(oldIndex, newIndex));
}
/// <summary>
/// Exposes the base implementation fo the <see cref="MoveItem"/> function.
/// </summary>
/// <param name="oldIndex">The old index.</param>
/// <param name="newIndex">The new index.</param>
/// <remarks>Used to avoid compiler warning regarding unverificable code.</remarks>
protected virtual void MoveItemBase(int oldIndex, int newIndex) {
base.MoveItem(oldIndex, newIndex);
}
#endif
/// <summary>
/// Sets the item at the specified position.
/// </summary>
/// <param name = "index">The index to set the item at.</param>
/// <param name = "item">The item to set.</param>
protected override sealed void SetItem(int index, T item) {
Execute.OnUIThread(() => SetItemBase(index, item));
}
/// <summary>
/// Exposes the base implementation of the <see cref = "SetItem" /> function.
/// </summary>
/// <param name = "index">The index.</param>
/// <param name = "item">The item.</param>
/// <remarks>
/// Used to avoid compiler warning regarding unverifiable code.
/// </remarks>
protected virtual void SetItemBase(int index, T item) {
base.SetItem(index, item);
}
/// <summary>
/// Removes the item at the specified position.
/// </summary>
/// <param name = "index">The position used to identify the item to remove.</param>
protected override sealed void RemoveItem(int index) {
Execute.OnUIThread(() => RemoveItemBase(index));
}
/// <summary>
/// Exposes the base implementation of the <see cref = "RemoveItem" /> function.
/// </summary>
/// <param name = "index">The index.</param>
/// <remarks>
/// Used to avoid compiler warning regarding unverifiable code.
/// </remarks>
protected virtual void RemoveItemBase(int index) {
base.RemoveItem(index);
}
/// <summary>
/// Clears the items contained by the collection.
/// </summary>
protected override sealed void ClearItems() {
Execute.OnUIThread(ClearItemsBase);
}
/// <summary>
/// Exposes the base implementation of the <see cref = "ClearItems" /> function.
/// </summary>
/// <remarks>
/// Used to avoid compiler warning regarding unverifiable code.
/// </remarks>
protected virtual void ClearItemsBase() {
base.ClearItems();
}
/// <summary>
/// Raises the <see cref = "E:System.Collections.ObjectModel.ObservableCollection`1.CollectionChanged" /> event with the provided arguments.
/// </summary>
/// <param name = "e">Arguments of the event being raised.</param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) {
if (IsNotifying) {
base.OnCollectionChanged(e);
}
}
/// <summary>
/// Raises the PropertyChanged event with the provided arguments.
/// </summary>
/// <param name = "e">The event data to report in the event.</param>
protected override void OnPropertyChanged(PropertyChangedEventArgs e) {
if (IsNotifying) {
base.OnPropertyChanged(e);
}
}
/// <summary>
/// Adds the range.
/// </summary>
/// <param name = "items">The items.</param>
public virtual void AddRange(IEnumerable<T> items) {
Execute.OnUIThread(() => {
var previousNotificationSetting = IsNotifying;
IsNotifying = false;
var index = Count;
foreach(var item in items) {
InsertItemBase(index, item);
index++;
}
IsNotifying = previousNotificationSetting;
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
});
}
/// <summary>
/// Removes the range.
/// </summary>
/// <param name = "items">The items.</param>
public virtual void RemoveRange(IEnumerable<T> items) {
Execute.OnUIThread(() => {
var previousNotificationSetting = IsNotifying;
IsNotifying = false;
foreach(var item in items) {
var index = IndexOf(item);
if (index >= 0) {
RemoveItemBase(index);
}
}
IsNotifying = previousNotificationSetting;
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
});
}
/// <summary>
/// Called when the object is deserialized.
/// </summary>
/// <param name="c">The streaming context.</param>
[OnDeserialized]
public void OnDeserialized(StreamingContext c) {
IsNotifying = true;
}
/// <summary>
/// Used to indicate whether or not the IsNotifying property is serialized to Xml.
/// </summary>
/// <returns>Whether or not to serialize the IsNotifying property. The default is false.</returns>
public virtual bool ShouldSerializeIsNotifying() {
return false;
}
}
Source
PS. Just take in mind that this class use some other classes from Caliburn.Micro so that you could either copy/pase all dependencies by your-self - OR - if you are not using any other application frameworks - just reference the library binary and give it a chance.

I spent ages looking at all the solutions and none really fit what I needed, until I finally realized the problem: I didn't want a threadsafe list - I just wanted a non-threadsafe list that could be modified on any thread, but that notified changes on the UI thread.
(The reason for not wanting a threadsafe collection is the usual one - often you need to perform multiple operations, like "if it's not in the list, then add it" which threadsafe lists don't actually help with, so you want to control the locking yourself).
The solution turned out to be quite simple in concept and has worked well for me. Just create a new list class that implements IList<T> and INotifyCollectionChanged. Delegate all calls you need to an underlying implementation (e.g. a List<T>) and then call notifications on the UI thread where needed.
public class AlbumList : IList<Album>, INotifyCollectionChanged
{
private readonly IList<Album> _listImplementation = new List<Album>();
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void OnChanged(NotifyCollectionChangedEventArgs e)
{
Application.Current?.Dispatcher.Invoke(DispatcherPriority.Render,
new Action(() => CollectionChanged?.Invoke(this, e)));
}
public void Add(Album item)
{
_listImplementation.Add(item);
OnChanged(new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, item));
}
public bool Remove(Album item)
{
int index = _listImplementation.IndexOf(item);
var removed = index >= 0;
if (removed)
{
_listImplementation.RemoveAt(index);
OnChanged(new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, item, index));
}
return removed;
}
// ...snip...
}

There's a detailed explanation and an implementation here. It was written mainly for .NET 3.5 SP1 but it will still work in 4.0.
The primary target of this implementation is when the "real" list exists longer than the bindable view of it (eg. if it is bound in a window that the user can open and close). If the lifetimes are the other way around (eg. you're updating the list from a background worker that runs only when the window is open), then there are some simpler designs available.

Related

Is there a way to test the RaiseChanged use of a property in MVVMLight?

I have lots of properties in my viewmodels that have very little logic in them but they have the RaisePropertyChanged() method in them to refresh the GUI. i.e.
private bool _StatesIsSelected;
public bool StatesIsSelected
{
get { return _StatesIsSelected; }
set
{
_StatesIsSelected = value;
RaisePropertyChanged("StatesIsSelected");
}
}
I am starting to wonder if I should have unit tests confirming that the RaisePropertyChanged() method has been called. If I forgot to put it in the property the GUI wouldn't get refreshed and the application would have a bug...so it should have a unit test. But how do you test that?
So to sum it up....Am I being to militant about having unit tests for this logic? And if I am not being to militant...what is there a good way to test for this?
Are you being militant? That is not really an easy question to answer. We do test the majority of our property changed events, of which there are a lot, and I'm not really sure how much value there is in those tests. By that I mean if we removed them and stopped writing them in the future would we start seeing more bugs, or even any that wouldn't be pretty obvious as soon as you used the client? To be honest the answer is probably no. Conversely they are easy tests to write and certainly don't hurt.
Anyway, yes there is a very nice way to do this (had to make a few minor tweeks, so can't guarantee the code will compile, but should make the concepts clear):
public static class PropertyChangedTestHelperFactory
{
/// <summary>
/// Factory method for creating <see cref="PropertyChangedTestHelper{TTarget}"/> instances.
/// </summary>
/// <param name="target">
/// The target.
/// </param>
/// <typeparam name="TTarget">
/// The target type.
/// </typeparam>
/// <returns>
/// The <see cref="PropertyChangedTestHelper{TTarget}"/>
/// </returns>
public static PropertyChangedTestHelper<TTarget> CreatePropertyChangedHelper<TTarget>(
this TTarget target)
where TTarget : INotifyPropertyChanged
{
return new PropertyChangedTestHelper<TTarget>(target);
}
}
public sealed class PropertyChangedTestHelper<TTarget> : IDisposable
where TTarget : INotifyPropertyChanged
{
/// <summary>
/// This list contains the expected property names that should occur in property change notifications
/// </summary>
private readonly Queue<string> propertyNames = new Queue<string>();
/// <summary>
/// The target of the helper
/// </summary>
private readonly TTarget target;
/// <summary>
/// Initialises a new instance of the <see cref="StrictPropertyChangedTestHelper{TTarget}"/> class.
/// </summary>
/// <param name="target">The target.</param>
public PropertyChangedTestHelper(TTarget target)
{
this.target = target;
this.target.PropertyChanged += this.HandleTargetPropertyChanged;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.target.PropertyChanged -= this.HandleTargetPropertyChanged;
if (this.propertyNames.Count != 0)
{
Assert.Fail("Property change notification {0} was not raised", this.propertyNames.Peek());
}
}
/// <summary>
/// Sets an expectation that a refresh change notification will be raised.
/// </summary>
public void ExpectRefresh()
{
this.propertyNames.Enqueue(string.Empty);
}
/// <summary>
/// Sets an expectation that a property change notification will be raised.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="propertyExpression">The property expression.</param>
public void Expect<TProperty>(Expression<Func<TTarget, TProperty>> propertyExpression)
{
this.propertyNames.Enqueue(((MemberExpression)propertyExpression.Body).Member.Name);
}
/// <summary>
/// Handles the target property changed event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
private void HandleTargetPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (this.propertyNames.Count == 0)
{
Assert.Fail("Unexpected property change notification {0}", e.PropertyName);
}
var expected = this.propertyNames.Dequeue();
var propertyName = (e.PropertyName ?? string.Empty).Trim();
if (propertyName != expected)
{
Assert.Fail("Out of order property change notification, expected '{0}', actual '{1}'", expected, propertyName);
}
}
}
Usage:
[TestMethod]
public void StatesIsSelected_RaisesIsValidChangeNotification()
{
// Arrange
var target = new SomeViewModel();
using (var helper = target.CreatePropertyChangedHelper())
{
helper.Expect(item => item.StatesIsSelected);
// Act
target.StatesIsSelected = true;
// Assert
}
}
When the helper is disposed the expectations are interrogated and the test will fail if they are not all met in the order they were defined.
We also have a Weak version that only requires that the expectations are met, not that they are met exactly (i.e. other property change events could be raised) and that is not order dependent.
FYI - if I were you I'd think about ditching MVVMLight and moving to Caliburn.Micro, its in a different league.
you can test it easily:
void TestMethod()
{
Container container = new Container();
bool isRaised = false;
container.PropertyChanged += (o,e) => {
if(e.PropertyName == "StatesIsSelected")
isRaised = true;
};
container.StatesIsSelected = true;
Assert.True(isRaised);
}
It think it would be useful to write the test for the ViewModel base class but not for all properties which raise a change, thats just too extreme
If you're targeting .NET Framework >= 4.5 you can inherit from ViewModelBase and write helper method:
public class ViewModelBaseExtended : ViewModelBase
{
protected void TryRaisePropertyChanged<T>(ref T oldValue, T newValue,
[CallerMemberName] string propertyName = "")
{
if (oldValue == null || !oldValue.Equals(newValue))
{
oldValue = newValue;
RaisePropertyChanged(propertyName);
}
}
}
and your property code would look like that:
private bool _StatesIsSelected;
public bool StatesIsSelected
{
get { return _StatesIsSelected; }
set
{
TryRaisePropertyChanged(ref _StatesIsSelected, value);
}
}
Now you would only have to assert property value in your unit tests.

ICustomTypeDescriptor wth XamDataGrid

I have extended the XamDataGrid to support DynamicColumn generation using a DependencyProperty called ColumnSource. Thus the gird now will generate columns dynamically based on a dependency property called "ColumnSource". this idea was inspired from the DevExpress WPF Grid I had used before.
Having said that, I need to mention that I am using Field (not UnBoundField) inside the extended control to generate the columns and binding them to the ViewModel objects. It has worked fine for requirement I had till now.
Now I have a situation where I have a ViewModel that needs to have dynamic properties. Obviously I have ICustomTypeDescriptor in mind.I just am curious is it possible to view data in the XamDataGrid with the following limitations:
.Net 4.0
ICustomTypeDescriptor
Use of field and not UnboundField class for column generations.
Data shown should be two way bindable,
that is change in cell data should change appropriate ViewModel
property.
I am pasting the Extended control's code here. It is very long so I will try to curtail the code responsible for other functionalities.
public class AdvancedXamDataGrid : XamDataGrid
{
#region Static Constructor
static AdvancedXamDataGrid()
{
//Dependency properties overrides if any to be done here.
DataSourceProperty.OverrideMetadata(typeof(AdvancedXamDataGrid), new FrameworkPropertyMetadata(null, DataSourcePropetyChanged));
}
#endregion
#region Dependency Properties
/// <summary>
/// Dependency proeprty for Columns List shown in the Grid Header
/// </summary>
public static readonly DependencyProperty ColumnsSourceProperty = DependencyProperty.Register("ColumnsSource", typeof(IEnumerable),
typeof(AdvancedXamDataGrid), new FrameworkPropertyMetadata(null, OnColumnsSourceChanged));
/// <summary>
/// Gets or sets the <see cref="ColumnsSource"/>.
/// This is a Dependency Property.
/// </summary>
public IEnumerable ColumnsSource
{
get { return GetValue(ColumnsSourceProperty) as IEnumerable; }
set { SetValue(ColumnsSourceProperty, value); }
}
#endregion
#region Dependency Property Property Changed Handlers (static).
/// <summary>
/// The handler is fired when the <see cref="ColumnsSource"/> is changed.
/// </summary>
/// <param name="sender">The dependency object that raises the event.</param>
/// <param name="e">The event argument</param>
private static void OnColumnsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as AdvancedXamDataGrid;
if (null != control)
{
if (null != control._fieldAdornerSettings)
control.DetachAdorner();
control._fieldAdornerSettings = new FieldAdornerSettings();
control._fieldAdornerList = new List<FieldAdorner>();
var oldValue = e.OldValue as IEnumerable;
var newValue = e.NewValue as IEnumerable;
if (BindingOperations.IsDataBound(sender, ColumnsSourceProperty))
control.ColumnsSourceChanged(oldValue, newValue);
}
}
/// <summary>
/// This handler is fired when the data source property changes.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void DataSourcePropetyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as AdvancedXamDataGrid;
if (null != control)
{
var dataSource = e.NewValue as IEnumerable;
control.DataSource = dataSource;
}
}
#endregion
#region Instance Properties and Event Handlers
/// <summary>
/// Handles when the <see cref="ColumnsSource"/> is changed.
/// </summary>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
private void ColumnsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (null != oldValue)
//I could never figure out why I need this check. But this check is requred for consistent laytout for first time load.Funny I know!
FieldLayouts.Clear(); //Clear the existing columns.
var oldColSource = oldValue as INotifyCollectionChanged;
if (null != oldColSource)
{
oldColSource.CollectionChanged -= oldColSource_CollectionChanged;
//Remove the columns first.
foreach (IGridColumn column in oldValue)
{
RemoveField(column);
}
}
var newColSource = newValue as INotifyCollectionChanged;
if (null != newColSource)
{
newColSource.CollectionChanged += oldColSource_CollectionChanged;
}
if (null != newValue)
{
var fieldLayout = new FieldLayout {IsDefault = true, Key = Convert.ToString(Guid.NewGuid())};
FieldLayouts.Add(fieldLayout);
foreach (IGridColumn col in newValue)
{
AddField(col);
}
DefaultFieldLayout = fieldLayout;
}
}
/// <summary>
/// Fires when the ColumnsSource Collection changes.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void oldColSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Remove old Items.
foreach (IGridColumn col in e.OldItems)
{
RemoveField(col);
}
//Add new items.
foreach (IGridColumn col in e.NewItems)
{
AddField(col);
}
}
/// <summary>
/// Adds a Field to the wrapped grids FiledCollection.
/// </summary>
/// <param name="column"></param>
private void AddField(IGridColumn column)
{
if (FieldLayouts.Count > 0)
{
var fieldLayout = FieldLayouts[0];
var field = new Field {Name = column.Name, Label = column.DisplayName.ToUpper(), ToolTip = column.ToolTip};
switch (column.ColumnType)
{
// case GridColumnType.Text:
// field.DataType = typeof(string);
// break;
case GridColumnType.Boolean:
var style = new Style(typeof (XamCheckEditor));
style.Setters.Add(new Setter(XamCheckEditor.IsCheckedProperty,
new Binding()
{
Path = new PropertyPath(string.Concat("DataItem.", column.Name)),
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode = BindingMode.TwoWay
}));
field.Settings.EditorType = typeof (XamCheckEditor);
field.Settings.EditorStyle = style;
break;
}
if (column.ColumnType == GridColumnType.Combo)
{
var style = new Style(typeof (XamComboEditor));
style.Setters.Add(new Setter(XamComboEditor.ItemsSourceProperty,
new Binding() {Path = new PropertyPath(column.ItemsSource)}));
style.Setters.Add(new Setter(XamComboEditor.SelectedItemProperty,
new Binding(column.SelectedItemPropertyName) {Mode = BindingMode.TwoWay}));
style.Setters.Add(new Setter(XamComboEditor.DisplayMemberPathProperty, column.DisplayMemberPath));
style.Setters.Add(new Setter(XamComboEditor.ValuePathProperty, column.ValueMemberPath));
field.Settings.EditorType = typeof (XamComboEditor);
field.Settings.EditorStyle = style;
}
if (column.IsReadOnly)
field.Settings.AllowEdit = false;
if (!column.IsVisible)
field.Visibility = Visibility.Collapsed;
fieldLayout.Fields.Add(field);
if (!string.IsNullOrEmpty(column.TemplateKey))
_fieldAdornerList.Add(new FieldAdorner()
{
Name = column.Name,
BindToParentSource = column.BindToParent,
TemplateKey = column.TemplateKey
});
//Register to the property changed notofication.
var propertyNotifier = column as INotifyPropertyChanged;
propertyNotifier.PropertyChanged += propertyNotifier_PropertyChanged;
}
}
/// <summary>
/// Removes a field
/// </summary>
/// <param name="column"></param>
private void RemoveField(IGridColumn column)
{
if (FieldLayouts.Count > 0)
{
var fieldLayout = FieldLayouts[0];
var field = fieldLayout.Fields.FirstOrDefault(f => f.Name.Equals(column.Name));
if (null != field)
fieldLayout.Fields.Remove(field);
var propertyNotifier = column as INotifyPropertyChanged;
propertyNotifier.PropertyChanged -= propertyNotifier_PropertyChanged;
}
}
/// <summary>
/// Event handler for handling property notification.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void propertyNotifier_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var column = sender as IGridColumn;
if (null != column)
{
var fieldLayout = FieldLayouts[0];
var field = fieldLayout.Fields.FirstOrDefault(f => f.Name.Equals(column.Name));
if (e.PropertyName.Equals("IsVisible"))
{
if (field != null)
field.Visibility = column.IsVisible ? Visibility.Visible : Visibility.Collapsed;
}
if (e.PropertyName.Equals("IsReadOnly"))
{
if (field != null)
field.Settings.AllowEdit = !column.IsReadOnly;
}
}
}
#endregion
}
Here is the IGridColumn contract:
/// <summary>
/// A contract that need to be implemented by an item that needs to participate in ColumnSource binding.
/// </summary>
public interface IGridColumn : INotifyPropertyChanged
{
/// <summary>
/// Gets or sets the PropertyName to which the Column would bind.
/// </summary>
string Name { get; set; }
/// <summary>
/// Gets or sets the Display Text that will be visible in the column header.
/// </summary>
string DisplayName { get; set; }
/// <summary>
/// Gets the type of the property that gets bound to this column.
/// </summary>
GridColumnType ColumnType { get; }
/// <summary>
/// Gets or sets if the column is read-only.
/// </summary>
bool IsReadOnly { get; set; }
/// <summary>
/// Gets or sets if the column is visible.
/// </summary>
bool IsVisible { get; set; }
#region For Combo Columns
/// <summary>
/// Gets or sets the Items source of the combo editor.
/// </summary>
string ItemsSource { get; set; }
/// <summary>
/// Gets or sets the SelectedItem propertyName.
/// </summary>
string SelectedItemPropertyName { get; set; }
/// <summary>
/// Gets or sets the name of the property that be the display item of the combo.
/// </summary>
string DisplayMemberPath { get; set; }
/// <summary>
/// Gets or sets the name of the property that be the value item of the combo.
/// </summary>
string ValueMemberPath { get; set; }
/// <summary>
/// Gets or sets the tool tip on the column.
/// </summary>
string ToolTip { get; set; }
/// <summary>
/// Gets or sets the Template Key for the adorner.
/// </summary>
string TemplateKey { get; set; }
/// <summary>
/// Gets or sets if the smart tag, would be bound to the view model of the grid.
/// <remarks>
/// Note: By default it would be bound to an item of the grid.
/// </remarks>
/// </summary>
bool BindToParent { get; set; }
/// <summary>
/// Gets or sets the caption for the smart tag.
/// </summary>
string SmartTagCaption { get; set; }
#endregion
}
/// <summary>
/// An enumeration offering various types of Grid Column types.
/// </summary>
public enum GridColumnType
{
Text=0,
Boolean,
Integer,
Double,
Decimal,
Combo
} ;
I had the plan to populating the ColumnSource for the Grid and bind them to ICustomTypeDescriptor instances of ViewModels whose Dynamic property names would match with the IGridColumn Names.

Implement file explorer based on tree view with multiple selection

I`m beginner WPF. I developing a new project at my work and I need to insert a file explorer control with multiple selection.
The concept need to be similar to acronis file explorer: (Treeview with checkboxes)
Look at the left container, I need to implement something similar to this,
I habe searched alot through google and I saw lot of implementations but nothing wasn`t similar to this.
Because I don`t have alot experience in WPF it quite difficult for me to start.
Do you have some tips or similar projects which might help me do it?
My project based on MVVM DP.
Thanks
Remodelling the Treeview is very easy, you start with your collection that you want to bind to, i.e.
<Grid>
<TreeView ItemsSource="{Binding Folders}"/>
</Grid>
However you then need to define how to display the data you have bound to. I'm assuming that your items are just an IEnumerable (any list or array) of FolderViewModels and FileViewModels (both have a Name property), so now we need to say how to display those. You do that by defining a DataTemplate and since this is for a tree we use a HeirarchicalDataTemplate as that also defines subItems
<Grid.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}"
ItemsSource="{Binding SubFoldersAndFiles}">
<CheckBox Content="{Binding Name}"/>
</HierarchicalDataTemplate>
<Grid.Resources/>
Files are the same but dont need sub items
<HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}">
<CheckBox Content="{Binding Name}"/>
</HierarchicalDataTemplate>
So putting it all together you get
<Grid>
<Grid.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}"
ItemsSource="{Binding SubFoldersAndFiles}">
<CheckBox Content="{Binding Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}">
<CheckBox Content="{Binding Name}"/>
</HierarchicalDataTemplate>
<Grid.Resources/>
<TreeView ItemsSource="{Binding Folders}"/>
</Grid>
Icons
If you want to show icons then you change the content in the CheckBox, I'm assuming you will define an Image on your ViewModel.
<CheckBox>
<CheckBox.Content>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Image}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</CheckBox.Content>
Selection
Finally you have to handle the selection of items. I'd advise adding an IsSelected property to your FileViewModel and FolderViewModels. For files this is incredibly simple, its just a bool.
public class FileViewModel : INotifyProperty
...
public bool IsSelected //Something here to handle get/set and NotifyPropertyChanged that depends on your MVVM framework, I use ReactiveUI a lot so that's this syntax
{
get { return _IsSelected;}
set { this.RaiseAndSetIfChanged(x=>x.IsSelected, value); }
}
and
<CheckBox IsChecked="{Binding IsSelected}">
Its slightly more complicated with FolderViewModel and I'll look at the logic in a second. First the Xaml, just replace the current CheckBox declaration with
<CheckBox IsThreeState="True" IsChecked="{Binding IsSelected}">
<!--IsChecked = True, False or null-->
So now we need to return a set of Nullable<bool> (aka bool?).
public bool? IsSelected
{
get
{
if (SubFoldersAndFiles.All(x=>x.IsSelected) return true;
if (SubFoldersAndFiles.All(x=>x.IsSelected==false) return false;
return null;
}
set
{
// We can't set to indeterminate at folder level so we have to set to
// set to oposite of what we have now
if(value == null)
value = !IsSelected;
foreach(var x in SubFoldersAndFiles)
x.IsSelected = value;
}
Or something very similar...
After taking a look at the answer by #AlSki, I decided it is neither intuitive nor versatile enough for my liking and came up with my own solution. The disadvantage of using my solution, however, is it requires a tad bit more boilerplate. On the other hand, it offers a LOT more flexibility.
The samples below assume you use .NET 4.6.1 and C# 6.0.
/// <summary>
/// A base for abstract objects (implements INotifyPropertyChanged).
/// </summary>
[Serializable]
public abstract class AbstractObject : INotifyPropertyChanged
{
/// <summary>
///
/// </summary>
[field: NonSerialized()]
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
///
/// </summary>
/// <param name="propertyName"></param>
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
///
/// </summary>
/// <typeparam name="TKind"></typeparam>
/// <param name="Source"></param>
/// <param name="NewValue"></param>
/// <param name="Names"></param>
protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify)
{
//Set value if the new value is different from the old
if (!Source.Equals(NewValue))
{
Source = NewValue;
//Notify all applicable properties
Notify?.ForEach(i => OnPropertyChanged(i));
return true;
}
return false;
}
/// <summary>
///
/// </summary>
public AbstractObject()
{
}
}
An object with a check state.
/// <summary>
/// Specifies an object with a checked state.
/// </summary>
public interface ICheckable
{
/// <summary>
///
/// </summary>
bool? IsChecked
{
get; set;
}
}
/// <summary>
///
/// </summary>
public class CheckableObject : AbstractObject, ICheckable
{
/// <summary>
///
/// </summary>
[field: NonSerialized()]
public event EventHandler<EventArgs> Checked;
/// <summary>
///
/// </summary>
[field: NonSerialized()]
public event EventHandler<EventArgs> Unchecked;
/// <summary>
///
/// </summary>
[XmlIgnore]
protected bool? isChecked;
/// <summary>
///
/// </summary>
public virtual bool? IsChecked
{
get
{
return isChecked;
}
set
{
if (SetValue(ref isChecked, value, "IsChecked") && value != null)
{
if (value.Value)
{
OnChecked();
}
else OnUnchecked();
}
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return base.ToString();
//return isChecked.ToString();
}
/// <summary>
///
/// </summary>
protected virtual void OnChecked()
{
Checked?.Invoke(this, new EventArgs());
}
/// <summary>
///
/// </summary>
protected virtual void OnUnchecked()
{
Unchecked?.Invoke(this, new EventArgs());
}
/// <summary>
///
/// </summary>
public CheckableObject() : base()
{
}
/// <summary>
///
/// </summary>
/// <param name="isChecked"></param>
public CheckableObject(bool isChecked = false)
{
IsChecked = isChecked;
}
}
The view model for checked system objects:
/// <summary>
///
/// </summary>
public class CheckableSystemObject : CheckableObject
{
#region Properties
/// <summary>
///
/// </summary>
public event EventHandler Collapsed;
/// <summary>
///
/// </summary>
public event EventHandler Expanded;
bool StateChangeHandled = false;
CheckableSystemObject Parent { get; set; } = default(CheckableSystemObject);
ISystemProvider SystemProvider { get; set; } = default(ISystemProvider);
ConcurrentCollection<CheckableSystemObject> children = new ConcurrentCollection<CheckableSystemObject>();
/// <summary>
///
/// </summary>
public ConcurrentCollection<CheckableSystemObject> Children
{
get
{
return children;
}
private set
{
SetValue(ref children, value, "Children");
}
}
bool isExpanded = false;
/// <summary>
///
/// </summary>
public bool IsExpanded
{
get
{
return isExpanded;
}
set
{
if (SetValue(ref isExpanded, value, "IsExpanded"))
{
if (value)
{
OnExpanded();
}
else OnCollapsed();
}
}
}
bool isSelected = false;
/// <summary>
///
/// </summary>
public bool IsSelected
{
get
{
return isSelected;
}
set
{
SetValue(ref isSelected, value, "IsSelected");
}
}
string path = string.Empty;
/// <summary>
///
/// </summary>
public string Path
{
get
{
return path;
}
set
{
SetValue(ref path, value, "Path");
}
}
bool queryOnExpanded = false;
/// <summary>
///
/// </summary>
public bool QueryOnExpanded
{
get
{
return queryOnExpanded;
}
set
{
SetValue(ref queryOnExpanded, value);
}
}
/// <summary>
///
/// </summary>
public override bool? IsChecked
{
get
{
return isChecked;
}
set
{
if (SetValue(ref isChecked, value, "IsChecked") && value != null)
{
if (value.Value)
{
OnChecked();
}
else OnUnchecked();
}
}
}
#endregion
#region CheckableSystemObject
/// <summary>
///
/// </summary>
/// <param name="path"></param>
/// <param name="systemProvider"></param>
/// <param name="isChecked"></param>
public CheckableSystemObject(string path, ISystemProvider systemProvider, bool? isChecked = false) : base()
{
Path = path;
SystemProvider = systemProvider;
IsChecked = isChecked;
}
#endregion
#region Methods
void Determine()
{
//If it has a parent, determine it's state by enumerating all children, but current instance, which is already accounted for.
if (Parent != null)
{
StateChangeHandled = true;
var p = Parent;
while (p != null)
{
p.IsChecked = Determine(p);
p = p.Parent;
}
StateChangeHandled = false;
}
}
bool? Determine(CheckableSystemObject Root)
{
//Whether or not all children and all children's children have the same value
var Uniform = true;
//If uniform, the value
var Result = default(bool?);
var j = false;
foreach (var i in Root.Children)
{
//Get first child's state
if (j == false)
{
Result = i.IsChecked;
j = true;
}
//If the previous child's state is not equal to the current child's state, it is not uniform and we are done!
else if (Result != i.IsChecked)
{
Uniform = false;
break;
}
}
return !Uniform ? null : Result;
}
void Query(ISystemProvider SystemProvider)
{
children.Clear();
if (SystemProvider != null)
{
foreach (var i in SystemProvider.Query(path))
{
children.Add(new CheckableSystemObject(i, SystemProvider, isChecked)
{
Parent = this
});
}
}
}
/// <summary>
///
/// </summary>
protected override void OnChecked()
{
base.OnChecked();
if (!StateChangeHandled)
{
//By checking the root only, all children are checked automatically
foreach (var i in children)
i.IsChecked = true;
Determine();
}
}
/// <summary>
///
/// </summary>
protected override void OnUnchecked()
{
base.OnUnchecked();
if (!StateChangeHandled)
{
//By unchecking the root only, all children are unchecked automatically
foreach (var i in children)
i.IsChecked = false;
Determine();
}
}
/// <summary>
///
/// </summary>
protected virtual void OnCollapsed()
{
Collapsed?.Invoke(this, new EventArgs());
}
/// <summary>
///
/// </summary>
protected virtual void OnExpanded()
{
Expanded?.Invoke(this, new EventArgs());
if (!children.Any<CheckableSystemObject>() || queryOnExpanded)
BeginQuery(SystemProvider);
}
/// <summary>
///
/// </summary>
/// <param name="SystemProvider"></param>
public async void BeginQuery(ISystemProvider SystemProvider)
{
await Task.Run(() => Query(SystemProvider));
}
#endregion
}
Utilities for querying system objects; note, by defining your own SystemProvider, you can query different types of systems (i.e., local or remote). By default, your local system is queried. If you want to display objects from a remote server like FTP, you'd want to define a SystemProvider that utilizes the appropriate web protocol.
/// <summary>
/// Specifies an object capable of querying system objects.
/// </summary>
public interface ISystemProvider
{
/// <summary>
///
/// </summary>
/// <param name="Path">The path to query.</param>
/// <param name="Source">A source used to make queries.</param>
/// <returns>A list of system object paths.</returns>
IEnumerable<string> Query(string Path, object Source = null);
}
/// <summary>
/// Defines base functionality for an <see cref="ISystemProvider"/>.
/// </summary>
public abstract class SystemProvider : ISystemProvider
{
/// <summary>
///
/// </summary>
/// <param name="Path"></param>
/// <param name="Source"></param>
/// <returns></returns>
public abstract IEnumerable<string> Query(string Path, object Source = null);
}
/// <summary>
/// Defines functionality to query a local system.
/// </summary>
public class LocalSystemProvider : SystemProvider
{
/// <summary>
///
/// </summary>
/// <param name="Path"></param>
/// <param name="Source"></param>
/// <returns></returns>
public override IEnumerable<string> Query(string Path, object Source = null)
{
if (Path.IsNullOrEmpty())
{
foreach (var i in System.IO.DriveInfo.GetDrives())
yield return i.Name;
}
else
{
if (System.IO.Directory.Exists(Path))
{
foreach (var i in System.IO.Directory.EnumerateFileSystemEntries(Path))
yield return i;
}
}
}
}
And then an inherited TreeView, which puts this all together:
/// <summary>
///
/// </summary>
public class SystemObjectPicker : TreeViewExt
{
#region Properties
/// <summary>
///
/// </summary>
public static DependencyProperty QueryOnExpandedProperty = DependencyProperty.Register("QueryOnExpanded", typeof(bool), typeof(SystemObjectPicker), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnQueryOnExpandedChanged));
/// <summary>
///
/// </summary>
public bool QueryOnExpanded
{
get
{
return (bool)GetValue(QueryOnExpandedProperty);
}
set
{
SetValue(QueryOnExpandedProperty, value);
}
}
static void OnQueryOnExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.As<SystemObjectPicker>().OnQueryOnExpandedChanged((bool)e.NewValue);
}
/// <summary>
///
/// </summary>
public static DependencyProperty RootProperty = DependencyProperty.Register("Root", typeof(string), typeof(SystemObjectPicker), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnRootChanged));
/// <summary>
///
/// </summary>
public string Root
{
get
{
return (string)GetValue(RootProperty);
}
set
{
SetValue(RootProperty, value);
}
}
static void OnRootChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.As<SystemObjectPicker>().OnRootChanged((string)e.NewValue);
}
/// <summary>
///
/// </summary>
static DependencyProperty SystemObjectsProperty = DependencyProperty.Register("SystemObjects", typeof(ConcurrentCollection<CheckableSystemObject>), typeof(SystemObjectPicker), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
/// <summary>
///
/// </summary>
ConcurrentCollection<CheckableSystemObject> SystemObjects
{
get
{
return (ConcurrentCollection<CheckableSystemObject>)GetValue(SystemObjectsProperty);
}
set
{
SetValue(SystemObjectsProperty, value);
}
}
/// <summary>
///
/// </summary>
public static DependencyProperty SystemProviderProperty = DependencyProperty.Register("SystemProvider", typeof(ISystemProvider), typeof(SystemObjectPicker), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSystemProviderChanged));
/// <summary>
///
/// </summary>
public ISystemProvider SystemProvider
{
get
{
return (ISystemProvider)GetValue(SystemProviderProperty);
}
set
{
SetValue(SystemProviderProperty, value);
}
}
static void OnSystemProviderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.As<SystemObjectPicker>().OnSystemProviderChanged((ISystemProvider)e.NewValue);
}
#endregion
#region SystemObjectPicker
/// <summary>
///
/// </summary>
public SystemObjectPicker() : base()
{
SetCurrentValue(SystemObjectsProperty, new ConcurrentCollection<CheckableSystemObject>());
SetCurrentValue(SystemProviderProperty, new LocalSystemProvider());
SetBinding(ItemsSourceProperty, new Binding()
{
Mode = BindingMode.OneWay,
Path = new PropertyPath("SystemObjects"),
Source = this
});
}
#endregion
#region Methods
void OnQueryOnExpandedChanged(CheckableSystemObject Item, bool Value)
{
foreach (var i in Item.Children)
{
i.QueryOnExpanded = Value;
OnQueryOnExpandedChanged(i, Value);
}
}
/// <summary>
///
/// </summary>
/// <param name="Value"></param>
protected virtual void OnQueryOnExpandedChanged(bool Value)
{
foreach (var i in SystemObjects)
OnQueryOnExpandedChanged(i, Value);
}
/// <summary>
///
/// </summary>
/// <param name="Provider"></param>
/// <param name="Root"></param>
protected virtual void OnRefreshed(ISystemProvider Provider, string Root)
{
SystemObjects.Clear();
if (Provider != null)
{
foreach (var i in Provider.Query(Root))
{
SystemObjects.Add(new CheckableSystemObject(i, SystemProvider)
{
QueryOnExpanded = QueryOnExpanded
});
}
}
}
/// <summary>
///
/// </summary>
/// <param name="Value"></param>
protected virtual void OnRootChanged(string Value)
{
OnRefreshed(SystemProvider, Value);
}
/// <summary>
///
/// </summary>
/// <param name="Value"></param>
protected virtual void OnSystemProviderChanged(ISystemProvider Value)
{
OnRefreshed(Value, Root);
}
#endregion
}
Obviously, it's dramatically more complex than #AlSki's answer, but, again, you get more flexibility and the hard stuff is taken care of for you already.
In addition, I have published this code in the latest version of my open source project (3.1) if such a thing interests you; if not, the samples above is all you need to get it working.
If you do not download the project, note the following:
You will find some extension methods that do not exist, which can be supplemented with their counterparts (e.g., IsNullOrEmpty extension is identical to string.IsNullOrEmpty()).
TreeViewExt is a custom TreeView I designed so if you don't care about that, simply change TreeViewExt to TreeView; either way, you should not have to define a special control template for it as it was designed to work with TreeView's existing facilities.
In the sample, I use my own version of a concurrent ObservableCollection; this is so you can query data on a background thread without jumping through hoops. Change this to ObservableCollection and make all queries synchronous OR use your own concurrent ObservableCollection to preserve the asynchronous functionality.
Finally, here is how you would use the control:
<Controls.Extended:SystemObjectPicker>
<Controls.Extended:SystemObjectPicker.ItemContainerStyle>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</Controls.Extended:SystemObjectPicker.ItemContainerStyle>
<Controls.Extended:SystemObjectPicker.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<CheckBox
IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,5,0"/>
<TextBlock
Text="{Binding Path, Converter={StaticResource FileNameConverter}, Mode=OneWay}"/>
</StackPanel>
</HierarchicalDataTemplate>
</Controls.Extended:SystemObjectPicker.ItemTemplate>
</Controls.Extended:SystemObjectPicker>
To Do
Add a property to CheckableSystemObject that exposes a view model for the system object; that way, you can access the FileInfo/DirectoryInfo associated with it's path or some other source of data that otherwise describes it. If the object is remote, you may have already defined your own class to describe it, which could be useful if you have the reference to it.
Catch possible exceptions when querying a local system; if a system object cannot be accessed, it will fail. LocalSystemProvider also fails to address system paths that exceed 260 characters; however, that is beyond the scope of this project.
Note To Moderators
I referenced my own open source project for convenience as I published the above samples in the latest version; my intention is not to self-promote so if referencing your own project is frowned upon, I will proceed to remove the link.

How to sort TabItems in the TabControl

I have a collection of type Page with an Order property, I set ItemsSource property of a TabControl an ObservableCollection. What I need to whenever I changed the Order property of an Entity
the related TabItem go in the correct location.
WPF XAML :
<TabControl Grid.Row="1" ItemsSource="{Binding Pages.ListViewModels}" SelectedItem="{Binding Pages.Current}" >
<TabControl.ContentTemplate>
<DataTemplate>
<views:EditPageView />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
C# Codes:
public class QuestionPageSection : INotifyPropertyChanged
{
public virtual int Id { get; set; }
public virtual string Header { get; set; }
private int _Order;
public virtual int Order
{
get
{
return _Order;
}
set
{
_Order = value;
this.RaisePropertyChanged(() => this.Order , PropertyChanged);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I want to force TabControl to sort TabItems based on the Order property. So now I have these questoins:
Is there any way to do it declaratively?
Does TabControl have a SortColumn property?
Does TabItem have a TabOrder property?
Is there any type of collection that listen to its childs to automatically sort itself based on a property of childs??
Any other idea would be apperciated.
You simply need to sort the collection that your TabControl is bound to
I've always hated the fact that ObservableCollection doesn't have a built-in Sort method, so I usually use my own custom class that inherits from ObservableCollection
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
public ObservableCollectionEx() : base() { }
public ObservableCollectionEx(List<T> l) : base(l) { }
public ObservableCollectionEx(IEnumerable<T> l) : base(l) { }
#region IndexOf
/// <summary>
/// Returns the index of the first object which meets the specified function
/// </summary>
/// <param name="keySelector">A bool function to compare each Item by</param>
/// <returns>The index of the first Item which matches the function</returns>
public int IndexOf(Func<T, bool> compareFunction)
{
return Items.IndexOf(Items.FirstOrDefault(compareFunction));
}
#endregion
#region Sorting
/// <summary>
/// Sorts the items of the collection in ascending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
public void Sort<TKey>(Func<T, TKey> keySelector)
{
InternalSort(Items.OrderBy(keySelector));
}
/// <summary>
/// Sorts the items of the collection in descending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
public void SortDescending<TKey>(Func<T, TKey> keySelector)
{
InternalSort(Items.OrderByDescending(keySelector));
}
/// <summary>
/// Sorts the items of the collection in ascending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
/// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
{
InternalSort(Items.OrderBy(keySelector, comparer));
}
/// <summary>
/// Moves the items of the collection so that their orders are the same as those of the items provided.
/// </summary>
/// <param name="sortedItems">An <see cref="IEnumerable{T}"/> to provide item orders.</param>
private void InternalSort(IEnumerable<T> sortedItems)
{
var sortedItemsList = sortedItems.ToList();
foreach (var item in sortedItemsList)
{
Move(IndexOf(item), sortedItemsList.IndexOf(item));
}
}
#endregion
}
I can use it like this:
ListViewModels = GetListViewModels();
ListViewModels.Sort(p => p.Order);
You can sort you're ObservableCollection on the UI side by using CollectionViewSource. Here's a link with examples: http://msdn.microsoft.com/en-us/library/ms742542.aspx
Thanks Rachel, your solution gave me the clue, but still your solution is an answer because I can manually call the Sort method whenever I change the Order property, but I wanted to make it automatic. So I end up with a dynamic version of your code.
Based on the Rachel I approached to this solution.
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
public ObservableCollectionEx() : base() { }
public ObservableCollectionEx(List<T> l) : base(l) { }
public ObservableCollectionEx(IEnumerable<T> l) : base(l) { }
Func<IEnumerable<T>,IEnumerable<T>> sortFunction;
Action reset;
#region IndexOf
/// <summary>
/// Returns the index of the first object which meets the specified function
/// </summary>
/// <param name="keySelector">A bool function to compare each Item by</param>
/// <returns>The index of the first Item which matches the function</returns>
public int IndexOf(Func<T , bool> compareFunction)
{
return Items.IndexOf(Items.FirstOrDefault(compareFunction));
}
#endregion IndexOf
#region Sorting
/// <summary>
/// Sorts the items of the collection in ascending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
public void SetSort<TKey>(Func<T , TKey> keySelector)
{
sortFunction = list => list.OrderBy(keySelector);
InternalSort();
reset = () => SetSort(keySelector);
}
/// <summary>
/// Sorts the items of the collection in descending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
public void SetSortDescending<TKey>(Func<T , TKey> keySelector)
{
sortFunction = list => list.OrderByDescending(keySelector);
InternalSort();
reset = () => SetSortDescending(keySelector);
}
/// <summary>
/// Sorts the items of the collection in ascending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
/// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
public void SetSort<TKey>(Func<T , TKey> keySelector , IComparer<TKey> comparer)
{
sortFunction = list => list.OrderBy(keySelector , comparer);
InternalSort();
reset = () => SetSort(keySelector , comparer);
}
/// <summary>
/// Sorts the items of the collection in descending order according to a key.
/// </summary>
/// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
/// <param name="keySelector">A function to extract a key from an item.</param>
/// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
public void SetSortDescending<TKey>(Func<T , TKey> keySelector , IComparer<TKey> comparer)
{
sortFunction = list => list.OrderByDescending(keySelector , comparer);
InternalSort();
reset = () => SetSortDescending(keySelector , comparer);
}
/// <summary>
/// Moves the items of the collection so that their orders are the same as those of the items provided.
/// </summary>
private void InternalSort()
{
UpdateTracking(null , Items.ToList());
}
private void MoveItemToItsLocation(T item)
{
var sortListCache = sortFunction(Items).ToList();
Move(IndexOf(item) , sortListCache.IndexOf(item));
}
#endregion Sorting
public void UpdateTracking(IEnumerable<T> oldItems , IEnumerable<T> newItems)
{
if (sortFunction == null) return;
PropertyChangedEventHandler changeTracker = (o , change) => { MoveItemToItsLocation((T)o); };
Action<T> attachChangeTracker = o => o.ExecuteOnCast<INotifyPropertyChanged>(x => x.PropertyChanged += changeTracker);
Action<T> detachChangeTracker = o => o.ExecuteOnCast<INotifyPropertyChanged>(x => x.PropertyChanged -= changeTracker);
var greeting = new[] { attachChangeTracker , MoveItemToItsLocation };
var farwell = new[] { detachChangeTracker };
oldItems.ForEach(detachChangeTracker);
newItems.ForEach(attachChangeTracker , MoveItemToItsLocation);
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
UpdateTracking(e.OldItems.SafeGet(x => x.Cast<T>()) , e.NewItems.SafeGet(x => x.Cast<T>()));
break;
case NotifyCollectionChangedAction.Reset:
UpdateTracking(Items.ToList() , null);
if (reset != null)
reset();
break;
default:
break;
}
}
}
I only made a little change to make it follow the changes in the underlying collection, so it will sort itself automatically after any changes made to underlying collection or any element in the underlying collection.

Binding double click of a row to a command in a Silverlight Data Grid

I want to be able to route the double-click of a grid to a Command. I'm using Rx to simulate the double click but I can't figure out what control to attach the mouse handler to (the mouse event on the e.Row object in DataGrid.RowLoading event doesn't seem to work).
Anyone got any ideas?
Rx code for handling the Double click is as follows:
Observable.FromEvent<MouseButtonEventArgs>(e.Row, "MouseLeftButtonDown").TimeInterval().Subscribe(evt =>
{
if (evt.Interval.Milliseconds <= 300)
{
// Execute command on double click
}
});
I changed this code from handling MouseLeftButtonDown to MouseLeftButtonUp and it works now. The row must have something else handling the button down events.
Observable.FromEvent<MouseButtonEventArgs>(e.Row, "MouseLeftButtonUp").TimeInterval().Subscribe(evt =>
{
if (evt.Interval != TimeSpan.Zero && evt.Interval.TotalMilliseconds <= 300)
{
// Execute command on double click
}
});
Full source code is included below:
CommandBehaviorBase.cs
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
/// <summary>
/// Provides the base implementation of all Behaviors that can be attached to a <see cref="FrameworkElement"/> which trigger a command.
/// </summary>
/// <typeparam name="T">The type of control this behavior can be attached to, must derive from <see cref="FrameworkElement"/>.</typeparam>
public abstract class CommandBehaviorBase<T> : Behavior<T> where T : FrameworkElement
{
#region Constants and Fields
/// <summary>The DependencyProperty backing store for CommandParameter. This enables animation, styling, binding, etc...</summary>
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(CommandBehaviorBase<T>),
new PropertyMetadata(null, OnCommandParameterPropertyChanged));
/// <summary>The DependencyProperty backing store for Command. This enables animation, styling, binding, etc...</summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(CommandBehaviorBase<T>), new PropertyMetadata(null));
#endregion
/// <summary>
/// Gets or sets the command to execute
/// </summary>
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
/// <summary>
/// Gets or sets the command parameter to execute with.
/// </summary>
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
/// <summary>
/// Gets or sets the command binding path (Hack for SL3).
/// </summary>
/// <remarks>This is a hack to overcome the fact that we cannot
/// bind to the Command dependency property due to a limitation in Silverlight 3.0
/// This shouldn't be necessary as in Silverlight 4.0 <see cref="DependencyObject"/> supports data binding hooray!</remarks>
public string CommandPath { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this mapping is currently enabled.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// Implements the logic that disables the key mapping based on whether the command can be executed.
/// </summary>
/// <summary>
/// Updates the target object's IsEnabled property based on the commands ability to execute.
/// </summary>
public virtual void UpdateEnabledState()
{
if (this.Command == null && !string.IsNullOrEmpty(this.CommandPath))
{
this.Command = this.AssociatedObject.DataContext.GetPropertyPathValue<ICommand>(this.CommandPath, null);
}
if (this.AssociatedObject == null)
{
this.Command = null;
this.CommandParameter = null;
}
else if (this.Command != null)
{
this.IsEnabled = this.Command.CanExecute(this.CommandParameter);
}
}
/// <summary>
/// Executes the command, if it's set, providing the <see cref="CommandParameter"/>
/// </summary>
protected virtual void ExecuteCommand()
{
if (this.Command != null)
{
this.Command.Execute(this.CommandParameter);
}
}
/// <summary>
/// Attaches to the target <see cref="FrameworkElement"/> and sets up the command.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.UpdateEnabledState();
}
/// <summary>
/// Raised when the command parameter changes, re-evaluates whether the Command can execute
/// </summary>
/// <param name="sender">The KeyCommandBehavior that command parameter changed for.</param>
/// <param name="args">The parameter is not used.</param>
private static void OnCommandParameterPropertyChanged(
DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((CommandBehaviorBase<T>)sender).UpdateEnabledState();
}
}
DoubleClickCommandBehavior.cs
using System;
using System.Linq;
using System.Windows;
using System.Windows.Input;
/// <summary>
/// Provides behavior for any clickable control and will execute a command when the control is double clicked.
/// Does not disable the control if the command cannot be executed.
/// </summary>
public class DoubleClickCommandBehavior : CommandBehaviorBase<FrameworkElement>
{
#region Constants and Fields
/// <summary>
/// Stores the observable that subscribes to click events.
/// </summary>
private IDisposable observable;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the DoubleClickCommandBehavior class.
/// </summary>
public DoubleClickCommandBehavior()
{
// Default double-click interval is 220 milliseconds.
this.Interval = 220;
}
#endregion
/// <summary>
/// Gets or sets the double click interval in milliseconds.
/// </summary>
public int Interval
{
get;
set;
}
/// <summary>
/// Subscribes to the MouseLeftButtonUp of the data grid and times the intervals between the events,
/// if the time between clicks is less than the configured interval the command is executed.
/// </summary>
/// <remarks>Originally attached to MouseLeftButtonDown but the events were not firing.</remarks>
protected override void OnAttached()
{
base.OnAttached();
this.observable =
Observable.FromEvent<MouseButtonEventArgs>(this.AssociatedObject, "MouseLeftButtonUp").TimeInterval().
Subscribe(
evt =>
{
if (evt.Interval != TimeSpan.Zero && evt.Interval.TotalMilliseconds <= this.Interval)
{
this.UpdateEnabledState();
this.ExecuteCommand();
}
});
}
/// <summary>
/// Disposes of the observable
/// </summary>
protected override void OnDetaching()
{
if (this.observable != null)
{
this.observable.Dispose();
this.observable = null;
}
base.OnDetaching();
}
}
I'm having similar problems (though not using Rx to handle the double click, instead using a generic DoubleClickTrigger). My specific problem is more related to the fact that I'm not sure how or where to hook up my trigger.
I've tried something like the following:
<data:DataGrid.Resources>
<ControlTemplate x:Key="rowTemplate" TargetType="data:DataGridRow">
<data:DataGridRow>
<fxui:Interaction.Triggers>
<fxui:DoubleClickTrigger>
<Interactivity:InvokeCommandAction Command="{Binding Source={StaticResource selectCommand}}" CommandParameter="{Binding}"/>
</fxui:DoubleClickTrigger>
</fxui:Interaction.Triggers>
</data:DataGridRow>
</ControlTemplate>
</data:DataGrid.Resources>
<data:DataGrid.RowStyle>
<Style TargetType="data:DataGridRow">
<Setter Property="Template" Value="{StaticResource rowTemplate}"/>
</Style>
</data:DataGrid.RowStyle>
With no luck.

Resources