Playing around with BindingProxy

This commit is contained in:
Antony Male 2015-06-04 12:00:43 +01:00
parent fc7f164c53
commit a703659095
6 changed files with 136 additions and 10 deletions

View File

@ -101,6 +101,7 @@
<Compile Include="Xaml\ActionExtension.cs" />
<Compile Include="BindableCollection.cs" />
<Compile Include="BootstrapperBase.cs" />
<Compile Include="Xaml\BindingProxy.cs" />
<Compile Include="Xaml\BoolToVisibilityConverter.cs" />
<Compile Include="Xaml\CommandAction.cs" />
<Compile Include="Conductor.cs" />

View File

@ -246,7 +246,7 @@ namespace Stylet
if (viewAsFrameworkElement != null)
{
logger.Info("Setting {0}'s DataContext to {1}", view, viewModel);
viewAsFrameworkElement.DataContext = viewModel;
View.SetDataContext(viewAsFrameworkElement, viewModel);
}
var viewModelAsViewAware = viewModel as IViewAware;

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Stylet.Xaml
{
internal class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new PropertyMetadata(null));
}
}

View File

@ -4,6 +4,7 @@ using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using System.Reflection;
using Stylet.Logging;
namespace Stylet.Xaml
{
@ -12,10 +13,13 @@ namespace Stylet.Xaml
/// </summary>
public static class View
{
/// <summary>
/// Key which will be used to retrieve the ViewManager associated with the current application, from application's resources
/// </summary>
public const string ViewManagerResourceKey = "b9a38199-8cb3-4103-8526-c6cfcd089df7";
private static readonly ILogger logger = LogManager.GetLogger(typeof(View));
internal const string ViewManagerResourceKey = "b9a38199-8cb3-4103-8526-c6cfcd089df7";
internal const string ActionTargetProxyResourceKey = "8b7cb732-8a14-4813-a580-b1f3cccea7b7";
internal const string DataContextProxyResourceKey = "982a3cb4-68b8-464f-9f65-8835d86d94dd";
/// <summary>
/// Initial value of the ActionTarget property.
@ -30,7 +34,23 @@ namespace Stylet.Xaml
/// <returns>ActionTarget associated with the given object</returns>
public static object GetActionTarget(DependencyObject obj)
{
return obj.GetValue(ActionTargetProperty);
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;
}
/// <summary>
@ -47,7 +67,20 @@ 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));
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;
var bindingProxy = new BindingProxy()
{
Data = e.NewValue,
};
bindingProxy.Freeze();
frameworkElement.Resources[ActionTargetProxyResourceKey] = bindingProxy;
}));
/// <summary>
/// Fetch the ViewModel currently associated with a given object
@ -106,6 +139,44 @@ 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;
}));
/// <summary>
/// Helper to set the Content property of a given object to a particular View
/// </summary>

View File

@ -6,7 +6,13 @@
<DockPanel LastChildFill="False">
<GroupBox DockPanel.Dock="Top" Header="ShowDialog and DialogResult" Padding="10">
<StackPanel Orientation="Horizontal">
<Button Command="{s:Action ShowDialogAndDialogResult}">Show Dialog</Button>
<Button Command="{s:Action ShowDialogAndDialogResult}" Content="Show Dialog">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Foo" Command="{s:Action Foo}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
<TextBlock Margin="50,0,0,0">Result: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ShowDialogAndDialogResultDialogResult}"/>
</StackPanel>

View File

@ -5,6 +5,7 @@ using Stylet.Xaml;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
namespace StyletUnitTests
@ -51,7 +52,7 @@ namespace StyletUnitTests
public void ModelStores()
{
var obj = new FrameworkElement();
obj.Resources.Add(View.ViewManagerResourceKey, this.viewManager.Object);
obj.Resources.Add("b9a38199-8cb3-4103-8526-c6cfcd089df7", this.viewManager.Object);
View.SetModel(obj, 5);
Assert.AreEqual(5, View.GetModel(obj));
}
@ -60,7 +61,7 @@ namespace StyletUnitTests
public void ChangingModelCallsOnModelChanged()
{
var obj = new FrameworkElement();
obj.Resources.Add(View.ViewManagerResourceKey, this.viewManager.Object);
obj.Resources.Add("b9a38199-8cb3-4103-8526-c6cfcd089df7", this.viewManager.Object);
var model = new object();
View.SetModel(obj, null);
@ -155,5 +156,26 @@ namespace StyletUnitTests
var content = (TextBlock)element.Content;
Assert.AreEqual("View for TestViewModel.SubViewModel", content.Text);
}
[Test]
public void ActionTargetIsRestoredAcrossPopupBoundaries()
{
Execute.InDesignMode = true;
var userControl = new UserControl();
var grid = new Grid();
userControl.Content = grid;
var button = new Button();
grid.Children.Add(button);
var contextMenu = new ContextMenu();
button.ContextMenu = contextMenu;
var actionTarget = new object();
View.SetActionTarget(userControl, actionTarget);
Assert.AreEqual(actionTarget, View.GetActionTarget(button));
}
}
}