Last active
May 8, 2017 23:52
-
-
Save ajai8085/52bbe0c50a3d3a8e42d0923a548309db to your computer and use it in GitHub Desktop.
Poor Man's Single Page MVVM with Event Aggregator (Missing validation)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.Serialization; | |
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Input; | |
using System.Windows.Threading; | |
using System.Linq; | |
namespace AJ.Mvvm | |
{ | |
public static class CachedFactory | |
{ | |
public class CachedItem | |
{ | |
public ICachedItem CachedObject { get; set; } | |
public Type CachedItemType { get; set; } | |
public DateTime CachedAt { get; private set; } | |
public TimeSpan? ExpiresAfter { get; set; } | |
public CachedItem() | |
{ | |
CachedAt = DateTime.Now; | |
} | |
public bool IsExpired() | |
{ | |
if (ExpiresAfter.HasValue) | |
{ | |
var res = (DateTime.Now - CachedAt.Add(ExpiresAfter.Value)); | |
if (res.TotalMilliseconds > 0) | |
return true; | |
} | |
return false; | |
} | |
} | |
private static readonly ConcurrentDictionary<string, CachedItem> CachedViewModels = | |
new ConcurrentDictionary<string, CachedItem>(); | |
/// <summary> | |
/// Caches created object and returns it . If object does not exists in the cache, this method will contact you . | |
/// </summary> | |
/// <typeparam name="TClass"></typeparam> | |
/// <param name="onCreateInstance"></param> | |
/// <returns></returns> | |
public static TClass CrateOrGetCachedObject<TClass>(Func<TClass> onCreateInstance, | |
TimeSpan? expiresafter = null) | |
where TClass : class | |
{ | |
TClass instance = default(TClass); | |
var typetoCache = typeof(TClass); | |
CachedItem cachedViewModel = null; | |
var key = typetoCache.FullName; | |
if (!CachedViewModels.TryGetValue(key, out cachedViewModel)) | |
{ | |
instance = onCreateInstance(); | |
var cachedItem = instance as ICachedItem; | |
if (cachedItem != null) | |
{ | |
cachedViewModel = new CachedItem | |
{ | |
ExpiresAfter = expiresafter, | |
CachedItemType = typetoCache, | |
CachedObject = cachedItem | |
}; | |
CachedViewModels.TryAdd(key, cachedViewModel | |
); | |
cachedItem.OnResolveFromCache(false); | |
} | |
else | |
{ | |
System.Diagnostics.Debugger.Log(0,"Default", | |
string.Format("Object {0} failed to cache , in order to cache implement ICachedItem", | |
typetoCache)); | |
} | |
} | |
else | |
{ | |
if (cachedViewModel.IsExpired()) | |
{ | |
CachedItem cachedItemLocal = null; | |
if (CachedViewModels.TryRemove(key, out cachedItemLocal)) | |
{ | |
instance = CrateOrGetCachedObject<TClass>(onCreateInstance, expiresafter); | |
} | |
else | |
{ | |
cachedViewModel.CachedObject.OnResolveFromCache(true); | |
instance = cachedViewModel.CachedObject as TClass; | |
} | |
} | |
else | |
{ | |
cachedViewModel.CachedObject.OnResolveFromCache(true); | |
instance = cachedViewModel.CachedObject as TClass; | |
} | |
} | |
return instance; | |
} | |
/// <summary> | |
/// This method will not cache an item if it is not implementing ICachedItem | |
/// </summary> | |
/// <param name="objectType"></param> | |
/// <param name="onCreateInstance"></param> | |
/// <returns></returns> | |
public static object CrateOrGetCachedObject(Type objectType, Func<object> onCreateInstance, | |
TimeSpan? expiresafter = null) | |
{ | |
var typetoCache = objectType; | |
CachedItem cachedViewModel = null; | |
object instance = null; | |
var key = typetoCache.FullName; | |
if (!CachedViewModels.TryGetValue(key, out cachedViewModel)) | |
{ | |
instance = onCreateInstance(); | |
var cachedItem = instance as ICachedItem; | |
if (cachedItem != null) | |
{ | |
CachedViewModels.TryAdd(typetoCache.FullName, | |
new CachedItem | |
{ | |
ExpiresAfter = expiresafter, | |
CachedItemType = typetoCache, | |
CachedObject = cachedItem | |
}); | |
cachedItem.OnResolveFromCache(false); | |
} | |
else | |
{ | |
System.Diagnostics.Debugger.Log(1,"Default", | |
string.Format("Object {0} failed to cache , in order to cache implement ICachedItem", | |
typetoCache)); | |
} | |
} | |
else | |
{ | |
if (cachedViewModel.IsExpired()) | |
{ | |
CachedItem cachedItemLocal = null; | |
if (CachedViewModels.TryRemove(key, out cachedItemLocal)) | |
{ | |
instance = CrateOrGetCachedObject(onCreateInstance, expiresafter); | |
} | |
else | |
{ | |
cachedViewModel.CachedObject.OnResolveFromCache(true); | |
instance = cachedViewModel.CachedObject; | |
} | |
} | |
else | |
{ | |
cachedViewModel.CachedObject.OnResolveFromCache(true); | |
instance = cachedViewModel.CachedObject; | |
} | |
} | |
return instance; | |
} | |
} | |
public static class ViewModelExtensions | |
{ | |
public static bool? ShowDialog<TViewModel>(this TViewModel viewModel, bool maximized = true, | |
ParentWinFormOrWpfWindow ownerForm = null) | |
where TViewModel : ScreenBase | |
{ | |
bool? result = null; | |
var viewModelType = typeof(TViewModel); | |
var bfound = false; | |
var freshBakedView = false; | |
Type viewType = null; | |
if (viewModelType.TryInferViewType(out viewType)) | |
{ | |
object view = null; | |
var isCachedView = false; | |
if (viewModel.View == null) | |
{ | |
var cachedview = CachedFactory.CrateOrGetCachedObject(viewType, () => | |
{ | |
freshBakedView = true; | |
return Activator.CreateInstance(viewType); | |
}); | |
view = cachedview; | |
isCachedView = !freshBakedView; | |
} | |
else | |
{ | |
view = viewModel.View; | |
var cachedView = view as ICachedItem; | |
if (cachedView != null) | |
{ | |
cachedView.OnResolveFromCache(true); | |
} | |
isCachedView = true; | |
} | |
var windowView = view as Window; | |
if (windowView != null) | |
{ | |
SetParent(ownerForm, windowView); //Set the parent window for the WindowView | |
if (freshBakedView) | |
{ | |
if (maximized) | |
{ | |
//Fix for showing window on the working screen -> http://stackoverflow.com/questions/3121900/how-can-i-make-a-wpf-window-maximized-on-the-screen-with-the-mouse-cursor | |
windowView.SourceInitialized += | |
(s, a) => windowView.WindowState = WindowState.Maximized; | |
} | |
//Let the window know about the viewmodel | |
windowView.DataContext = viewModel; | |
//Let the viewmodel know about the view | |
viewModel.View = windowView; | |
viewModel.ViewParent = windowView; | |
windowView.Loaded += (s, a) => viewModel.RaiseViewLoaded(isCachedView); | |
} | |
else | |
{ | |
viewModel.RaiseViewLoaded(isCachedView); | |
} | |
bfound = true; | |
result = windowView.ShowDialog(); | |
} | |
} | |
if (!bfound) | |
{ | |
var windowViewDlg = new Window | |
{ | |
Title = "View not found !!!!", | |
Content = new Label { Content = "View Not found for view model -> " + viewModelType.FullName } | |
}; | |
result = windowViewDlg.ShowDialog(); | |
} | |
return result; | |
} | |
private static void SetParent(ParentWinFormOrWpfWindow parentWindow, Window childWindow) | |
{ | |
if (parentWindow != null && parentWindow.IsValidWinForm()) | |
{ | |
var helper = | |
new System.Windows.Interop.WindowInteropHelper(childWindow) | |
{ | |
Owner = parentWindow.WinformsAdapter.Handle | |
}; //Set the owner of the WPF form as winform | |
childWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner; | |
} | |
else if (parentWindow != null && parentWindow.IsValidWpfWindow()) | |
{ | |
childWindow.Owner = parentWindow.WpfWindowAdapter; | |
childWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner; | |
} | |
else | |
{ | |
childWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen; | |
} | |
} | |
private static bool TryInferViewType(this Type viewModelType, out Type viewType) | |
{ | |
var bFound = false; | |
var viewTypeName = viewModelType.FullName; | |
if (viewModelType.IsGenericType) | |
{ | |
//Get the non generic viewmodel | |
var index = viewTypeName.IndexOf('`'); | |
viewTypeName = index == -1 ? viewTypeName : viewTypeName.Substring(0, index); | |
} | |
viewType = null; | |
if (viewTypeName.EndsWith("ViewModel", StringComparison.InvariantCultureIgnoreCase)) | |
{ | |
var index = viewTypeName.LastIndexOf("Model", StringComparison.InvariantCultureIgnoreCase) - 1; | |
var viewName = viewTypeName.Substring(0, index + 1); | |
viewType = Type.GetType(viewName); | |
if (viewType == null) | |
{ | |
viewType = viewModelType.Assembly.GetType(viewName); | |
} | |
bFound = true; | |
} | |
return bFound; | |
} | |
public static void SetWindowDefaults(this Window windowView, bool maximized) | |
{ | |
if (maximized) | |
{ | |
//Fix for showing window on the working screen -> http://stackoverflow.com/questions/3121900/how-can-i-make-a-wpf-window-maximized-on-the-screen-with-the-mouse-cursor | |
windowView.WindowStartupLocation = WindowStartupLocation.CenterScreen; | |
windowView.SourceInitialized += (s, a) => windowView.WindowState = WindowState.Maximized; | |
} | |
} | |
public static void SetWindowWithoutHeader(this Window windowView) | |
{ | |
windowView.ResizeMode = ResizeMode.NoResize; | |
windowView.WindowStyle = WindowStyle.None; | |
} | |
} | |
public class ParentWinFormOrWpfWindow : IDisposable | |
{ | |
public System.Windows.Forms.Form WinformsAdapter { get; private set; } | |
public Window WpfWindowAdapter { get; private set; } | |
public bool IsValidWinForm() | |
{ | |
return WinformsAdapter != null; | |
} | |
public bool IsValidWpfWindow() | |
{ | |
return WpfWindowAdapter != null; | |
} | |
private ParentWinFormOrWpfWindow() //Do not instantiate this class . | |
{ | |
} | |
public void Dispose() | |
{ | |
WinformsAdapter = null; | |
WpfWindowAdapter = null; | |
} | |
public static implicit operator ParentWinFormOrWpfWindow(System.Windows.Forms.Form rhs) | |
{ | |
return new ParentWinFormOrWpfWindow() { WinformsAdapter = (rhs) }; | |
} | |
public static implicit operator ParentWinFormOrWpfWindow(Window rhs) | |
{ | |
return new ParentWinFormOrWpfWindow() { WpfWindowAdapter = (rhs) }; | |
} | |
} | |
static public class ViewExtensions | |
{ | |
/// <summary> | |
/// Executes the action on the UI thread. | |
/// </summary> | |
/// <param name="action">The action to execute.</param> | |
public static void OnUIThread(this Action action) | |
{ | |
if (Dispatcher.CurrentDispatcher.CheckAccess()) | |
{ | |
action(); | |
} | |
else | |
{ | |
Dispatcher.CurrentDispatcher.BeginInvoke(action); | |
} | |
} | |
public static MemberInfo GetMemberInfo(this System.Linq.Expressions.Expression expression) | |
{ | |
LambdaExpression lambdaExpression = (LambdaExpression)expression; | |
MemberExpression memberExpression; | |
if (lambdaExpression.Body is UnaryExpression) | |
{ | |
UnaryExpression unaryExpression = (UnaryExpression)lambdaExpression.Body; | |
memberExpression = (MemberExpression)unaryExpression.Operand; | |
} | |
else | |
{ | |
memberExpression = (MemberExpression)lambdaExpression.Body; | |
} | |
return memberExpression.Member; | |
} | |
} | |
public interface ICachedItem | |
{ | |
void OnResolveFromCache(bool isResolvedFromCache); | |
} | |
public interface IHaveTitle | |
{ | |
string Title { set; } | |
} | |
public interface IHaveView<TView> where TView : DependencyObject | |
{ | |
TView View { get; set; } | |
} | |
public abstract class PropertyChagnedBase : INotifyPropertyChanged | |
{ | |
protected bool _isDirty = false; | |
protected bool _isNotifying = true; | |
protected virtual bool SetValueNotify<T>(ref T oldValue, T newValue, Expression<Func<T>> propertyExpression) | |
{ | |
if (EqualityComparer<T>.Default.Equals(oldValue, newValue)) | |
{ | |
return false; | |
} | |
oldValue = newValue; | |
NotifyOfPropertyChange(propertyExpression); | |
_isDirty = true; | |
return true; | |
} | |
protected virtual bool SetValueNotify<T>(ref T oldValue, T newValue, | |
[CallerMemberName] string propertyName = null) | |
{ | |
if (EqualityComparer<T>.Default.Equals(oldValue, newValue)) | |
{ | |
return false; | |
} | |
oldValue = newValue; | |
NotifyOfPropertyChange(propertyName); | |
_isDirty = true; | |
return true; | |
} | |
protected virtual ICommand GetCommand(ref ICommand command, Action executeMethod, | |
Func<bool> canExecuteMethod = null) | |
{ | |
if (command == null) | |
{ | |
command = new DelegateCommand(executeMethod, canExecuteMethod); | |
} | |
return command; | |
} | |
/// <summary> | |
/// Occurs when a property value changes. | |
/// </summary> | |
public event PropertyChangedEventHandler PropertyChanged = | |
delegate (object param0, PropertyChangedEventArgs param1) | |
{ | |
}; | |
/// <summary> | |
/// Notifies subscribers of the property change. | |
/// </summary> | |
/// <param name="propertyName">Name of the property.</param> | |
public virtual void NotifyOfPropertyChange([CallerMemberName] string propertyName = null) | |
{ | |
if (_isNotifying) | |
{ | |
new Action(() => | |
{ | |
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); | |
}).OnUIThread(); | |
} | |
} | |
/// <summary> | |
/// Notifies subscribers of the property change. | |
/// </summary> | |
/// <typeparam name="TProperty">The type of the property.</typeparam> | |
/// <param name="property">The property expression.</param> | |
public virtual void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property) | |
{ | |
this.NotifyOfPropertyChange(property.GetMemberInfo().Name); | |
} | |
/// <summary> | |
/// Raises the <see cref="E:Caliburn.Micro.PropertyChangedBase.PropertyChanged" /> event directly. | |
/// </summary> | |
/// <param name="e">The <see cref="T:System.ComponentModel.PropertyChangedEventArgs" /> instance containing the event data.</param> | |
[EditorBrowsable(EditorBrowsableState.Never)] | |
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) | |
{ | |
PropertyChangedEventHandler propertyChanged = this.PropertyChanged; | |
if (propertyChanged != null) | |
{ | |
propertyChanged(this, e); | |
} | |
} | |
} | |
/// <summary> | |
/// A base class that implements the infrastructure for property change notification and automatically performs UI thread marshalling. | |
/// </summary> | |
public abstract class ScreenBase : ViewAwareScreenBase<DependencyObject> | |
{ | |
protected virtual TViewType GetView<TViewType>() where TViewType : class | |
{ | |
return View as TViewType; | |
} | |
protected bool GetView<TViewType>(Action<TViewType> cbfnAction) where TViewType : class | |
{ | |
var view = GetView<TViewType>(); | |
if (view != null) | |
{ | |
cbfnAction(view); | |
return true; | |
} | |
else | |
{ | |
return false; | |
} | |
} | |
} | |
/// <summary> | |
/// A base class that implements the infrastructure for property change notification and automatically performs UI thread marshalling. | |
/// </summary> | |
public abstract class ViewAwareScreenBase<TView> : INotifyPropertyChanged, IHaveTitle, IHaveView<TView>, IDisposable | |
where TView : DependencyObject | |
{ | |
protected abstract void ResetValues(); | |
TView _view; | |
public TView View | |
{ | |
get { return _view; } | |
set { _view = value; } | |
} | |
/// <summary> | |
/// Trigger ResetValues function followed by the callback function onSetParameters if defined . Callback can be used for setting default values to view model | |
/// </summary> | |
/// <typeparam name="TViewModel"></typeparam> | |
/// <param name="onSetParameters"></param> | |
public virtual void ResetValues(Action onSetParameters) | |
{ | |
ResetValues(); | |
if (onSetParameters != null) | |
{ | |
onSetParameters(); | |
} | |
} | |
public DependencyObject ViewParent { get; set; } | |
private bool _isDirty; | |
public bool IsDirty | |
{ | |
get { return _isDirty; } | |
set | |
{ | |
if (_isDirty != value) | |
{ | |
_isDirty = value; | |
this.NotifyOfPropertyChange(); | |
} | |
} | |
} | |
public int CacheResolveCount { get; private set; } | |
public void RaiseViewLoaded(bool isCached) | |
{ | |
if (isCached) | |
{ | |
CacheResolveCount++; | |
} | |
OnViewLoaded(View); | |
} | |
/// <summary> | |
/// Called when View Window get loaded | |
/// </summary> | |
/// <param name="view"></param> | |
protected virtual void OnViewLoaded(DependencyObject view) | |
{ | |
} | |
protected Window GetViewWindow() | |
{ | |
var window = View as Window; | |
if (window != null) | |
return window; | |
else | |
window = ViewParent as Window; | |
if (window != null) | |
return window; | |
return Window.GetWindow(View); | |
} | |
public string Title | |
{ | |
set | |
{ | |
var window = GetViewWindow(); | |
if (window != null) | |
{ | |
window.Title = value; | |
} | |
} | |
} | |
protected bool SetValueNotify<T>(ref T oldValue, T newValue, Expression<Func<T>> propertyExpression) | |
{ | |
if (EqualityComparer<T>.Default.Equals(oldValue, newValue)) | |
{ | |
return false; | |
} | |
oldValue = newValue; | |
NotifyOfPropertyChange(propertyExpression); | |
IsDirty = true; | |
return true; | |
} | |
protected bool SetValueNotify<T>(ref T oldValue, T newValue, [CallerMemberName] string propertyName = null) | |
{ | |
if (EqualityComparer<T>.Default.Equals(oldValue, newValue)) | |
{ | |
return false; | |
} | |
oldValue = newValue; | |
NotifyOfPropertyChange(propertyName); | |
IsDirty = true; | |
return true; | |
} | |
protected ICommand GetCommand(ref ICommand command, Action executeMethod, Func<bool> canExecuteMethod = null) | |
{ | |
if (command == null) | |
{ | |
command = new DelegateCommand(executeMethod, canExecuteMethod); | |
} | |
return command; | |
} | |
protected ICommand GetCommandWithParam<T>(ref ICommand command, Action<T> executeMethod, | |
Func<T, bool> canExecuteMethod = null) | |
{ | |
if (command == null) | |
{ | |
command = new DelegateCommand<T>(executeMethod, canExecuteMethod); | |
} | |
return command; | |
} | |
protected ICommand GetCommand<TCommandParam>(ref ICommand command, Action<TCommandParam> executeMethod, | |
Func<TCommandParam, bool> canExecuteMethod = null) | |
{ | |
if (command == null) | |
{ | |
command = new DelegateCommand<TCommandParam>(executeMethod, canExecuteMethod); | |
} | |
return command; | |
} | |
/// <summary> | |
/// Gets a value indicating whether the control is in design mode | |
/// (running under Blend or Visual Studio). | |
/// </summary> | |
public bool IsInDesignMode | |
{ | |
get { return IsInDesignModeStatic; } | |
} | |
private static bool? _isInDesignMode; | |
/// <summary> | |
/// Gets a value indicating whether the control is in design mode | |
/// (running in Blend or Visual Studio). | |
/// </summary> | |
public static bool IsInDesignModeStatic | |
{ | |
get | |
{ | |
if (!_isInDesignMode.HasValue) | |
{ | |
DependencyProperty prop = DesignerProperties.IsInDesignModeProperty; | |
_isInDesignMode = | |
(bool) | |
DependencyPropertyDescriptor.FromProperty(prop, typeof(FrameworkElement)).Metadata.DefaultValue; | |
} | |
return _isInDesignMode.Value; | |
} | |
} | |
/// <summary> | |
/// Occurs when a property value changes. | |
/// </summary> | |
public event PropertyChangedEventHandler PropertyChanged = | |
delegate (object param0, PropertyChangedEventArgs param1) | |
{ | |
}; | |
/// <summary> | |
/// Enables/Disables property change notification. | |
/// </summary> | |
public bool IsNotifying { get; set; } | |
/// <summary> | |
/// Creates an instance of <see cref="T:Caliburn.Micro.PropertyChangedBase" />. | |
/// </summary> | |
public ViewAwareScreenBase() | |
{ | |
this.IsNotifying = true; | |
} | |
/// <summary> | |
/// Raises a change notification indicating that all bindings should be refresrefreshed. | |
/// </summary> | |
public void Refresh() | |
{ | |
this.NotifyOfPropertyChange(string.Empty); | |
} | |
/// <summary> | |
/// Notifies subscribers of the property change. | |
/// </summary> | |
/// <param name="propertyName">Name of the property.</param> | |
public virtual void NotifyOfPropertyChange([CallerMemberName] string propertyName = null) | |
{ | |
if (this.IsNotifying) | |
{ | |
new Action(() => | |
{ | |
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); | |
}).OnUIThread(); | |
} | |
} | |
/// <summary> | |
/// Notifies subscribers of the property change. | |
/// </summary> | |
/// <typeparam name="TProperty">The type of the property.</typeparam> | |
/// <param name="property">The property expression.</param> | |
public void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property) | |
{ | |
this.NotifyOfPropertyChange(property.GetMemberInfo().Name); | |
} | |
/// <summary> | |
/// Raises the <see cref="E:Caliburn.Micro.PropertyChangedBase.PropertyChanged" /> event directly. | |
/// </summary> | |
/// <param name="e">The <see cref="T:System.ComponentModel.PropertyChangedEventArgs" /> instance containing the event data.</param> | |
[EditorBrowsable(EditorBrowsableState.Never)] | |
protected void OnPropertyChanged(PropertyChangedEventArgs e) | |
{ | |
PropertyChangedEventHandler propertyChanged = this.PropertyChanged; | |
if (propertyChanged != null) | |
{ | |
propertyChanged(this, e); | |
} | |
} | |
protected bool _isDisposed = false; | |
public void Dispose() | |
{ | |
Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
~ViewAwareScreenBase() | |
{ | |
Dispose(false); | |
} | |
protected abstract void Dispose(bool isDisposing); | |
private WindowClosedDialogResults _windowDialogResult; | |
public virtual WindowClosedDialogResults WindowDialogResult | |
{ | |
get { return _windowDialogResult; } | |
set { SetValueNotify(ref _windowDialogResult, value); } | |
} | |
protected virtual void CloseWindow(WindowClosedDialogResults dialogResult = WindowClosedDialogResults.Cancel) | |
{ | |
WindowDialogResult = dialogResult; | |
var vw = GetViewWindow(); | |
if (vw != null) | |
{ | |
var cwv = vw as ICachedItem; | |
if (cwv == null) | |
{ | |
vw.DialogResult = dialogResult == WindowClosedDialogResults.OK | |
? (bool?)true | |
: (dialogResult == WindowClosedDialogResults.Cancel ? (bool?)false : null); | |
vw.Close(); | |
} | |
else | |
{ | |
HideWindow(dialogResult); | |
} | |
} | |
} | |
/// <summary> | |
/// When you hide window , dialog result is available from Windowmanagerextensionmethod | |
/// </summary> | |
/// <param name="dialogResult"></param> | |
protected virtual void HideWindow(WindowClosedDialogResults dialogResult = WindowClosedDialogResults.Cancel) | |
{ | |
WindowDialogResult = dialogResult; | |
var vw = GetViewWindow(); | |
if (vw != null) | |
{ | |
vw.Hide(); | |
} | |
} | |
private bool _isBusy; | |
public virtual bool IsBusy | |
{ | |
get { return _isBusy; } | |
set { SetValueNotify(ref _isBusy, value); } | |
} | |
public IDisposable ActivateBusyBlock(Action beforeAction = null, Action afterAction = null) | |
{ | |
IsBusy = true; | |
if (beforeAction != null) | |
{ | |
beforeAction(); | |
} | |
return new DisposableObject(() => | |
{ | |
IsBusy = false; | |
if (afterAction != null) | |
{ | |
afterAction(); | |
} | |
}); | |
} | |
//public DisposableObject<TProgress> ActivateBusyBlock<TProgress>() | |
//{ | |
// IsBusy = true; | |
// return new DisposableObject<TProgress>(() => IsBusy = false, OnProgress); | |
//} | |
//protected virtual void OnProgress<TProgress>(TProgress progressMessage) | |
//{ | |
//} | |
} | |
public class DisposableObject : IDisposable | |
{ | |
private readonly Action _onDispose; | |
public DisposableObject(Action onDispose) | |
{ | |
_onDispose = onDispose; | |
} | |
public void Dispose() | |
{ | |
if (_onDispose != null) | |
{ | |
_onDispose(); | |
} | |
} | |
} | |
public class DisposableObject<TProgress> : DisposableObject | |
{ | |
private readonly Action<TProgress> _onProgress; | |
public DisposableObject(Action onDispose, Action<TProgress> onProgress) | |
: base(onDispose) | |
{ | |
_onProgress = onProgress; | |
} | |
public bool ReportProgress(TProgress progress) | |
{ | |
if (_onProgress != null) | |
{ | |
_onProgress(progress); | |
return true; | |
} | |
return false; | |
} | |
} | |
public enum WindowClosedDialogResults | |
{ | |
None, | |
OK, | |
Cancel, | |
Abort, | |
Retry, | |
Ignore, | |
Yes, | |
No, | |
} | |
////http://blog.excastle.com/2010/07/25/mvvm-and-dialogresult-with-no-code-behind/comment-page-1/ | |
//public static class DialogCloser | |
//{ | |
// public static readonly DependencyProperty DialogResultProperty = | |
// DependencyProperty.RegisterAttached( | |
// "DialogResult", | |
// typeof(bool?), | |
// typeof(DialogCloser), | |
// new PropertyMetadata(DialogResultChanged)); | |
// private static void DialogResultChanged( | |
// DependencyObject d, | |
// DependencyPropertyChangedEventArgs e) | |
// { | |
// var window = d as Window; | |
// if (window != null) | |
// window.DialogResult = e.NewValue as bool?; | |
// } | |
// public static void SetDialogResult(Window target, bool? value) | |
// { | |
// target.SetValue(DialogResultProperty, value); | |
// } | |
//} | |
} | |
namespace AJ.Mvvm | |
{ | |
/// <summary> | |
/// This class allows delegating the commanding logic to methods passed as parameters, | |
/// and enables a View to bind commands to objects that are not part of the element tree. | |
/// </summary> | |
public class DelegateCommand : ICommand | |
{ | |
#region Constructors | |
/// <summary> | |
/// Constructor | |
/// </summary> | |
public DelegateCommand(Action executeMethod) | |
: this(executeMethod, null, false) | |
{ | |
} | |
/// <summary> | |
/// Constructor | |
/// </summary> | |
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod) | |
: this(executeMethod, canExecuteMethod, false) | |
{ | |
} | |
/// <summary> | |
/// Constructor | |
/// </summary> | |
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled) | |
{ | |
if (executeMethod == null) | |
{ | |
throw new ArgumentNullException("executeMethod"); | |
} | |
_executeMethod = executeMethod; | |
_canExecuteMethod = canExecuteMethod; | |
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; | |
} | |
#endregion | |
#region Public Methods | |
/// <summary> | |
/// Method to determine if the command can be executed | |
/// </summary> | |
public bool CanExecute() | |
{ | |
if (_canExecuteMethod != null) | |
{ | |
return _canExecuteMethod(); | |
} | |
return true; | |
} | |
/// <summary> | |
/// Execution of the command | |
/// </summary> | |
public void Execute() | |
{ | |
if (_executeMethod != null) | |
{ | |
_executeMethod(); | |
} | |
} | |
/// <summary> | |
/// Property to enable or disable CommandManager's automatic requery on this command | |
/// </summary> | |
public bool IsAutomaticRequeryDisabled | |
{ | |
get | |
{ | |
return _isAutomaticRequeryDisabled; | |
} | |
set | |
{ | |
if (_isAutomaticRequeryDisabled != value) | |
{ | |
if (value) | |
{ | |
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); | |
} | |
else | |
{ | |
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); | |
} | |
_isAutomaticRequeryDisabled = value; | |
} | |
} | |
} | |
/// <summary> | |
/// Raises the CanExecuteChaged event | |
/// </summary> | |
public void RaiseCanExecuteChanged() | |
{ | |
OnCanExecuteChanged(); | |
} | |
/// <summary> | |
/// Protected virtual method to raise CanExecuteChanged event | |
/// </summary> | |
protected virtual void OnCanExecuteChanged() | |
{ | |
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); | |
} | |
#endregion | |
#region ICommand Members | |
/// <summary> | |
/// ICommand.CanExecuteChanged implementation | |
/// </summary> | |
public event EventHandler CanExecuteChanged | |
{ | |
add | |
{ | |
if (!_isAutomaticRequeryDisabled) | |
{ | |
CommandManager.RequerySuggested += value; | |
} | |
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); | |
} | |
remove | |
{ | |
if (!_isAutomaticRequeryDisabled) | |
{ | |
CommandManager.RequerySuggested -= value; | |
} | |
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); | |
} | |
} | |
bool ICommand.CanExecute(object parameter) | |
{ | |
return CanExecute(); | |
} | |
void ICommand.Execute(object parameter) | |
{ | |
Execute(); | |
} | |
#endregion | |
#region Data | |
private readonly Action _executeMethod = null; | |
private readonly Func<bool> _canExecuteMethod = null; | |
private bool _isAutomaticRequeryDisabled = false; | |
private List<WeakReference> _canExecuteChangedHandlers; | |
#endregion | |
} | |
/// <summary> | |
/// This class allows delegating the commanding logic to methods passed as parameters, | |
/// and enables a View to bind commands to objects that are not part of the element tree. | |
/// </summary> | |
/// <typeparam name="T">Type of the parameter passed to the delegates</typeparam> | |
public class DelegateCommand<T> : ICommand | |
{ | |
#region Constructors | |
/// <summary> | |
/// Constructor | |
/// </summary> | |
public DelegateCommand(Action<T> executeMethod) | |
: this(executeMethod, null, false) | |
{ | |
} | |
/// <summary> | |
/// Constructor | |
/// </summary> | |
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) | |
: this(executeMethod, canExecuteMethod, false) | |
{ | |
} | |
/// <summary> | |
/// Constructor | |
/// </summary> | |
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled) | |
{ | |
if (executeMethod == null) | |
{ | |
throw new ArgumentNullException("executeMethod"); | |
} | |
_executeMethod = executeMethod; | |
_canExecuteMethod = canExecuteMethod; | |
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; | |
} | |
#endregion | |
#region Public Methods | |
/// <summary> | |
/// Method to determine if the command can be executed | |
/// </summary> | |
public bool CanExecute(T parameter) | |
{ | |
if (_canExecuteMethod != null) | |
{ | |
return _canExecuteMethod(parameter); | |
} | |
return true; | |
} | |
/// <summary> | |
/// Execution of the command | |
/// </summary> | |
public void Execute(T parameter) | |
{ | |
if (_executeMethod != null) | |
{ | |
_executeMethod(parameter); | |
} | |
} | |
/// <summary> | |
/// Raises the CanExecuteChaged event | |
/// </summary> | |
public void RaiseCanExecuteChanged() | |
{ | |
OnCanExecuteChanged(); | |
} | |
/// <summary> | |
/// Protected virtual method to raise CanExecuteChanged event | |
/// </summary> | |
protected virtual void OnCanExecuteChanged() | |
{ | |
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); | |
} | |
/// <summary> | |
/// Property to enable or disable CommandManager's automatic requery on this command | |
/// </summary> | |
public bool IsAutomaticRequeryDisabled | |
{ | |
get | |
{ | |
return _isAutomaticRequeryDisabled; | |
} | |
set | |
{ | |
if (_isAutomaticRequeryDisabled != value) | |
{ | |
if (value) | |
{ | |
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); | |
} | |
else | |
{ | |
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); | |
} | |
_isAutomaticRequeryDisabled = value; | |
} | |
} | |
} | |
#endregion | |
#region ICommand Members | |
/// <summary> | |
/// ICommand.CanExecuteChanged implementation | |
/// </summary> | |
public event EventHandler CanExecuteChanged | |
{ | |
add | |
{ | |
if (!_isAutomaticRequeryDisabled) | |
{ | |
CommandManager.RequerySuggested += value; | |
} | |
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); | |
} | |
remove | |
{ | |
if (!_isAutomaticRequeryDisabled) | |
{ | |
CommandManager.RequerySuggested -= value; | |
} | |
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); | |
} | |
} | |
bool ICommand.CanExecute(object parameter) | |
{ | |
// if T is of value type and the parameter is not | |
// set yet, then return false if CanExecute delegate | |
// exists, else return true | |
if (parameter == null && | |
typeof(T).IsValueType) | |
{ | |
return (_canExecuteMethod == null); | |
} | |
return CanExecute((T)parameter); | |
} | |
void ICommand.Execute(object parameter) | |
{ | |
Execute((T)parameter); | |
} | |
#endregion | |
#region Data | |
private readonly Action<T> _executeMethod = null; | |
private readonly Func<T, bool> _canExecuteMethod = null; | |
private bool _isAutomaticRequeryDisabled = false; | |
private List<WeakReference> _canExecuteChangedHandlers; | |
#endregion | |
} | |
/// <summary> | |
/// This class contains methods for the CommandManager that help avoid memory leaks by | |
/// using weak references. | |
/// </summary> | |
internal static class CommandManagerHelper | |
{ | |
internal static void CallWeakReferenceHandlers(List<WeakReference> handlers) | |
{ | |
if (handlers != null) | |
{ | |
// Take a snapshot of the handlers before we call out to them since the handlers | |
// could cause the array to me modified while we are reading it. | |
EventHandler[] callees = new EventHandler[handlers.Count]; | |
int count = 0; | |
for (int i = handlers.Count - 1; i >= 0; i--) | |
{ | |
WeakReference reference = handlers[i]; | |
var handler = reference.Target as EventHandler; | |
if (handler == null) | |
{ | |
// Clean up old handlers that have been collected | |
handlers.RemoveAt(i); | |
} | |
else | |
{ | |
callees[count] = handler; | |
count++; | |
} | |
} | |
// Call the handlers that we snapshotted | |
for (int i = 0; i < count; i++) | |
{ | |
EventHandler handler = callees[i]; | |
handler(null, EventArgs.Empty); | |
} | |
} | |
} | |
internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers) | |
{ | |
if (handlers != null) | |
{ | |
foreach (WeakReference handlerRef in handlers) | |
{ | |
var handler = handlerRef.Target as EventHandler; | |
if (handler != null) | |
{ | |
CommandManager.RequerySuggested += handler; | |
} | |
} | |
} | |
} | |
internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers) | |
{ | |
if (handlers != null) | |
{ | |
foreach (WeakReference handlerRef in handlers) | |
{ | |
var handler = handlerRef.Target as EventHandler; | |
if (handler != null) | |
{ | |
CommandManager.RequerySuggested -= handler; | |
} | |
} | |
} | |
} | |
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler) | |
{ | |
AddWeakReferenceHandler(ref handlers, handler, -1); | |
} | |
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize) | |
{ | |
if (handlers == null) | |
{ | |
handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>()); | |
} | |
handlers.Add(new WeakReference(handler)); | |
} | |
internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler) | |
{ | |
if (handlers != null) | |
{ | |
for (int i = handlers.Count - 1; i >= 0; i--) | |
{ | |
WeakReference reference = handlers[i]; | |
var existingHandler = reference.Target as EventHandler; | |
if ((existingHandler == null) || (existingHandler == handler)) | |
{ | |
// Clean up old handlers that have been collected | |
// in addition to the handler that is to be removed. | |
handlers.RemoveAt(i); | |
} | |
} | |
} | |
} | |
} | |
} | |
namespace AJ.Mvvm.Messaging | |
{ | |
static public class EnumerableExtensions | |
{ | |
/// <summary> | |
/// Applies the action to each element in the list. | |
/// </summary> | |
/// <typeparam name="T">The enumerable item's type.</typeparam> | |
/// <param name="enumerable">The elements to enumerate.</param> | |
/// <param name="action">The action to apply to each item in the list.</param> | |
public static void Apply<T>(this IEnumerable<T> enumerable, Action<T> action) | |
{ | |
foreach (var item in enumerable) | |
{ | |
action(item); | |
} | |
} | |
} | |
public interface IMessage | |
{ | |
} | |
public interface IHandleMessage | |
{ | |
} | |
public interface IHandleMessage<in TMessage, out TResult> : IHandleMessage where TMessage : IMessage | |
{ | |
TResult Handle(TMessage message); | |
} | |
public interface IHandleMessage<in TMessage> : IHandleMessage<TMessage, bool> where TMessage : IMessage | |
{ | |
} | |
public interface IMessageAggregator | |
{ | |
void Subscribe(object subscriber); | |
void Unsubscribe(object subscriber); | |
void Publish(object message); | |
} | |
public class MessageAggregator : IMessageAggregator | |
{ | |
static MessageAggregator _messageAggregator; | |
public static IMessageAggregator GetInstance() | |
{ | |
lock (typeof(MessageAggregator)) | |
{ | |
if (_messageAggregator == null) | |
_messageAggregator = new MessageAggregator(); | |
} | |
return _messageAggregator; | |
} | |
private MessageAggregator() | |
{ | |
} | |
public static Action<object, object> HandlerResultProcessing = (target, result) => { }; | |
readonly List<Handler> handlers = new List<Handler>(); | |
public bool HandlerExistsFor(Type messageType) | |
{ | |
return handlers.Any(handler => handler.Handles(messageType) & !handler.IsDead); | |
} | |
public virtual void Subscribe(object subscriber) | |
{ | |
if (subscriber == null) | |
{ | |
throw new ArgumentNullException("subscriber"); | |
} | |
lock (handlers) | |
{ | |
if (handlers.Any(x => x.Matches(subscriber))) | |
{ | |
return; | |
} | |
handlers.Add(new Handler(subscriber)); | |
} | |
} | |
public virtual void Unsubscribe(object subscriber) | |
{ | |
if (subscriber == null) | |
{ | |
throw new ArgumentNullException("subscriber"); | |
} | |
lock (handlers) | |
{ | |
var found = handlers.FirstOrDefault(x => x.Matches(subscriber)); | |
if (found != null) | |
{ | |
handlers.Remove(found); | |
} | |
} | |
} | |
public virtual void Publish(object message) | |
{ | |
Publish(message, x => x.OnUIThread()); | |
} | |
public virtual void Publish(object message, Action<System.Action> marshal) | |
{ | |
if (message == null) | |
{ | |
throw new ArgumentNullException("message"); | |
} | |
if (marshal == null) | |
{ | |
throw new ArgumentNullException("marshal"); | |
} | |
Handler[] toNotify; | |
lock (handlers) | |
{ | |
toNotify = handlers.ToArray(); | |
} | |
marshal(() => | |
{ | |
var messageType = message.GetType(); | |
var dead = toNotify | |
.Where(handler => !handler.Handle(messageType, message)) | |
.ToList(); | |
if (dead.Any()) | |
{ | |
lock (handlers) | |
{ | |
dead.Apply(x => handlers.Remove(x)); | |
} | |
} | |
}); | |
} | |
private class Handler | |
{ | |
readonly WeakReference reference; | |
readonly Dictionary<Type, MethodInfo> supportedHandlers = new Dictionary<Type, MethodInfo>(); | |
public bool IsDead | |
{ | |
get { return reference.Target == null; } | |
} | |
public Handler(object handler) | |
{ | |
reference = new WeakReference(handler); | |
var interfaces = handler.GetType().GetInterfaces() | |
.Where(x => typeof(IHandleMessage).IsAssignableFrom(x) && x.GetTypeInfo().IsGenericType); | |
foreach (var @interface in interfaces) | |
{ | |
var type = @interface.GetGenericArguments()[0]; | |
var method = @interface.GetMethod("Handle", new Type[] { type }); | |
if (method != null) | |
{ | |
supportedHandlers[type] = method; | |
} | |
} | |
} | |
public bool Matches(object instance) | |
{ | |
return reference.Target == instance; | |
} | |
public bool Handle(Type messageType, object message) | |
{ | |
var target = reference.Target; | |
if (target == null) | |
{ | |
return false; | |
} | |
foreach (var pair in supportedHandlers) | |
{ | |
if (pair.Key.IsAssignableFrom(messageType)) | |
{ | |
var result = pair.Value.Invoke(target, new[] { message }); | |
if (result != null) | |
{ | |
HandlerResultProcessing(target, result); | |
} | |
} | |
} | |
return true; | |
} | |
public bool Handles(Type messageType) | |
{ | |
return supportedHandlers.Any(pair => pair.Key.IsAssignableFrom(messageType)); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment