Created
July 15, 2011 14:37
-
-
Save jsadeli/1084807 to your computer and use it in GitHub Desktop.
XAML markup extension to bind directly to root object's datacontext
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.ComponentModel; | |
using System.Globalization; | |
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Data; | |
using System.Windows.Markup; | |
using System.Xaml; | |
namespace YourNamespace.MarkupExtensions | |
{ | |
/// <summary> | |
/// XAML markup extension to bind directly to root datacontext. | |
/// </summary> | |
/// <example> | |
/// Example usage in XAML: | |
/// <code> | |
/// <![CDATA[ | |
/// <UserControl x:Class="MyProject.Views.SampleView" | |
/// xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
/// xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
/// xmlns:m="clr-namespace:YourNamespace.MarkupExtensions;assembly=YourAssemblyName" | |
/// .... > | |
/// <Grid x:Name="LayoutRoot" Background="White"> | |
/// <ListBox ItemsSource="{Binding Names}"> | |
/// <ListBox.ItemTemplate> | |
/// <DataTemplate> | |
/// <StackPanel Orientation="Horizontal"> | |
/// <TextBlock Text="{Binding}"/> | |
/// <Button Content="{m:RootBinding Path=ButtonContent}" | |
/// Command="{m:RootBinding Path=MessageBoxCommand}"/> | |
/// </StackPanel> | |
/// </DataTemplate> | |
/// </ListBox.ItemTemplate> | |
/// </ListBox> | |
/// </Grid> | |
/// </UserControl> | |
/// ]]> | |
/// </code> | |
/// </example> | |
/// <author>Damian Schenkelman</author> | |
/// <author>Jeffrey Sadeli</author> | |
/// <seealso href="http://blogs.southworks.net/dschenkelman/2011/06/26/binding-to-view-model-properties-in-data-templates-the-rootbinding-markup-extension/"/> | |
/// <seealso href="http://msdn.microsoft.com/en-us/library/system.windows.data.binding.aspx"/> | |
/// <seealso href="http://msdn.microsoft.com/en-us/library/ms750413.aspx"/> | |
/// <seealso href="http://loosexaml.wordpress.com/2009/04/09/reference-to-self-in-xaml/"/> | |
public class RootBindingExtension : MarkupExtension | |
{ | |
#region property getters and setters | |
private bool _requiresFrameworkElementRootObject = true; | |
/// <summary> | |
/// Gets or sets a value indicating whether root object is required to be of type <see cref="FrameworkElement"/>. | |
/// Default value is <c>True</c>. | |
/// </summary> | |
/// <remarks> | |
/// In typical scenario, you want to leave this option as the default (<c>True</c>). | |
/// Change this option to <c>False</c> if the designer (e.g. Visual Studio) has problems. | |
/// </remarks> | |
[DefaultValue(true)] | |
public bool RequiresFrameworkElementRootObject | |
{ | |
get { return _requiresFrameworkElementRootObject; } | |
set { _requiresFrameworkElementRootObject = value; } | |
} | |
// ---------------------------------------------------------------------------------------- | |
/// <summary> | |
/// Gets or sets the binding path. | |
/// </summary> | |
[ConstructorArgument("path")] | |
public string Path { get; set; } | |
private BindingMode _mode = BindingMode.Default; | |
/// <summary> | |
/// Gets or sets the binding mode. | |
/// Default value is <see cref="System.Windows.Data.BindingMode.Default"/>. | |
/// </summary> | |
[DefaultValue(BindingMode.Default)] | |
public BindingMode Mode | |
{ | |
get { return _mode; } | |
set { _mode = value; } | |
} | |
private UpdateSourceTrigger _updateSourceTrigger = UpdateSourceTrigger.Default; | |
/// <summary> | |
/// Gets or sets the binding update source trigger. | |
/// Default value is <see cref="System.Windows.Data.UpdateSourceTrigger.Default"/>. | |
/// </summary> | |
[DefaultValue(UpdateSourceTrigger.Default)] | |
public UpdateSourceTrigger UpdateSourceTrigger | |
{ | |
get { return _updateSourceTrigger; } | |
set { _updateSourceTrigger = value; } | |
} | |
/// <summary> | |
/// Gets or sets a value that indicates whether to raise the <see cref="Binding.SourceUpdatedEvent"/> | |
/// when a value is transferred from the binding target to the binding source. | |
/// Default value is <c>False</c>. | |
/// </summary> | |
[DefaultValue(false)] | |
public bool NotifyOnSourceUpdated { get; set; } | |
/// <summary> | |
/// Gets or sets a value that indicates whether to raise the <see cref="Binding.TargetUpdatedEvent"/> | |
/// when a value is transferred from the binding target to the binding source. | |
/// Default value is <c>False</c>. | |
/// </summary> | |
[DefaultValue(false)] | |
public bool NotifyOnTargetUpdated { get; set; } | |
/// <summary> | |
/// Gets or sets a value that indicates whether to raise the <see cref="Validation.ErrorEvent"/> | |
/// attached event on the bound object. | |
/// Default value is <c>False</c>. | |
/// </summary> | |
[DefaultValue(false)] | |
public bool NotifyOnValidationError { get; set; } | |
/// <summary> | |
/// Gets or sets a value that indicates whether to include the <see cref="DataErrorValidationRule"/>. | |
/// Default value is <c>False</c>. | |
/// </summary> | |
[DefaultValue(false)] | |
public bool ValidatesOnDataErrors { get; set; } | |
/// <summary> | |
/// Gets or sets a value that indicates whether to include the <see cref="ExceptionValidationRule"/>. | |
/// Default value is <c>False</c>. | |
/// </summary> | |
[DefaultValue(false)] | |
public bool ValidatesOnExceptions { get; set; } | |
/// <summary> | |
/// Gets or sets a value indicating whether this instance is async. | |
/// Default value is <c>False</c>. | |
/// </summary> | |
[DefaultValue(false)] | |
public bool IsAsync { get; set; } | |
private string _bindingGroupName = ""; | |
/// <summary> | |
/// Gets or sets the name of the <see cref="BindingGroup"/> to which this binding belongs. | |
/// Default value is empty string. | |
/// </summary> | |
[DefaultValue("")] | |
public string BindingGroupName | |
{ | |
get { return _bindingGroupName; } | |
set { _bindingGroupName = value; } | |
} | |
/// <summary> | |
/// Gets or sets the converter to use. | |
/// Default value is <c>null</c>. | |
/// </summary> | |
[DefaultValue(null)] | |
public IValueConverter Converter { get; set; } | |
/// <summary> | |
/// Gets or sets the culture to which to evaluate the converter. | |
/// Default value is <c>null</c>. | |
/// </summary> | |
[DefaultValue(null)] | |
public CultureInfo ConverterCulture { get; set; } | |
/// <summary> | |
/// Gets or sets the parameter to pass to converter. | |
/// Default value is <c>null</c>. | |
/// </summary> | |
[DefaultValue(null)] | |
public object ConverterParameter { get; set; } | |
/// <summary> | |
/// Gets or sets a string that specifies how to format the binding if it displays the bound value as a string. | |
/// Default value is <c>null</c>. | |
/// </summary> | |
[DefaultValue(null)] | |
public string StringFormat { get; set; } | |
private object _fallbackValue = DependencyProperty.UnsetValue; | |
/// <summary> | |
/// Gets or sets the value to use when the binding is unable to return a value. | |
/// Default value is <see cref="DependencyProperty.UnsetValue"/>. | |
/// </summary> | |
public object FallbackValue | |
{ | |
get { return _fallbackValue; } | |
set { _fallbackValue = value; } | |
} | |
#endregion | |
#region constructors | |
/// <summary> | |
/// Initializes a new instance of the <see cref="RootBindingExtension"/> class. | |
/// </summary> | |
public RootBindingExtension() | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="RootBindingExtension"/> class. | |
/// </summary> | |
/// <param name="path">The binding path.</param> | |
public RootBindingExtension(string path) | |
: this() | |
{ | |
this.Path = path; | |
} | |
#endregion | |
/// <summary> | |
/// When implemented in a derived class, returns an object that is set as the value of the target property | |
/// for this markup extension. | |
/// </summary> | |
/// <param name="serviceProvider">Object 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) | |
{ | |
IRootObjectProvider rootProvider = (IRootObjectProvider) serviceProvider.GetService(typeof(IRootObjectProvider)); | |
object rootObject = rootProvider.RootObject; | |
if (rootObject == null) | |
throw new InvalidOperationException("Root object is null."); | |
if (this.RequiresFrameworkElementRootObject) | |
{ | |
FrameworkElement element = rootObject as FrameworkElement; | |
if (element == null) | |
throw new InvalidOperationException(string.Format("Root object's '{0}' type is not of type FrameworkElement.", rootObject.GetType())); | |
} | |
return ProvideValueHelper(rootObject.GetType()); | |
} | |
/// <summary> | |
/// Helper method for provide value to create binding object. | |
/// </summary> | |
/// <param name="ancestorType">Type of the ancestor.</param> | |
/// <returns>The binding object.</returns> | |
private Binding ProvideValueHelper(Type ancestorType) | |
{ | |
string bindingPath = string.IsNullOrEmpty(this.Path) ? "DataContext" : "DataContext." + this.Path; | |
return new Binding() | |
{ | |
Path = new PropertyPath(bindingPath), | |
Mode = this.Mode, | |
UpdateSourceTrigger = this.UpdateSourceTrigger, | |
NotifyOnSourceUpdated = this.NotifyOnSourceUpdated, | |
NotifyOnTargetUpdated = this.NotifyOnTargetUpdated, | |
NotifyOnValidationError = this.NotifyOnValidationError, | |
ValidatesOnDataErrors = this.ValidatesOnDataErrors, | |
ValidatesOnExceptions = this.ValidatesOnExceptions, | |
IsAsync = this.IsAsync, | |
BindingGroupName = this.BindingGroupName, | |
RelativeSource = new RelativeSource() | |
{ | |
Mode = RelativeSourceMode.FindAncestor, | |
AncestorType = ancestorType, | |
}, | |
Converter = this.Converter, | |
ConverterCulture = this.ConverterCulture, | |
ConverterParameter = this.ConverterParameter, | |
StringFormat = this.StringFormat, | |
FallbackValue = this.FallbackValue, | |
}; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Man this is really awesome. I have something similar but my DataTemplate is in a ResourceDictionary and the RootObject is resolving to this instead of the root page that is serializing the Xaml. Any ideas on how to access the page from a ResourceDictionary? Nothing that I can see...