Skip to content

Instantly share code, notes, and snippets.

@jsadeli
Created July 15, 2011 14:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsadeli/1084807 to your computer and use it in GitHub Desktop.
Save jsadeli/1084807 to your computer and use it in GitHub Desktop.
XAML markup extension to bind directly to root object's datacontext
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,
};
}
}
}
@Mike-E-angelo
Copy link

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment