Refactor to add {s:ViewModel}, and use that for actions

This commit is contained in:
Antony Male 2015-09-24 12:12:25 +01:00
parent 540482a5f4
commit 4d767f0364
9 changed files with 92 additions and 37 deletions

View File

@ -131,6 +131,7 @@
<Compile Include="Xaml\View.cs" />
<Compile Include="ViewManager.cs" />
<Compile Include="WindowManager.cs" />
<Compile Include="Xaml\ViewModelExtension.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="MessageBoxView.xaml">

View File

@ -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;

View File

@ -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()
//{

View File

@ -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))
{

View File

@ -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;

View File

@ -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";
/// <summary>
/// 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.
/// </summary>
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));
/// <summary>
/// Fetch the ViewModel currently associated with a given object
@ -129,6 +108,47 @@ namespace Stylet.Xaml
}
}));
/// <summary>
/// Set the ViewModel which can be subsequently retrieved using {s:ViewModel}
/// </summary>
/// <param name="view">View to store the ViewModel for</param>
/// <param name="viewModel">ViewModel to store</param>
public static void SetViewModel(FrameworkElement view, object viewModel)
{
var bindingProxy = new BindingProxy()
{
Data = viewModel,
};
view.Resources[ViewModelProxyResourceKey] = bindingProxy;
}
/// <summary>
/// Fetch a binding which can be used to retrieve the ViewModel associated with a View
/// </summary>
/// <param name="view">View to fetch the ViewModel for</param>
/// <returns>Binding which can retrieve the ViewModel</returns>
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));
/// <summary>
/// Helper to set the Content property of a given object to a particular View
/// </summary>

View File

@ -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
{
/// <summary>
/// MarkupExtension which can retrieve the ViewModel for the current View, if available
/// </summary>
public class ViewModelExtension : MarkupExtension
{
/// <summary>
/// Instantiates a new instsance of the <see cref="ViewModelExtension"/> class
/// </summary>
public ViewModelExtension()
{
}
/// <summary>
/// When implemented in a derived class, returns an object that is provided as the
/// value of the target property for this markup extension.
/// </summary>
/// <param name="serviceProvider">A service provider helper that can provide services for the markup extension.</param>
/// <returns>The object value to set on the property where the extension is applied.</returns>
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);
}
}
}

View File

@ -41,7 +41,7 @@
</TextBox.InputBindings>
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Click Here" Command="{s:Action ShowActionTargetSaved}"/>
<MenuItem Header="{Binding Foo}" DataContext="{s:ViewModel}" Command="{s:Action ShowActionTargetSaved}"/>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>

View File

@ -12,6 +12,8 @@ namespace StyletIntegrationTests
{
private IWindowManager windowManager;
public string Foo => "Foo";
public ShellViewModel(IWindowManager windowManager)
{
this.windowManager = windowManager;