Related
I have a view model and field and I want to send field another view model. I cant use object initialize because the constructor executions first and this field is null. I need this field in the constructor. I tried constructor parameter but that didn't work I get a error;
'ProductDetailViewModel' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TVm' in the generic type or method 'BasePage<TVm>'
P.S: sorry for my English :|
My Base Page:
/// <summary>
/// The base page for all pages to gain base functionality
/// </summary>
public class BasePage : UserControl
{
#region Private Member
/// <summary>
/// The View Model associated with this page
/// </summary>
private object mViewModel;
#endregion
#region Public Properties
/// <summary>
/// The animation the play when the page is first loaded
/// </summary>
public PageAnimation PageLoadAnimation { get; set; } = PageAnimation.SlideAndFadeInFromRight;
/// <summary>
/// The animation the play when the page is unloaded
/// </summary>
public PageAnimation PageUnloadAnimation { get; set; } = PageAnimation.SlideAndFadeOutToLeft;
/// <summary>
/// The time any slide animation takes to complete
/// </summary>
public float SlideSeconds { get; set; } = 0.4f;
/// <summary>
/// A flag to indicate if this page should animate out on load.
/// Useful for when we are moving the page to another frame
/// </summary>
public bool ShouldAnimateOut { get; set; }
/// <summary>
/// The View Model associated with this page
/// </summary>
public object ViewModelObject
{
get => mViewModel;
set
{
// If nothing has changed, return
if (mViewModel == value)
return;
// Update the value
mViewModel = value;
// Fire the view model changed method
OnViewModelChanged();
// Set the data context for this page
DataContext = mViewModel;
}
}
#endregion
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public BasePage()
{
// Don't bother animating in design time
if (DesignerProperties.GetIsInDesignMode(this))
return;
// If we are animating in, hide to begin with
if (PageLoadAnimation != PageAnimation.None)
Visibility = Visibility.Collapsed;
// Listen out for the page loading
Loaded += BasePage_LoadedAsync;
}
#endregion
#region Animation Load / Unload
/// <summary>
/// Once the page is loaded, perform any required animation
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void BasePage_LoadedAsync(object sender, System.Windows.RoutedEventArgs e)
{
// If we are setup to animate out on load
if (ShouldAnimateOut)
// Animate out the page
await AnimateOutAsync();
// Otherwise...
else
// Animate the page in
await AnimateInAsync();
}
/// <summary>
/// Animates the page in
/// </summary>
/// <returns></returns>
public async Task AnimateInAsync()
{
// Make sure we have something to do
if (PageLoadAnimation == PageAnimation.None)
return;
switch (PageLoadAnimation)
{
case PageAnimation.SlideAndFadeInFromRight:
// Start the animation
await this.SlideAndFadeInAsync(AnimationSlideInDirection.Right, false, SlideSeconds,
size: (int)Application.Current.MainWindow.Width);
break;
}
}
/// <summary>
/// Animates the page out
/// </summary>
/// <returns></returns>
public async Task AnimateOutAsync()
{
// Make sure we have something to do
if (PageUnloadAnimation == PageAnimation.None)
return;
switch (PageUnloadAnimation)
{
case PageAnimation.SlideAndFadeOutToLeft:
// Start the animation
await this.SlideAndFadeOutAsync(AnimationSlideInDirection.Left, SlideSeconds);
break;
}
}
#endregion
/// <summary>
/// Fired when the view model changes
/// </summary>
protected virtual void OnViewModelChanged()
{
}
}
/// <summary>
/// A base page with added ViewModel support
/// </summary>
public class BasePage<TVm> : BasePage
where TVm : BaseViewModel, new()
{
#region Public Properties
/// <summary>
/// The view model associated with this page
/// </summary>
public TVm ViewModel
{
get => (TVm)ViewModelObject;
set => ViewModelObject = value;
}
#endregion
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public BasePage() : base()
{
// If in design time mode...
if (DesignerProperties.GetIsInDesignMode(this))
// Just use a new instance of the VM
ViewModel = new TVm();
else
// Create a default view model
ViewModel = Framework.Service<TVm>() ?? new TVm();
}
/// <summary>
/// Constructor with specific view model
/// </summary>
/// <param name="specificViewModel">The specific view model to use, if any</param>
public BasePage(TVm specificViewModel = null) : base()
{
// Set specific view model
if (specificViewModel != null)
ViewModel = specificViewModel;
else
{
// If in design time mode...
if (DesignerProperties.GetIsInDesignMode(this))
// Just use a new instance of the VM
ViewModel = new TVm();
else
{
// Create a default view model
ViewModel = Framework.Service<TVm>() ?? new TVm();
}
}
}
#endregion
}
My Base View Model:
/// <summary>
/// A base view model that fires Property Changed events as needed
/// </summary>
public class BaseViewModel : INotifyPropertyChanged
{
#region Protected Members
/// <summary>
/// A global lock for property checks so prevent locking on different instances of expressions.
/// Considering how fast this check will always be it isn't an issue to globally lock all callers.
/// </summary>
protected object mPropertyValueCheckLock = new object();
#endregion
/// <summary>
/// The event that is fired when any child property changes its value
/// </summary>
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
/// <summary>
/// Call this to fire a <see cref="PropertyChanged"/> event
/// </summary>
/// <param name="name"></param>
public void OnPropertyChanged(string name)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#region Command Helpers
/// <summary>
/// Runs a command if the updating flag is not set.
/// If the flag is true (indicating the function is already running) then the action is not run.
/// If the flag is false (indicating no running function) then the action is run.
/// Once the action is finished if it was run, then the flag is reset to false
/// </summary>
/// <param name="updatingFlag">The boolean property flag defining if the command is already running</param>
/// <param name="action">The action to run if the command is not already running</param>
/// <returns></returns>
protected async Task RunCommandAsync(Expression<Func<bool>> updatingFlag, Func<Task> action)
{
// Lock to ensure single access to check
lock (mPropertyValueCheckLock)
{
// Check if the flag property is true (meaning the function is already running)
if (updatingFlag.GetPropertyValue())
return;
// Set the property flag to true to indicate we are running
updatingFlag.SetPropertyValue(true);
}
try
{
// Run the passed in action
await action();
}
finally
{
// Set the property flag back to false now it's finished
updatingFlag.SetPropertyValue(false);
}
}
/// <summary>
/// Runs a command if the updating flag is not set.
/// If the flag is true (indicating the function is already running) then the action is not run.
/// If the flag is false (indicating no running function) then the action is run.
/// Once the action is finished if it was run, then the flag is reset to false
/// </summary>
/// <param name="updatingFlag">The boolean property flag defining if the command is already running</param>
/// <param name="action">The action to run if the command is not already running</param>
/// <typeparam name="T">The type the action returns</typeparam>
/// <returns></returns>
protected async Task<T> RunCommandAsync<T>(Expression<Func<bool>> updatingFlag, Func<Task<T>> action, T defaultValue = default(T))
{
// Lock to ensure single access to check
lock (mPropertyValueCheckLock)
{
// Check if the flag property is true (meaning the function is already running)
if (updatingFlag.GetPropertyValue())
return defaultValue;
// Set the property flag to true to indicate we are running
updatingFlag.SetPropertyValue(true);
}
try
{
// Run the passed in action
return await action();
}
finally
{
// Set the property flag back to false now it's finished
updatingFlag.SetPropertyValue(false);
}
}
#endregion
}
View Model Constructor:
View Model
I'm trying to implement WeakEventManager in PCL using the Reactive library.
So the point is that it keeps a weak reference for the subscriber and each time event fires - it gets the delegate of the subscriber and fires that, but if he couldn't get an object from weak reference, then it disposes the link to the delegate.
The problem is that after a short amount of time, the weak reference returns null (but the subscriber is still alive) and after that disposing of the link is being performed. So my question is why this is happening and how to fix that?
Here how it looks like: (Look at the note in the code)
private static IDisposable InternalSubscribeWeakly<TEventPattern, TEvent>(this IObservable<TEventPattern> observable, TEvent Weak_onNext, Action<TEvent, TEventPattern> onNext)
where TEvent : class
{
if (onNext.Target != null)
throw new ArgumentException("onNext must refer to a static method, or else the subscription will still hold a strong reference to target");
// Is the delegate alive?
var Weak_onNextReferance = new WeakReference(Weak_onNext);
//This is a link for that event, so if you want to unsubscribe from event you have to dispose this object
IDisposable subscription = null;
subscription = observable.Subscribe(item =>
{
//So the library keeps weak reference for this object and each time event fired it tries to get that object
var current_onNext = Weak_onNextReferance.Target as TEvent;
if (current_onNext != null)
{
//If the object was found, it uses the delegate that subscriber provided and fires the event
onNext(current_onNext, item);
}
else
{
//If the object is not found it disposes the link
//NOTE: For some reasons after a short amount of time it can't get a reference from the WeakReference, however the subscriber is still alive
subscription.Dispose();
}
});
return subscription;
}
And then here is how I'm subscribing using that manager:
private void NoLeakWindow_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= NoLeakWindow_Loaded;
this.ObserveOn<Window, ElapsedEventHandler, ElapsedEventArgs>(h => (o, s) => h(o, s),
r => MainWindow.EPublisher.EventTimer.Elapsed += r,
r => MainWindow.EPublisher.EventTimer.Elapsed -= r)
.SubscribeWeakly(EventTimer_Elapsed);
this.ObserveOn<Window, ElapsedEventHandler, ElapsedEventArgs>(
h => (o, s) => h(o, s),
r => MainWindow.EPublisher.EventTimer.Elapsed += r,
r => MainWindow.EPublisher.EventTimer.Elapsed -= r)
.SubscribeWeakly(EventTimer_Elapsed2);
}
private void EventTimer_Elapsed(EventPattern<ElapsedEventArgs> e)
{
MessageBox.Show("EventTimer_Elapsed By Timer");
}
private void EventTimer_Elapsed2(EventPattern<ElapsedEventArgs> e)
{
MessageBox.Show("EventTimer2_Elapsed2 By Timer2");
}
And my event publisher:
public class EventPublisher
{
public Timer EventTimer = new Timer(3000);
public Timer EventTimer2 = new Timer(2700);
public event EventHandler<EventArgs> TimeElapsed;
public EventPublisher()
{
EventTimer.Start();
EventTimer2.Start();
}
}
And finally the WeakEventManager class full code:
/// <summary>
/// Static Class that holds the extension methods to handle events using weak references.
/// This way we do not need to worry about unregistered the event handler.
/// </summary>
public static class WeakEventManager
{
/// <summary>
/// Creates Observable for subscribing to it's event
/// </summary>
/// <typeparam name="T">The type of the T.</typeparam>
/// <typeparam name="TDelegate">The type of the T delegate.</typeparam>
/// <typeparam name="TArgs">The type of the T args.</typeparam>
/// <param name="subscriber">The subscriber</param>
/// <param name="converter">The converter.</param>
/// <param name="add">The add</param>
/// <param name="remove">The remove</param>
/// <returns>IObservable</returns>
public static IObservable<EventPattern<TArgs>> ObserveOn<T, TDelegate, TArgs>(this T subscriber, Func<EventHandler<TArgs>, TDelegate> converter, Action<TDelegate> add, Action<TDelegate> remove)
where T : class
{
return Observable.FromEventPattern<TDelegate, TArgs>(
converter,
add,
remove);
}
/// <summary>
/// Subscribe's action to event
/// </summary>
/// <typeparam name="T">The type of the T.</typeparam>
/// <param name="observable">The observable</param>
/// <param name="onNext">The action</param>
/// <returns></returns>
public static IDisposable SubscribeWeakly<T>(this IObservable<T> observable, Action<T> onNext) where T : class
{
IDisposable Result = null;
WeakSubscriberHelper<T> SubscriptionHelper = new WeakSubscriberHelper<T>(observable, ref Result, onNext);
return Result;
}
private class WeakSubscriberHelper<T> where T : class
{
public WeakSubscriberHelper(IObservable<T> observable, ref IDisposable Result, Action<T> eventAction)
{
Result = observable.InternalSubscribeWeakly(eventAction, WeakSubscriberHelper<T>.StaticEventHandler);
}
public static void StaticEventHandler(Action<T> subscriber, T item)
{
subscriber(item);
}
}
private static IDisposable InternalSubscribeWeakly<TEventPattern, TEvent>(this IObservable<TEventPattern> observable, TEvent Weak_onNext, Action<TEvent, TEventPattern> onNext)
where TEvent : class
{
if (onNext.Target != null)
throw new ArgumentException("onNext must refer to a static method, or else the subscription will still hold a strong reference to target");
// Is the delegate alive?
var Weak_onNextReferance = new WeakReference(Weak_onNext);
//This is a link for that event, so if you want to unsubscribe from event you have to dispose this object
IDisposable subscription = null;
subscription = observable.Subscribe(item =>
{
//So the library keeps weak reference for this object and each time event fired it tries to get that object
var current_onNext = Weak_onNextReferance.Target as TEvent;
if (current_onNext != null)
{
//If the object was found, it uses the delegate that subscriber provided and fires the event
onNext(current_onNext, item);
}
else
{
//If the object is not found it disposes the link
//NOTE: For some reasons after a short amount of time it can't get a reference from the WeakReference, however the subscriber is still alive
subscription.Dispose();
}
});
return subscription;
}
public static IDisposable SubscribeWeakly<T, TWeakClass>(this IObservable<T> observable, TWeakClass WeakClass, Action<T> onNext) where T : class where TWeakClass : class
{
IDisposable Result = null;
WeakClassSubscriberHelper<T> SubscriptionHelper = new WeakClassSubscriberHelper<T>(observable, WeakClass, ref Result, onNext);
return Result;
}
private class WeakClassSubscriberHelper<T> where T : class
{
public WeakClassSubscriberHelper(IObservable<T> observable, object WeakClass, ref IDisposable Result, Action<T> eventAction)
{
Result = observable.InternalSubscribeWeaklyToClass(eventAction, WeakClass, WeakClassSubscriberHelper<T>.StaticEventHandler);
}
public static void StaticEventHandler(Action<T> subscriber, T item)
{
subscriber(item);
}
}
private static IDisposable InternalSubscribeWeaklyToClass<TEventPattern, TEvent, TClass>(this IObservable<TEventPattern> observable, TEvent Weak_onNext, TClass WeakClass, Action<TEvent, TEventPattern> onNext)
where TEvent : class where TClass : class
{
if (onNext.Target != null)
throw new ArgumentException("onNext must refer to a static method, or else the subscription will still hold a strong reference to target");
// The class instance could live in a differnt
// place than the eventhandler. If either one is null,
// terminate the subscribtion.
var WeakClassReference = new WeakReference(WeakClass);
var Weak_onNextReferance = new WeakReference(Weak_onNext);
IDisposable subscription = null;
subscription = observable.Subscribe(item =>
{
var currentWeakClass = WeakClassReference.Target as TClass;
var current_onNext = Weak_onNextReferance.Target as TEvent;
if (currentWeakClass != null && current_onNext != null)
{
onNext(current_onNext, item);
}
else
{
subscription.Dispose();
}
});
return subscription;
}
}
I came up with my own solution. So basically I merged both of those implementations into one. So my solution is pretty straightforward and I think there are many ways to improve this.
So here is the solution:
/// <summary>
/// PclWeakEventManager base class
/// </summary>
/// <typeparam name="TEventSource">The type of the event source.</typeparam>
/// <typeparam name="TEventHandler">The type of the event handler.</typeparam>
/// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
public class PclWeakEventManager<TEventSource, TEventHandler, TEventArgs>
{
static readonly object StaticSource = new object();
/// <summary>
/// Mapping between the target of the delegate (for example a Button) and the handler (EventHandler).
/// Windows Phone needs this, otherwise the event handler gets garbage collected.
/// </summary>
ConditionalWeakTable<object, List<Delegate>> targetToEventHandler = new ConditionalWeakTable<object, List<Delegate>>();
/// <summary>
/// Mapping from the source of the event to the list of handlers. This is a CWT to ensure it does not leak the source of the event.
/// </summary>
ConditionalWeakTable<object, WeakHandlerList> sourceToWeakHandlers = new ConditionalWeakTable<object, WeakHandlerList>();
/// <summary>
/// Singleton instance
/// </summary>
static Lazy<PclWeakEventManager<TEventSource, TEventHandler, TEventArgs>> current =
new Lazy<PclWeakEventManager<TEventSource, TEventHandler, TEventArgs>>(() => new PclWeakEventManager<TEventSource, TEventHandler, TEventArgs>());
/// <summary>
/// Get the singleton instance
/// </summary>
static PclWeakEventManager<TEventSource, TEventHandler, TEventArgs> Current
{
get { return current.Value; }
}
/// <summary>
/// Initializes a new instance of the <see cref="PclWeakEventManager{TEventSource, TEventHandler, TEventArgs}"/> class.
/// Protected to disallow instances of this class and force a subclass.
/// </summary>
protected PclWeakEventManager()
{
}
#region Public static methods
/// <summary>
/// Adds a weak reference to the handler and associates it with the source.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="handler">The handler.</param>
public static void AddHandler(TEventSource source, TEventHandler handler, Func<EventHandler<TEventArgs>, TEventHandler> converter, Action<TEventHandler> add, Action<TEventHandler> remove)
{
if (source == null) throw new ArgumentNullException("source");
if (handler == null) throw new ArgumentNullException("handler");
if (!typeof(TEventHandler).GetTypeInfo().IsSubclassOf(typeof(Delegate)))
{
throw new ArgumentException("Handler must be Delegate type");
}
var observable = Observable.FromEventPattern(converter, add, remove);
Current.PrivateAddHandler(source, observable, handler);
}
/// <summary>
/// Removes the association between the source and the handler.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="handler">The handler.</param>
public static void RemoveHandler(TEventSource source, TEventHandler handler)
{
if (source == null) throw new ArgumentNullException("source");
if (handler == null) throw new ArgumentNullException("handler");
if (!typeof(TEventHandler).GetTypeInfo().IsSubclassOf(typeof(Delegate)))
{
throw new ArgumentException("handler must be Delegate type");
}
Current.PrivateRemoveHandler(source, handler);
}
#endregion
#region Event delivering
/// <summary>
/// Delivers the event to the handlers registered for the source.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="args">The <see cref="TEventArgs"/> instance containing the event data.</param>
public static void DeliverEvent(TEventSource sender, TEventArgs args)
{
Current.PrivateDeliverEvent(sender, args);
}
/// <summary>
/// Override this method to attach to an event.
/// </summary>
/// <param name="source">The source.</param>
protected virtual void StartListening(TEventSource source, IObservable<EventPattern<TEventArgs>> observable, TEventHandler handler)
{
//The handler - proxy should be static, otherwise it will create a strong reference
InternalSubscribeWeakly(observable, source, handler, DeliverEvent);
}
/// <summary>
/// Override this method to detach from an event.
/// </summary>
/// <param name="source">The source.</param>
protected virtual void StopListening(object source)
{
//This method is for future usage
}
/// <summary>
/// Fire the event handler
/// </summary>
/// <param name="sender">Event publisher</param>
/// <param name="args">Event arguments</param>
void PrivateDeliverEvent(object sender, TEventArgs args)
{
object source = sender != null ? sender : StaticSource;
var weakHandlers = default(WeakHandlerList);
bool hasStaleEntries = false;
if (this.sourceToWeakHandlers.TryGetValue(source, out weakHandlers))
{
using (weakHandlers.DeliverActive())
{
hasStaleEntries = weakHandlers.DeliverEvent(source, args);
}
}
if (hasStaleEntries)
{
this.Purge(source);
}
}
#endregion
#region Add weak handler methods
/// <summary>
/// Adds the event handler to WeakTables
/// </summary>
/// <param name="source">The event publisher source</param>
/// <param name="observable">Observable object</param>
/// <param name="handler">The event handler. This is used to create a weak reference</param>
void PrivateAddHandler(TEventSource source, IObservable<EventPattern<TEventArgs>> observable, TEventHandler handler)
{
this.AddWeakHandler(source, observable, handler);
this.AddTargetHandler(handler);
}
/// <summary>
/// Add a weak handler
/// </summary>
/// <param name="source">The event publisher source</param>
/// <param name="observable">Observable object</param>
/// <param name="handler">The event handler. This is used to create a weak reference</param>
void AddWeakHandler(TEventSource source, IObservable<EventPattern<TEventArgs>> observable, TEventHandler handler)
{
WeakHandlerList weakHandlers;
//If for the event source table wasn't created, then it creates a new
if (this.sourceToWeakHandlers.TryGetValue(source, out weakHandlers))
{
// clone list if we are currently delivering an event
if (weakHandlers.IsDeliverActive)
{
weakHandlers = weakHandlers.Clone();
this.sourceToWeakHandlers.Remove(source);
this.sourceToWeakHandlers.Add(source, weakHandlers);
}
weakHandlers.AddWeakHandler(source, handler);
}
else
{
weakHandlers = new WeakHandlerList();
weakHandlers.AddWeakHandler(source, handler);
this.sourceToWeakHandlers.Add(source, weakHandlers);
this.StartListening(source, observable, handler);
}
this.Purge(source);
}
/// <summary>
/// Subscribe to the event
/// </summary>
/// <param name="observable">Observable object</param>
/// <param name="source">The event publisher source</param>
/// <param name="handler">The event handler. This is used to create a weak reference</param>
/// <param name="onNext">Event handler delegate</param>
private static void InternalSubscribeWeakly(IObservable<EventPattern<TEventArgs>> observable, TEventSource source, TEventHandler handler, Action<TEventSource, TEventArgs> onNext)
{
if (onNext.Target != null)
throw new ArgumentException("onNext must refer to a static method, or else the subscription will still hold a strong reference to target");
// Is the delegate alive?
var Weak_onNextReferance = new WeakReference(handler);
//This is a link for that event, so if you want to unsubscribe from event you have to dispose this object
IDisposable subscription = null;
subscription = observable.Subscribe(item =>
{
//Purge handler if the subscriber is not alive
Current.Purge(source);
//So the library keeps weak reference for this object and each time event fired it tries to get that object
var current_onNext = Weak_onNextReferance.Target;
if (current_onNext != null)
{
//If the object was found, it uses the delegate that subscriber provided and fires the event
onNext((TEventSource)item.Sender, item.EventArgs);
}
else
{
//If the object is not found it disposes the link
subscription.Dispose();
Current.sourceToWeakHandlers.Remove(source);
}
});
}
/// <summary>
/// Adds the event handler to the weak event handlers list
/// </summary>
/// <param name="handler">The event handler. This is used to create a weak reference</param>
void AddTargetHandler(TEventHandler handler)
{
var #delegate = handler as Delegate;
object key = #delegate.Target ?? StaticSource;
List<Delegate> delegates;
if (this.targetToEventHandler.TryGetValue(key, out delegates))
{
delegates.Add(#delegate);
}
else
{
delegates = new List<Delegate>();
delegates.Add(#delegate);
this.targetToEventHandler.Add(key, delegates);
}
}
#endregion
#region Remove weak handler methods
/// <summary>
/// Remove the event handler
/// </summary>
/// <param name="source">Event source object</param>
/// <param name="handler">The event handler</param>
void PrivateRemoveHandler(TEventSource source, TEventHandler handler)
{
this.RemoveWeakHandler(source, handler);
this.RemoveTargetHandler(handler);
}
/// <summary>
/// Remove the event handler
/// </summary>
/// <param name="source">Event source object</param>
/// <param name="handler">The event handler</param>
void RemoveWeakHandler(TEventSource source, TEventHandler handler)
{
var weakHandlers = default(WeakHandlerList);
if (this.sourceToWeakHandlers.TryGetValue(source, out weakHandlers))
{
// clone list if we are currently delivering an event
if (weakHandlers.IsDeliverActive)
{
weakHandlers = weakHandlers.Clone();
this.sourceToWeakHandlers.Remove(source);
this.sourceToWeakHandlers.Add(source, weakHandlers);
}
if (weakHandlers.RemoveWeakHandler(source, handler) && weakHandlers.Count == 0)
{
this.sourceToWeakHandlers.Remove(source);
this.StopListening(source);
}
}
}
/// <summary>
/// Remove the handler from weaktable
/// </summary>
/// <param name="handler">The event handler</param>
void RemoveTargetHandler(TEventHandler handler)
{
var #delegate = handler as Delegate;
object key = #delegate.Target ?? StaticSource;
var delegates = default(List<Delegate>);
if (this.targetToEventHandler.TryGetValue(key, out delegates))
{
delegates.Remove(#delegate);
if (delegates.Count == 0)
{
this.targetToEventHandler.Remove(key);
}
}
}
/// <summary>
/// Remove dead handlers
/// </summary>
/// <param name="source">Source object</param>
void Purge(object source)
{
var weakHandlers = default(WeakHandlerList);
if (this.sourceToWeakHandlers.TryGetValue(source, out weakHandlers))
{
if (weakHandlers.IsDeliverActive)
{
weakHandlers = weakHandlers.Clone();
this.sourceToWeakHandlers.Remove(source);
this.sourceToWeakHandlers.Add(source, weakHandlers);
}
else
{
weakHandlers.Purge();
}
}
}
#endregion
#region WeakHandler table helper classes
/// <summary>
/// Weak handler helper class
/// </summary>
internal class WeakHandler
{
WeakReference source;
WeakReference originalHandler;
public bool IsActive
{
get { return this.source != null && this.source.IsAlive && this.originalHandler != null && this.originalHandler.IsAlive; }
}
public TEventHandler Handler
{
get
{
if (this.originalHandler == null)
{
return default(TEventHandler);
}
else
{
return (TEventHandler)this.originalHandler.Target;
}
}
}
public WeakHandler(object source, TEventHandler originalHandler)
{
this.source = new WeakReference(source);
this.originalHandler = new WeakReference(originalHandler);
}
/// <summary>
/// Checks if provided handler is the same
/// </summary>
/// <param name="source"></param>
/// <param name="handler"></param>
/// <returns>True if source.Target is equals to source, otherwise false</returns>
public bool Matches(object source, TEventHandler handler)
{
return this.source != null &&
object.ReferenceEquals(this.source.Target, source) &&
this.originalHandler != null;
}
}
/// <summary>
/// Weak event handler manager
/// </summary>
internal class WeakHandlerList
{
int deliveries = 0;
List<WeakHandler> handlers;
public WeakHandlerList()
{
handlers = new List<WeakHandler>();
}
/// <summary>
/// Adds new weak event handler to the list
/// </summary>
/// <param name="source">The event source</param>
/// <param name="handler">The event handler</param>
public void AddWeakHandler(TEventSource source, TEventHandler handler)
{
WeakHandler handlerSink = new WeakHandler(source, handler);
handlers.Add(handlerSink);
}
/// <summary>
/// Remove weak handler from the list
/// </summary>
/// <param name="source">The event source</param>
/// <param name="handler">The event handler</param>
/// <returns>True if the handler was removed, otherwise false</returns>
public bool RemoveWeakHandler(TEventSource source, TEventHandler handler)
{
foreach (var weakHandler in handlers)
{
if (weakHandler.Matches(source, handler))
{
return handlers.Remove(weakHandler);
}
}
return false;
}
/// <summary>
/// Clones the list
/// </summary>
/// <returns></returns>
public WeakHandlerList Clone()
{
WeakHandlerList newList = new WeakHandlerList();
newList.handlers.AddRange(this.handlers.Where(h => h.IsActive));
return newList;
}
/// <summary>
/// Items count
/// </summary>
public int Count
{
get { return this.handlers.Count; }
}
/// <summary>
/// True if any of the events are still in delivering process
/// </summary>
public bool IsDeliverActive
{
get { return this.deliveries > 0; }
}
public IDisposable DeliverActive()
{
Interlocked.Increment(ref this.deliveries);
return Disposable.Create(() => Interlocked.Decrement(ref this.deliveries));
}
/// <summary>
/// Fire the handler
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
/// <returns></returns>
public virtual bool DeliverEvent(object sender, TEventArgs args)
{
bool hasStaleEntries = false;
foreach (var handler in handlers)
{
if (handler.IsActive)
{
var #delegate = handler.Handler as Delegate;
#delegate.DynamicInvoke(sender, args);
}
else
{
hasStaleEntries = true;
}
}
return hasStaleEntries;
}
/// <summary>
/// Removes dead handlers
/// </summary>
public void Purge()
{
for (int i = handlers.Count - 1; i >= 0; i--)
{
if (!handlers[i].IsActive)
{
handlers.RemoveAt(i);
}
}
}
}
#endregion
}
I have read many posts on binding an ObservableCollection to a ListView from people with similar issues; however, I haven't found a solution for my usecase, yet.
In the following test application, I have a simple ListView and a Button. On startup, the ListView is initialized, i.e. 2 columns and 30 rows with values from 0-29 are created. Half of the 30 rows (i.e. 15 rows) are visible. To see the remaining 15 Items I have to scroll down using the Scrollbar.
The Button is binded to an Asynchronous Command using the AsynchronousCommand Class from this article. When the button is clicked (see Start_Click), random numbers are written into those 30 rows of the ListView. This is done in an endless loop of a separate Thread (see AsynchronousCommand).
Now, when I click on the button, I would expect all ListView Items to change to random values instantaneously. However, this is not what's happening. Instead, only those Items that are not visible (i.e. the 15 Items beyond the ScrollBar) are changing their values. Sometimes, none of the Items changes its value.
Here's the XAML:
<Window x:Class="ListViewTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="614">
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="38*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<ListView HorizontalAlignment="Left" ItemsSource="{Binding MyList}" Height="261" Margin="28,24,0,0" VerticalAlignment="Top" Width="454" Grid.ColumnSpan="2">
<ListView.View>
<GridView>
<GridViewColumn Width="100" Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Width="325" Header="Data" DisplayMemberBinding="{Binding Data}" />
</GridView>
</ListView.View>
</ListView>
<Button Content="Start" Command="{Binding StartCommand}" Grid.Column="1" HorizontalAlignment="Left" Margin="21.043,42,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
This is my Code (View's CodeBehind, ViewModel, Controller Logic, and Model):
/// <summary>
/// This is the CodeBehind of my View
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
LayoutRoot.DataContext = new ViewModel();
}
}
/// <summary>
/// This is my ViewModel
/// </summary>
public class ViewModel : NotifyPropertyChanged
{
private ObservableCollection<Document> _myList;
private Logic _logic;
private AsynchronousCommand _startCommand;
public ViewModel()
{
_myList = new ObservableCollection<Document>();
_logic = new Logic(this);
_startCommand = new AsynchronousCommand(_logic.Start_Click, true);
}
public ObservableCollection<Document> MyList
{
get { return _myList; }
set
{
if (_myList != value)
{
_myList = value;
RaisePropertyChangedEvent("MyList");
}
}
}
public AsynchronousCommand StartCommand
{
get
{
return _startCommand;
}
set
{
_startCommand = value;
}
}
}
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// This is my Controller
/// </summary>
public class Logic
{
private ViewModel _viewModel;
private Random _rnd;
public Logic(ViewModel vm)
{
_viewModel = vm;
_rnd = new Random();
for (int i = 0; i < 30; i++)
{
Document newDocument = new Document("Name " + i.ToString(), "Data " + i.ToString());
_viewModel.MyList.Add(newDocument);
}
}
public void Start_Click(object obj)
{
while (true)
{
int idx = _rnd.Next(0, 29);
_viewModel.StartCommand.ReportProgress(() =>
{
_viewModel.MyList[idx].Name = "New Name";
_viewModel.MyList[idx].Data = "New Data";
});
System.Threading.Thread.Sleep(20);
}
}
}
/// <summary>
/// This is my Model
/// </summary>
public class Document
{
public string Name { get; set; }
public string Data { get; set; }
public Document(string name, string data)
{
Name = name;
Data = data;
}
}
And this is the Code for my AsynchronousCommand, taken from Dave Kerr's article on CodeProject:
/// <summary>
/// The ViewModelCommand class - an ICommand that can fire a function.
/// </summary>
public class Command : ICommand
{
/// <summary>
/// Initializes a new instance of the <see cref="Command"/> class.
/// </summary>
/// <param name="action">The action.</param>
/// <param name="canExecute">if set to <c>true</c> [can execute].</param>
public Command(Action action, bool canExecute = true)
{
// Set the action.
this.action = action;
this.canExecute = canExecute;
}
/// <summary>
/// Initializes a new instance of the <see cref="Command"/> class.
/// </summary>
/// <param name="parameterizedAction">The parameterized action.</param>
/// <param name="canExecute">if set to <c>true</c> [can execute].</param>
public Command(Action<object> parameterizedAction, bool canExecute = true)
{
// Set the action.
this.parameterizedAction = parameterizedAction;
this.canExecute = canExecute;
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="param">The param.</param>
public virtual void DoExecute(object param)
{
// Invoke the executing command, allowing the command to be cancelled.
CancelCommandEventArgs args = new CancelCommandEventArgs() { Parameter = param, Cancel = false };
InvokeExecuting(args);
// If the event has been cancelled, bail now.
if (args.Cancel)
return;
// Call the action or the parameterized action, whichever has been set.
InvokeAction(param);
// Call the executed function.
InvokeExecuted(new CommandEventArgs() { Parameter = param });
}
protected void InvokeAction(object param)
{
Action theAction = action;
Action<object> theParameterizedAction = parameterizedAction;
if (theAction != null)
theAction();
else if (theParameterizedAction != null)
theParameterizedAction(param);
}
protected void InvokeExecuted(CommandEventArgs args)
{
CommandEventHandler executed = Executed;
// Call the executed event.
if (executed != null)
executed(this, args);
}
protected void InvokeExecuting(CancelCommandEventArgs args)
{
CancelCommandEventHandler executing = Executing;
// Call the executed event.
if (executing != null)
executing(this, args);
}
/// <summary>
/// The action (or parameterized action) that will be called when the command is invoked.
/// </summary>
protected Action action = null;
protected Action<object> parameterizedAction = null;
/// <summary>
/// Bool indicating whether the command can execute.
/// </summary>
private bool canExecute = false;
/// <summary>
/// Gets or sets a value indicating whether this instance can execute.
/// </summary>
/// <value>
/// <c>true</c> if this instance can execute; otherwise, <c>false</c>.
/// </value>
public bool CanExecute
{
get { return canExecute; }
set
{
if (canExecute != value)
{
canExecute = value;
EventHandler canExecuteChanged = CanExecuteChanged;
if (canExecuteChanged != null)
canExecuteChanged(this, EventArgs.Empty);
}
}
}
#region ICommand Members
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
///
bool ICommand.CanExecute(object parameter)
{
return canExecute;
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
void ICommand.Execute(object parameter)
{
this.DoExecute(parameter);
}
#endregion
/// <summary>
/// Occurs when can execute is changed.
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// Occurs when the command is about to execute.
/// </summary>
public event CancelCommandEventHandler Executing;
/// <summary>
/// Occurs when the command executed.
/// </summary>
public event CommandEventHandler Executed;
}
/// <summary>
/// The CommandEventHandler delegate.
/// </summary>
public delegate void CommandEventHandler(object sender, CommandEventArgs args);
/// <summary>
/// The CancelCommandEvent delegate.
/// </summary>
public delegate void CancelCommandEventHandler(object sender, CancelCommandEventArgs args);
/// <summary>
/// CommandEventArgs - simply holds the command parameter.
/// </summary>
public class CommandEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the parameter.
/// </summary>
/// <value>The parameter.</value>
public object Parameter { get; set; }
}
/// <summary>
/// CancelCommandEventArgs - just like above but allows the event to
/// be cancelled.
/// </summary>
public class CancelCommandEventArgs : CommandEventArgs
{
/// <summary>
/// Gets or sets a value indicating whether this <see cref="CancelCommandEventArgs"/> command should be cancelled.
/// </summary>
/// <value><c>true</c> if cancel; otherwise, <c>false</c>.</value>
public bool Cancel { get; set; }
}
/// <summary>
/// The AsynchronousCommand is a Command that runs on a thread from the thread pool.
/// </summary>
public class AsynchronousCommand : Command, INotifyPropertyChanged
{
/// <summary>
/// Initializes a new instance of the <see cref="AsynchronousCommand"/> class.
/// </summary>
/// <param name="action">The action.</param>
/// <param name="canExecute">if set to <c>true</c> the command can execute.</param>
public AsynchronousCommand(Action action, bool canExecute = true)
: base(action, canExecute)
{
// Initialise the command.
Initialise();
}
/// <summary>
/// Initializes a new instance of the <see cref="AsynchronousCommand"/> class.
/// </summary>
/// <param name="parameterizedAction">The parameterized action.</param>
/// <param name="canExecute">if set to <c>true</c> [can execute].</param>
public AsynchronousCommand(Action<object> parameterizedAction, bool canExecute = true)
: base(parameterizedAction, canExecute)
{
// Initialise the command.
Initialise();
}
/// <summary>
/// Initialises this instance.
/// </summary>
private void Initialise()
{
// Construct the cancel command.
cancelCommand = new Command(
() =>
{
// Set the Is Cancellation Requested flag.
IsCancellationRequested = true;
}, true);
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="param">The param.</param>
public override void DoExecute(object param)
{
// If we are already executing, do not continue.
if (IsExecuting)
return;
// Invoke the executing command, allowing the command to be cancelled.
CancelCommandEventArgs args = new CancelCommandEventArgs() { Parameter = param, Cancel = false };
InvokeExecuting(args);
// If the event has been cancelled, bail now.
if (args.Cancel)
return;
// We are executing.
IsExecuting = true;
// Store the calling dispatcher.
callingDispatcher = Dispatcher.CurrentDispatcher;
// Run the action on a new thread from the thread pool (this will therefore work in SL and WP7 as well).
ThreadPool.QueueUserWorkItem(
(state) =>
{
// Invoke the action.
InvokeAction(param);
// Fire the executed event and set the executing state.
ReportProgress(
() =>
{
// We are no longer executing.
IsExecuting = false;
// If we were cancelled, invoke the cancelled event - otherwise invoke executed.
if (IsCancellationRequested)
InvokeCancelled(new CommandEventArgs() { Parameter = param });
else
InvokeExecuted(new CommandEventArgs() { Parameter = param });
// We are no longer requesting cancellation.
IsCancellationRequested = false;
}
);
}
);
}
/// <summary>
/// Raises the property changed event.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
private void NotifyPropertyChanged(string propertyName)
{
// Store the event handler - in case it changes between
// the line to check it and the line to fire it.
PropertyChangedEventHandler propertyChanged = PropertyChanged;
// If the event has been subscribed to, fire it.
if (propertyChanged != null)
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Reports progress on the thread which invoked the command.
/// </summary>
/// <param name="action">The action.</param>
public void ReportProgress(Action action)
{
if (IsExecuting)
{
if (callingDispatcher.CheckAccess())
action();
else
callingDispatcher.BeginInvoke(((Action)(() => { action(); })));
}
}
/// <summary>
/// Cancels the command if requested.
/// </summary>
/// <returns>True if the command has been cancelled and we must return.</returns>
public bool CancelIfRequested()
{
// If we haven't requested cancellation, there's nothing to do.
if (IsCancellationRequested == false)
return false;
// We're done.
return true;
}
/// <summary>
/// Invokes the cancelled event.
/// </summary>
/// <param name="args">The <see cref="Apex.MVVM.CommandEventArgs"/> instance containing the event data.</param>
protected void InvokeCancelled(CommandEventArgs args)
{
CommandEventHandler cancelled = Cancelled;
// Call the cancelled event.
if (cancelled != null)
cancelled(this, args);
}
protected Dispatcher callingDispatcher;
/// <summary>
/// Flag indicating that the command is executing.
/// </summary>
private bool isExecuting = false;
/// <summary>
/// Flag indicated that cancellation has been requested.
/// </summary>
private bool isCancellationRequested;
/// <summary>
/// The cancel command.
/// </summary>
private Command cancelCommand;
/// <summary>
/// The property changed event.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Occurs when the command is cancelled.
/// </summary>
public event CommandEventHandler Cancelled;
/// <summary>
/// Gets or sets a value indicating whether this instance is executing.
/// </summary>
/// <value>
/// <c>true</c> if this instance is executing; otherwise, <c>false</c>.
/// </value>
public bool IsExecuting
{
get
{
return isExecuting;
}
set
{
if (isExecuting != value)
{
isExecuting = value;
NotifyPropertyChanged("IsExecuting");
}
}
}
/// <summary>
/// Gets or sets a value indicating whether this instance is cancellation requested.
/// </summary>
/// <value>
/// <c>true</c> if this instance is cancellation requested; otherwise, <c>false</c>.
/// </value>
public bool IsCancellationRequested
{
get
{
return isCancellationRequested;
}
set
{
if (isCancellationRequested != value)
{
isCancellationRequested = value;
NotifyPropertyChanged("IsCancellationRequested");
}
}
}
/// <summary>
/// Gets the cancel command.
/// </summary>
public Command CancelCommand
{
get { return cancelCommand; }
}
}
Your Document Class has to implement INotifyPropertyChanged Interface so the UI gets updated once your Properties change.
Why is it working when you scroll?
Because of Virtualization. Not visible entries have not been evualuated yet, so once they get evaluated they already receive the "New Value".
Here is your Document class that works:
/// <summary>
/// This is my Model
/// </summary>
public class Document : INotifyPropertyChanged
{
private string _name;
private string _data;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public string Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged();
}
}
public Document(string name, string data)
{
Name = name;
Data = data;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Let's say we have a control (a Button). This button is just thrown inside a grid in the most laziest way possible. The Command property is already binded, and it pops a MessageBox. I want to write a second command for my Button, let's say when Mouse enters it's surface (MouseEnter). But I want to do it this way
<Button Command="{Binding MyClick}" MySecondCommand= "{Binding MouseHoovering}"
Margin="34,39,0,0">
I know that ICommandSource forces me to implement Command property. As it is already implemented by ButtonBase and exposed in my example. What shoud I do to make "MySecondCommand" possible ? Just to declare it as a dependency property is not enough! :)
add your project --> CommandBehaviorBinding.cs,CommandBehavior.cs
use like this
<Button Command="{Binding MyClickCommand}"
Margin="34,39,0,0" local:CommandBehavior.Event="MouseHoovering"
local:CommandBehavior.Command="{Binding MouseHooveringCommand}">
CommandBehaviorBinding.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Reflection;
using System.Windows;
namespace yournamespace
{
public class CommandBehaviorBinding : IDisposable
{
#region Properties
/// <summary>
/// Get the owner of the CommandBinding ex: a Button
/// This property can only be set from the BindEvent Method
/// </summary>
public DependencyObject Owner { get; private set; }
/// <summary>
/// The event name to hook up to
/// This property can only be set from the BindEvent Method
/// </summary>
public string EventName { get; private set; }
/// <summary>
/// The event info of the event
/// </summary>
public EventInfo Event { get; private set; }
/// <summary>
/// Gets the EventHandler for the binding with the event
/// </summary>
public Delegate EventHandler { get; private set; }
#region Execution
//stores the strategy of how to execute the event handler
IExecutionStrategy strategy;
/// <summary>
/// Gets or sets a CommandParameter
/// </summary>
public object CommandParameter { get; set; }
ICommand command;
/// <summary>
/// The command to execute when the specified event is raised
/// </summary>
public ICommand Command
{
get { return command; }
set
{
command = value;
//set the execution strategy to execute the command
strategy = new CommandExecutionStrategy { Behavior = this };
}
}
Action<object> action;
/// <summary>
/// Gets or sets the Action
/// </summary>
public Action<object> Action
{
get { return action; }
set
{
action = value;
// set the execution strategy to execute the action
strategy = new ActionExecutionStrategy { Behavior = this };
}
}
#endregion
#endregion
//Creates an EventHandler on runtime and registers that handler to the Event specified
public void BindEvent(DependencyObject owner, string eventName)
{
EventName = eventName;
Owner = owner;
Event = Owner.GetType().GetEvent(EventName, BindingFlags.Public | BindingFlags.Instance);
if (Event == null)
throw new InvalidOperationException(String.Format("Could not resolve event name {0}", EventName));
//Create an event handler for the event that will call the ExecuteCommand method
EventHandler = EventHandlerGenerator.CreateDelegate(
Event.EventHandlerType, typeof(CommandBehaviorBinding).GetMethod("Execute", BindingFlags.Public | BindingFlags.Instance), this);
//Register the handler to the Event
Event.AddEventHandler(Owner, EventHandler);
}
/// <summary>
/// Executes the strategy
/// </summary>
public void Execute()
{
strategy.Execute(CommandParameter);
}
#region IDisposable Members
bool disposed = false;
/// <summary>
/// Unregisters the EventHandler from the Event
/// </summary>
public void Dispose()
{
if (!disposed)
{
Event.RemoveEventHandler(Owner, EventHandler);
disposed = true;
}
}
#endregion
}
}
CommandBehavior.cs :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace yournamespace
{
public class CommandBehavior
{
#region Behavior
/// <summary>
/// Behavior Attached Dependency Property
/// </summary>
private static readonly DependencyProperty BehaviorProperty =
DependencyProperty.RegisterAttached("Behavior", typeof(CommandBehaviorBinding), typeof(CommandBehavior),
new FrameworkPropertyMetadata((CommandBehaviorBinding)null));
/// <summary>
/// Gets the Behavior property.
/// </summary>
private static CommandBehaviorBinding GetBehavior(DependencyObject d)
{
return (CommandBehaviorBinding)d.GetValue(BehaviorProperty);
}
/// <summary>
/// Sets the Behavior property.
/// </summary>
private static void SetBehavior(DependencyObject d, CommandBehaviorBinding value)
{
d.SetValue(BehaviorProperty, value);
}
#endregion
#region Command
/// <summary>
/// Command Attached Dependency Property
/// </summary>
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CommandBehavior),
new FrameworkPropertyMetadata((ICommand)null,
new PropertyChangedCallback(OnCommandChanged)));
/// <summary>
/// Gets the Command property.
/// </summary>
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
/// <summary>
/// Sets the Command property.
/// </summary>
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
/// <summary>
/// Handles changes to the Command property.
/// </summary>
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
binding.Command = (ICommand)e.NewValue;
}
#endregion
#region Action
/// <summary>
/// Action Attached Dependency Property
/// </summary>
public static readonly DependencyProperty ActionProperty =
DependencyProperty.RegisterAttached("Action", typeof(Action<object>), typeof(CommandBehavior),
new FrameworkPropertyMetadata((Action<object>)null,
new PropertyChangedCallback(OnActionChanged)));
/// <summary>
/// Gets the Action property.
/// </summary>
public static Action<object> GetAction(DependencyObject d)
{
return (Action<object>)d.GetValue(ActionProperty);
}
/// <summary>
/// Sets the Action property.
/// </summary>
public static void SetAction(DependencyObject d, Action<object> value)
{
d.SetValue(ActionProperty, value);
}
/// <summary>
/// Handles changes to the Action property.
/// </summary>
private static void OnActionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
binding.Action = (Action<object>)e.NewValue;
}
#endregion
#region CommandParameter
/// <summary>
/// CommandParameter Attached Dependency Property
/// </summary>
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CommandBehavior),
new FrameworkPropertyMetadata((object)null,
new PropertyChangedCallback(OnCommandParameterChanged)));
/// <summary>
/// Gets the CommandParameter property.
/// </summary>
public static object GetCommandParameter(DependencyObject d)
{
return (object)d.GetValue(CommandParameterProperty);
}
/// <summary>
/// Sets the CommandParameter property.
/// </summary>
public static void SetCommandParameter(DependencyObject d, object value)
{
d.SetValue(CommandParameterProperty, value);
}
/// <summary>
/// Handles changes to the CommandParameter property.
/// </summary>
private static void OnCommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
binding.CommandParameter = e.NewValue;
}
#endregion
#region Event
/// <summary>
/// Event Attached Dependency Property
/// </summary>
public static readonly DependencyProperty EventProperty =
DependencyProperty.RegisterAttached("Event", typeof(string), typeof(CommandBehavior),
new FrameworkPropertyMetadata((string)String.Empty,
new PropertyChangedCallback(OnEventChanged)));
/// <summary>
/// Gets the Event property. This dependency property
/// indicates ....
/// </summary>
public static string GetEvent(DependencyObject d)
{
return (string)d.GetValue(EventProperty);
}
/// <summary>
/// Sets the Event property. This dependency property
/// indicates ....
/// </summary>
public static void SetEvent(DependencyObject d, string value)
{
d.SetValue(EventProperty, value);
}
/// <summary>
/// Handles changes to the Event property.
/// </summary>
private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandBehaviorBinding binding = FetchOrCreateBinding(d);
//check if the Event is set. If yes we need to rebind the Command to the new event and unregister the old one
if (binding.Event != null && binding.Owner != null)
binding.Dispose();
//bind the new event to the command
binding.BindEvent(d, e.NewValue.ToString());
}
#endregion
#region Helpers
//tries to get a CommandBehaviorBinding from the element. Creates a new instance if there is not one attached
private static CommandBehaviorBinding FetchOrCreateBinding(DependencyObject d)
{
CommandBehaviorBinding binding = CommandBehavior.GetBehavior(d);
if (binding == null)
{
binding = new CommandBehaviorBinding();
CommandBehavior.SetBehavior(d, binding);
}
return binding;
}
#endregion
}
}
I'm curious if there is a simple way to bind properties of a class to individual items of a CheckedListBox. There are several components out there (Telerik/DevExpress) that provide PropertyEditor grids, but I'm looking to do it in a CheckedListBox.
IE:
public class MyItem
{
public bool Property1
{
get;
set;
}
public bool Property2
{
get;
set;
}
public bool Property3
{
get;
set;
}
}
And when adding items to the CheckedListBox, I'd like to have some sort of method that lets me do:
this.AddCheckListBoxItem("Property A", this.myItem.Property1);
this.AddCheckListBoxItem("Property B", this.myItem.Property2);
this.AddCheckListBoxItem("Property C", this.myItem.Property3);
the first parameter being the display name within the CheckedListBox.
Then throughout any changes to the checkstate, the bool values would automatically be updated without further code.
Currently there isn't any easy/simple way to get the functionallity you are looking for.
As in the comments the nearest solution I can think of would be to use reflection.
If you manage to build a helper class that has this functionallity, please post here as I would also find that usefull.
I've created a solution using reflection as suggested by #Jethro. I used generics in the class definition, but I didn't implement anything that uses them yet. To summarize, basically using reflection, I've bound a single object and it's boolean properties to individual items within a standard CheckedListBox. It's nice as it doesn't require having to code to set the property values and get them when saving data, as the reflection takes care of this.
I created a Form, and added a CheckedListBox to it, and a button. Here's the code side.
using System;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
/// <summary>
/// The main form.
/// </summary>
public partial class Form1 : Form
{
/// <summary>
/// The checked list box property helper.
/// </summary>
private CheckedListBoxPropertyHelper<MyItem> helper;
/// <summary>
/// My object to bind to the checked list box.
/// </summary>
private MyItem myObjectDataSource;
/// <summary>
/// Initializes a new instance of the <see cref="Form1"/> class.
/// </summary>
public Form1()
{
this.InitializeComponent();
this.myObjectDataSource = new MyItem();
this.helper = new CheckedListBoxPropertyHelper<MyItem>(this.checkedListBox1, this.myObjectDataSource, true);
this.helper.AddCheckListBoxItem(new CheckedPropertyItem("Property One", "Property1"));
this.helper.AddCheckListBoxItem(new CheckedPropertyItem("Property Two", "Property2"));
this.helper.AddCheckListBoxItem(new CheckedPropertyItem("Property Three", "Property3"));
}
/// <summary>
/// Handles the Click event of the Button1 control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void Button1_Click(object sender, EventArgs e)
{
// In the constructor
// if we instantiated: this.helper = new CheckedListBoxPropertyHelper<MyItem>(this.checkedListBox1, this.myObjectDataSource, true);
// as: this.helper = new CheckedListBoxPropertyHelper<MyItem>(this.checkedListBox1, this.myObjectDataSource, false);
// changing the last bindImmediate property to false, the changes to the checkboxes wouldn't take effect immediately
// on the underlying object, you need to call this.helper.CommitChanges() at which point the changes
// will be made to the datasource object.
// this.helper.CommitChanges();
Console.WriteLine(this.myObjectDataSource.Property1.ToString());
Console.WriteLine(this.myObjectDataSource.Property2.ToString());
Console.WriteLine(this.myObjectDataSource.Property3.ToString());
}
}
/// <summary>
/// My item.
/// </summary>
public class MyItem
{
/// <summary>
/// Gets or sets a value indicating whether this <see cref="MyItem"/> is property1.
/// </summary>
/// <value><c>true</c> if property1; otherwise, <c>false</c>.</value>
public bool Property1
{
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="MyItem"/> is property2.
/// </summary>
/// <value><c>true</c> if property2; otherwise, <c>false</c>.</value>
public bool Property2
{
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="MyItem"/> is property3.
/// </summary>
/// <value><c>true</c> if property3; otherwise, <c>false</c>.</value>
public bool Property3
{
get;
set;
}
}
/// <summary>
/// The checked list box property helper. This binds datasource properties to checkedlistbox items.
/// </summary>
public class CheckedListBoxPropertyHelper<T> where T : class
{
/// <summary>
/// The checked list box.
/// </summary>
private CheckedListBox checkedListBox;
/// <summary>
/// The property info.
/// </summary>
private PropertyInfo[] PropertyInfo;
/// <summary>
/// Initializes a new instance of the <see cref="CheckedListBoxPropertyHelper"/> class.
/// </summary>
/// <param name="checkedListBox">The checked list box.</param>
/// <param name="dataSource">The data source.</param>
/// <param name="bindImmediate">if set to <c>true</c> [bind immediate].</param>
public CheckedListBoxPropertyHelper(CheckedListBox checkedListBox, T dataSource, bool bindImmediate)
{
this.checkedListBox = checkedListBox;
this.DataSource = dataSource;
this.PropertyInfo = this.DataSource.GetType().GetProperties();
this.BindImmediate = bindImmediate;
this.checkedListBox.ItemCheck += new ItemCheckEventHandler(CheckedListBox_ItemCheck);
}
/// <summary>
/// The data source.
/// </summary>
public T DataSource
{
get;
private set;
}
/// <summary>
/// Gets or sets a value indicating whether to [bind immediately] to the datasource object.
/// </summary>
/// <value><c>true</c> if [bind immediately]; otherwise, <c>false</c>.</value>
public bool BindImmediate
{
get;
set;
}
/// <summary>
/// Commits the changes.
/// </summary>
public void CommitChanges()
{
if (!this.BindImmediate)
{
for (int i = 0; i < this.checkedListBox.Items.Count; i++)
{
CheckedPropertyItem checkedItem = this.checkedListBox.Items[i] as CheckedPropertyItem;
this.SetChecked(this.checkedListBox.GetItemChecked(i), checkedItem);
}
}
else
{
throw new InvalidOperationException("You cannot commit changes when bind immediate is on.");
}
}
/// <summary>
/// Adds the check list box item.
/// </summary>
/// <param name="item">The item.</param>
public void AddCheckListBoxItem(CheckedPropertyItem item)
{
PropertyInfo info = this.GetPropertyInfo(item.PropertyName);
bool isChecked = false;
if (info != null)
{
isChecked = (bool)info.GetValue(this.DataSource, null);
}
this.checkedListBox.Items.Add(item, isChecked);
}
/// <summary>
/// Handles the ItemCheck event of the CheckedListBox control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Forms.ItemCheckEventArgs"/> instance containing the event data.</param>
private void CheckedListBox_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (BindImmediate)
{
CheckedListBox clb = sender as CheckedListBox;
CheckedPropertyItem checkedItem = clb.Items[e.Index] as CheckedPropertyItem;
this.SetChecked(
e.NewValue == CheckState.Checked ? true : false,
checkedItem);
}
}
/// <summary>
/// Sets the checked.
/// </summary>
/// <param name="isChecked">if set to <c>true</c> [is checked].</param>
/// <param name="clb">The CLB.</param>
private void SetChecked(bool isChecked, CheckedPropertyItem item)
{
PropertyInfo info = this.GetPropertyInfo(item.PropertyName);
if (info.CanWrite)
{
info.SetValue(this.DataSource, isChecked, null);
}
}
/// <summary>
/// Gets the property info.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
private PropertyInfo GetPropertyInfo(string propertyName)
{
return this.PropertyInfo
.Where(c => c.Name == propertyName)
.SingleOrDefault();
}
}
/// <summary>
/// Checked Property Item binding.
/// </summary>
public class CheckedPropertyItem
{
/// <summary>
/// Initializes a new instance of the <see cref="CheckedPropertyItem"/> class.
/// </summary>
/// <param name="title">The title.</param>
/// <param name="propertyName">Name of the property.</param>
public CheckedPropertyItem(string title, string propertyName)
{
this.Title = title;
this.PropertyName = propertyName;
}
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
public string Title
{
get;
private set;
}
/// <summary>
/// Gets the name of the property.
/// </summary>
public string PropertyName
{
get;
private set;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>A <see cref="System.String"/> that represents this instance.</returns>
public override string ToString()
{
return this.Title;
}
}