diff --git a/Stylet/ViewManager.cs b/Stylet/ViewManager.cs index 51f4a21..c608828 100644 --- a/Stylet/ViewManager.cs +++ b/Stylet/ViewManager.cs @@ -246,7 +246,7 @@ namespace Stylet if (viewAsFrameworkElement != null) { logger.Info("Setting {0}'s DataContext to {1}", view, viewModel); - View.SetDataContext(viewAsFrameworkElement, viewModel); + viewAsFrameworkElement.DataContext = viewModel; } var viewModelAsViewAware = viewModel as IViewAware; diff --git a/Stylet/Xaml/ActionBase.cs b/Stylet/Xaml/ActionBase.cs index 85cf18f..a66819c 100644 --- a/Stylet/Xaml/ActionBase.cs +++ b/Stylet/Xaml/ActionBase.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Runtime.ExceptionServices; using System.Windows; using System.Windows.Data; +using System.Globalization; namespace Stylet.Xaml { @@ -69,13 +70,29 @@ namespace Stylet.Xaml this.ActionNonExistentBehaviour = actionNonExistentBehaviour; this.logger = logger; - var binding = new Binding() + var multiBinding = new MultiBinding(); + multiBinding.Converter = new ActionTargetMultiValueConverter(); + multiBinding.Bindings.Add(new Binding() { Path = new PropertyPath(View.ActionTargetProperty), Mode = BindingMode.OneWay, Source = this.Subject, - }; - BindingOperations.SetBinding(this, targetProperty, binding); + }); + multiBinding.Bindings.Add(new Binding() + { + Path = new PropertyPath(View.BackupActionTargetBindingProxyProperty), + Mode = BindingMode.OneWay, + Source = this.Subject, + Converter = new BindingProxyToValueConverter(), + }); + + //var binding = new Binding() + //{ + // Path = new PropertyPath(View.ActionTargetProperty), + // Mode = BindingMode.OneWay, + // Source = this.Subject, + //}; + BindingOperations.SetBinding(this, targetProperty, multiBinding); } private void UpdateActionTarget(object oldTarget, object newTarget) @@ -196,5 +213,24 @@ namespace Stylet.Xaml ExceptionDispatchInfo.Capture(e.InnerException).Throw(); } } + + private class ActionTargetMultiValueConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + foreach (var value in values) + { + if (value != View.InitialActionTarget) + return value; + } + + return View.InitialActionTarget; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new InvalidOperationException(); + } + } } } diff --git a/Stylet/Xaml/ActionExtension.cs b/Stylet/Xaml/ActionExtension.cs index d4d24fd..3ab7728 100644 --- a/Stylet/Xaml/ActionExtension.cs +++ b/Stylet/Xaml/ActionExtension.cs @@ -103,20 +103,26 @@ namespace Stylet.Xaml // Seems this is the case when we're in a template. We'll get called again properly in a second. // http://social.msdn.microsoft.com/Forums/vstudio/en-US/a9ead3d5-a4e4-4f9c-b507-b7a7d530c6a9/gaining-access-to-target-object-instead-of-shareddp-in-custom-markupextensions-providevalue-method?forum=wpf - if (!(valueService.TargetObject is DependencyObject)) + var targetObjectAsDependencyObject = valueService.TargetObject as DependencyObject; + 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)) { // If they're in design mode and haven't set View.ActionTarget, default to looking sensible - return new CommandAction((DependencyObject)valueService.TargetObject, this.Method, this.CommandNullTargetBehaviour, this.CommandActionNotFoundBehaviour); + return new CommandAction(targetObjectAsDependencyObject, this.Method, this.CommandNullTargetBehaviour, this.CommandActionNotFoundBehaviour); } var propertyAsEventInfo = valueService.TargetProperty as EventInfo; if (propertyAsEventInfo != null) { - var ec = new EventAction((DependencyObject)valueService.TargetObject, propertyAsEventInfo.EventHandlerType, this.Method, this.EventNullTargetBehaviour, this.EventActionNotFoundBehaviour); + var ec = new EventAction(targetObjectAsDependencyObject, propertyAsEventInfo.EventHandlerType, this.Method, this.EventNullTargetBehaviour, this.EventActionNotFoundBehaviour); return ec.GetDelegate(); } @@ -127,7 +133,7 @@ namespace Stylet.Xaml var parameters = propertyAsMethodInfo.GetParameters(); if (parameters.Length == 2 && typeof(Delegate).IsAssignableFrom(parameters[1].ParameterType)) { - var ec = new EventAction((DependencyObject)valueService.TargetObject, parameters[1].ParameterType, this.Method, this.EventNullTargetBehaviour, this.EventActionNotFoundBehaviour); + var ec = new EventAction(targetObjectAsDependencyObject, parameters[1].ParameterType, this.Method, this.EventNullTargetBehaviour, this.EventActionNotFoundBehaviour); return ec.GetDelegate(); } } diff --git a/Stylet/Xaml/BindingProxy.cs b/Stylet/Xaml/BindingProxy.cs index 82ea15d..2f50efd 100644 --- a/Stylet/Xaml/BindingProxy.cs +++ b/Stylet/Xaml/BindingProxy.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; +using System.Windows.Data; namespace Stylet.Xaml { @@ -23,4 +25,21 @@ namespace Stylet.Xaml public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new PropertyMetadata(null)); } + + internal class BindingProxyToValueConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var proxy = value as BindingProxy; + if (proxy != null) + return proxy.Data; + + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new InvalidOperationException(); + } + } } diff --git a/Stylet/Xaml/View.cs b/Stylet/Xaml/View.cs index 034c824..1b29223 100644 --- a/Stylet/Xaml/View.cs +++ b/Stylet/Xaml/View.cs @@ -19,8 +19,6 @@ namespace Stylet.Xaml internal const string ActionTargetProxyResourceKey = "8b7cb732-8a14-4813-a580-b1f3cccea7b7"; - internal const string DataContextProxyResourceKey = "982a3cb4-68b8-464f-9f65-8835d86d94dd"; - /// /// Initial value of the ActionTarget property. /// This can be used as a marker - if the property has this value, it hasn't yet been assigned to anything else. @@ -34,23 +32,7 @@ namespace Stylet.Xaml /// ActionTarget associated with the given object public static object GetActionTarget(DependencyObject obj) { - var actionTarget = obj.GetValue(ActionTargetProperty); - - if (actionTarget == InitialActionTarget) - { - var frameworkElement = obj as FrameworkElement; - if (frameworkElement != null) - { - var bindingProxy = frameworkElement.TryFindResource(ActionTargetProxyResourceKey) as BindingProxy; - if (bindingProxy != null) - { - logger.Info("ActionTarget not set on object {0}, but a BindingProxy containing an ActionTarget was, so using that", obj); - actionTarget = bindingProxy.Data; - } - } - } - - return actionTarget; + return obj.GetValue(ActionTargetProperty); } /// @@ -74,14 +56,22 @@ namespace Stylet.Xaml 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, }; - bindingProxy.Freeze(); frameworkElement.Resources[ActionTargetProxyResourceKey] = bindingProxy; })); + internal static readonly DependencyProperty BackupActionTargetBindingProxyProperty = + DependencyProperty.RegisterAttached("BackupActionTargetBindingProxy", typeof(BindingProxy), typeof(View), new PropertyMetadata(null)); + + /// /// Fetch the ViewModel currently associated with a given object /// @@ -139,44 +129,6 @@ namespace Stylet.Xaml } })); - - internal static void SetDataContext(FrameworkElement obj, object value) - { - obj.DataContext = value; - var bindingProxy = new BindingProxy() - { - Data = value, - }; - obj.Resources[DataContextProxyResourceKey] = bindingProxy; - } - - public static bool GetRestoreDataContext(DependencyObject obj) - { - return (bool)obj.GetValue(RestoreDataContextProperty); - } - - public static void SetRestoreDataContext(DependencyObject obj, bool value) - { - obj.SetValue(RestoreDataContextProperty, value); - } - - public static readonly DependencyProperty RestoreDataContextProperty = - DependencyProperty.RegisterAttached("RestoreDataContext", typeof(bool), typeof(View), new PropertyMetadata(false, (d, e) => - { - if (!(e.NewValue is bool) || !(bool)e.NewValue) - return; - - var frameworkElement = d as FrameworkElement; - if (frameworkElement == null) - return; - - var bindingProxy = frameworkElement.Resources[DataContextProxyResourceKey] as BindingProxy; - if (bindingProxy == null) - return; - - frameworkElement.DataContext = bindingProxy.Data; - })); - /// /// Helper to set the Content property of a given object to a particular View /// diff --git a/StyletIntegrationTests/ShellView.xaml b/StyletIntegrationTests/ShellView.xaml index 7206f19..db03844 100644 --- a/StyletIntegrationTests/ShellView.xaml +++ b/StyletIntegrationTests/ShellView.xaml @@ -6,13 +6,7 @@ - +