From 4d767f036417a443b9a9b7987c7a8b21e8160411 Mon Sep 17 00:00:00 2001 From: Antony Male Date: Thu, 24 Sep 2015 12:12:25 +0100 Subject: [PATCH] Refactor to add {s:ViewModel}, and use that for actions --- Stylet/Stylet.csproj | 1 + Stylet/ViewManager.cs | 3 +- Stylet/Xaml/ActionBase.cs | 8 +-- Stylet/Xaml/ActionExtension.cs | 5 -- Stylet/Xaml/BindingProxy.cs | 2 + Stylet/Xaml/View.cs | 66 +++++++++++++++--------- Stylet/Xaml/ViewModelExtension.cs | 40 ++++++++++++++ StyletIntegrationTests/ShellView.xaml | 2 +- StyletIntegrationTests/ShellViewModel.cs | 2 + 9 files changed, 92 insertions(+), 37 deletions(-) create mode 100644 Stylet/Xaml/ViewModelExtension.cs diff --git a/Stylet/Stylet.csproj b/Stylet/Stylet.csproj index 812e3bd..8507806 100644 --- a/Stylet/Stylet.csproj +++ b/Stylet/Stylet.csproj @@ -131,6 +131,7 @@ + diff --git a/Stylet/ViewManager.cs b/Stylet/ViewManager.cs index c608828..37bf5ee 100644 --- a/Stylet/ViewManager.cs +++ b/Stylet/ViewManager.cs @@ -245,8 +245,9 @@ namespace Stylet var viewAsFrameworkElement = view as FrameworkElement; if (viewAsFrameworkElement != null) { - logger.Info("Setting {0}'s DataContext to {1}", view, viewModel); + logger.Info("Setting {0}'s DataContext and ViewModel proxy to {1}", view, viewModel); viewAsFrameworkElement.DataContext = viewModel; + View.SetViewModel(viewAsFrameworkElement, viewModel); } var viewModelAsViewAware = viewModel as IViewAware; diff --git a/Stylet/Xaml/ActionBase.cs b/Stylet/Xaml/ActionBase.cs index a66819c..8ca1436 100644 --- a/Stylet/Xaml/ActionBase.cs +++ b/Stylet/Xaml/ActionBase.cs @@ -78,13 +78,7 @@ namespace Stylet.Xaml Mode = BindingMode.OneWay, Source = this.Subject, }); - multiBinding.Bindings.Add(new Binding() - { - Path = new PropertyPath(View.BackupActionTargetBindingProxyProperty), - Mode = BindingMode.OneWay, - Source = this.Subject, - Converter = new BindingProxyToValueConverter(), - }); + multiBinding.Bindings.Add(View.GetBindingToViewModel(this.Subject)); //var binding = new Binding() //{ diff --git a/Stylet/Xaml/ActionExtension.cs b/Stylet/Xaml/ActionExtension.cs index 3ab7728..a1f3d06 100644 --- a/Stylet/Xaml/ActionExtension.cs +++ b/Stylet/Xaml/ActionExtension.cs @@ -107,11 +107,6 @@ namespace Stylet.Xaml if (targetObjectAsDependencyObject == null) return this; - // In some cases, the View.ActionTarget attached property won't be propagated - think popups, context menus, KeyBindings, etc - // In this case, we can grab a reference to the last-set View.ActionTarget using the dynamic resources mechanism - var resourceReference = new DynamicResourceExtension(View.ActionTargetProxyResourceKey).ProvideValue(serviceProvider); - targetObjectAsDependencyObject.SetValue(View.BackupActionTargetBindingProxyProperty, resourceReference); - var propertyAsDependencyProperty = valueService.TargetProperty as DependencyProperty; if (propertyAsDependencyProperty != null && propertyAsDependencyProperty.PropertyType == typeof(ICommand)) { diff --git a/Stylet/Xaml/BindingProxy.cs b/Stylet/Xaml/BindingProxy.cs index 2f50efd..0c00b01 100644 --- a/Stylet/Xaml/BindingProxy.cs +++ b/Stylet/Xaml/BindingProxy.cs @@ -28,6 +28,8 @@ namespace Stylet.Xaml internal class BindingProxyToValueConverter : IValueConverter { + public static readonly BindingProxyToValueConverter Instance = new BindingProxyToValueConverter(); + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var proxy = value as BindingProxy; diff --git a/Stylet/Xaml/View.cs b/Stylet/Xaml/View.cs index 1b29223..732c276 100644 --- a/Stylet/Xaml/View.cs +++ b/Stylet/Xaml/View.cs @@ -17,7 +17,7 @@ namespace Stylet.Xaml internal const string ViewManagerResourceKey = "b9a38199-8cb3-4103-8526-c6cfcd089df7"; - internal const string ActionTargetProxyResourceKey = "8b7cb732-8a14-4813-a580-b1f3cccea7b7"; + internal const string ViewModelProxyResourceKey = "8b7cb732-8a14-4813-a580-b1f3cccea7b7"; /// /// Initial value of the ActionTarget property. @@ -49,28 +49,7 @@ namespace Stylet.Xaml /// The object's ActionTarget. This is used to determine what object to call Actions on by the ActionExtension markup extension. /// public static readonly DependencyProperty ActionTargetProperty = - DependencyProperty.RegisterAttached("ActionTarget", typeof(object), typeof(View), new FrameworkPropertyMetadata(InitialActionTarget, FrameworkPropertyMetadataOptions.Inherits, (d, e) => - { - // Also set a binding proxy if we can, in case there's something weird in the way - var frameworkElement = d as FrameworkElement; - if (frameworkElement == null) - return; - - // Don't set if it's been set already - var currentValue = (BindingProxy)d.GetValue(BackupActionTargetBindingProxyProperty); - if (currentValue != null && currentValue.Data == e.NewValue) - return; - - var bindingProxy = new BindingProxy() - { - Data = e.NewValue, - }; - frameworkElement.Resources[ActionTargetProxyResourceKey] = bindingProxy; - })); - - internal static readonly DependencyProperty BackupActionTargetBindingProxyProperty = - DependencyProperty.RegisterAttached("BackupActionTargetBindingProxy", typeof(BindingProxy), typeof(View), new PropertyMetadata(null)); - + DependencyProperty.RegisterAttached("ActionTarget", typeof(object), typeof(View), new FrameworkPropertyMetadata(InitialActionTarget)); /// /// Fetch the ViewModel currently associated with a given object @@ -129,6 +108,47 @@ namespace Stylet.Xaml } })); + /// + /// Set the ViewModel which can be subsequently retrieved using {s:ViewModel} + /// + /// View to store the ViewModel for + /// ViewModel to store + public static void SetViewModel(FrameworkElement view, object viewModel) + { + var bindingProxy = new BindingProxy() + { + Data = viewModel, + }; + view.Resources[ViewModelProxyResourceKey] = bindingProxy; + } + + /// + /// Fetch a binding which can be used to retrieve the ViewModel associated with a View + /// + /// View to fetch the ViewModel for + /// Binding which can retrieve the ViewModel + public static Binding GetBindingToViewModel(DependencyObject view) + { + if (view.GetValue(ViewModelProxyProperty) == null) + { + var resource = new DynamicResourceExtension(ViewModelProxyResourceKey).ProvideValue(null); + view.SetValue(ViewModelProxyProperty, resource); + } + + var binding = new Binding() + { + Source = view, + Path = new PropertyPath(View.ViewModelProxyProperty), + Mode = BindingMode.OneWay, + Converter = BindingProxyToValueConverter.Instance, + }; + + return binding; + } + + internal static readonly DependencyProperty ViewModelProxyProperty = + DependencyProperty.RegisterAttached("ViewModelProxy", typeof(BindingProxy), typeof(View), new PropertyMetadata(null)); + /// /// Helper to set the Content property of a given object to a particular View /// diff --git a/Stylet/Xaml/ViewModelExtension.cs b/Stylet/Xaml/ViewModelExtension.cs new file mode 100644 index 0000000..1542875 --- /dev/null +++ b/Stylet/Xaml/ViewModelExtension.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; +using System.Windows.Markup; + +namespace Stylet.Xaml +{ + /// + /// MarkupExtension which can retrieve the ViewModel for the current View, if available + /// + public class ViewModelExtension : MarkupExtension + { + /// + /// Instantiates a new instsance of the class + /// + public ViewModelExtension() + { + } + + /// + /// When implemented in a derived class, returns an object that is provided as the + /// value of the target property for this markup extension. + /// + /// A service provider helper that can provide services for the markup extension. + /// The object value to set on the property where the extension is applied. + public override object ProvideValue(IServiceProvider serviceProvider) + { + var valueService = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + var targetObjectAsDependencyObject = valueService.TargetObject as DependencyObject; + if (targetObjectAsDependencyObject == null) + return this; + + return View.GetBindingToViewModel(targetObjectAsDependencyObject).ProvideValue(serviceProvider); + } + } +} diff --git a/StyletIntegrationTests/ShellView.xaml b/StyletIntegrationTests/ShellView.xaml index db03844..81710e7 100644 --- a/StyletIntegrationTests/ShellView.xaml +++ b/StyletIntegrationTests/ShellView.xaml @@ -41,7 +41,7 @@ - + diff --git a/StyletIntegrationTests/ShellViewModel.cs b/StyletIntegrationTests/ShellViewModel.cs index 4c2f84f..65cb049 100644 --- a/StyletIntegrationTests/ShellViewModel.cs +++ b/StyletIntegrationTests/ShellViewModel.cs @@ -12,6 +12,8 @@ namespace StyletIntegrationTests { private IWindowManager windowManager; + public string Foo => "Foo"; + public ShellViewModel(IWindowManager windowManager) { this.windowManager = windowManager;