views:

1817

answers:

3

I'm trying to bind a TextBlock's Text property in a very dynamic way. I need to get the Path from an underlying object.

Here's the DataTemplate:

<DataTemplate DataType={x:Type local:DummyClass}>
  <TextBlock Text={Binding Path=???} />
</DataTemplate>

The DummyClass object has a property named "FieldValuePath" - the path that needs to be put where the ??? is.

The idea behind this is that the data template is supposed to be a GUI for viewing/editing any property of any object. So it's kind of preferable to be able to declare XAML which would bind some controls (textboxes, textblocks, datepickers, etc) to a given property.

Maybe anyone has any suggestions on how to implement such thing?

+5  A: 

If you create the binding in the code behind then you could get it to work. For example a simple code generated binding is:

Binding binding = new Binding("BindingPath");
binding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(textBoxName, TextBox.TextProperty, binding);

Since the path in this binding ("BindingPath") is just a string, that string could come from any available object.

You'll need to hook into the creation of your data items to set these binding though.


A further possibility based on your comments:

This blog post outlines a way to create a custom binding class by inheriting from MarkupExtension. You may be able to use this as a starting point to wrap my suggestion into a reusable xaml markup for your special binding case.


More thoughts:

Okay, this was an interesting problem, so I decided to spend a little time seeing if I could come up with a working solution. I apologise in advance for the length of the following code samples...

Basing my solution on the blog post I linked to above I created this class:

public class IndirectBinder : MarkupExtension
    {
        public string IndirectProperty { get; set; }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            //try to get bound items for our custom work
            DependencyObject targetObject;
            DependencyProperty targetProperty;
            bool status = TryGetTargetItems(serviceProvider, out targetObject, out targetProperty);

            if (status)
            {
                Control targetControl = targetObject as Control;
                if (targetControl == null) return null;

                //Find the object to take the binding from
                object dataContext = targetControl.DataContext;
                if (dataContext == null) return null;

                //Reflect out the indirect property and get the value
                PropertyInfo pi = dataContext.GetType().GetProperty(IndirectProperty);
                if (pi == null) return null;

                string realProperty = pi.GetValue(dataContext, null) as string;
                if (realProperty == null) return null;

                //Create the binding against the inner property
                Binding binding = new Binding(realProperty);
                binding.Mode = BindingMode.TwoWay;
                BindingOperations.SetBinding(targetObject, targetProperty, binding);

                //Return the initial value of the binding
                PropertyInfo realPi = dataContext.GetType().GetProperty(realProperty);
                if (realPi == null) return null;

                return realPi.GetValue(dataContext, null);

            }

            return null;

        }

        protected virtual bool TryGetTargetItems(IServiceProvider provider, out DependencyObject target, out DependencyProperty dp)
        {
            target = null;
            dp = null;
            if (provider == null) return false;

            //create a binding and assign it to the target
            IProvideValueTarget service = (IProvideValueTarget)provider.GetService(typeof(IProvideValueTarget));
            if (service == null) return false;

            //we need dependency objects / properties
            target = service.TargetObject as DependencyObject;
            dp = service.TargetProperty as DependencyProperty;
            return target != null && dp != null;
        }

You can use this new markup with the following xaml:

<TextBox Text="{local:IndirectBinder IndirectProperty=FieldValuePath}"/>

Where TextBox can be any class that inherits from control and Text can be any dependency property.

Obviously if you need to expose any of the other databinding options (such as one or two way binding) then you'll need to add more properties to the class.

While this is a complicated solution, one advantage that it has over using a converter is that the binding that is finally created is against the actual inner property rather than the object. This means that it correctly reacts to PropertyChanged events.

Martin Harris
well, this is an approach that might work, but i'd really like to be able to create Bindings in XAML, because i'm planning to write have different binding properties depending on the state of the underlying data.But possibly i could indeed try to write all this logic in the code-behind...
arconaut
and besides, it's not only TextBlock that i'm planning to specify binding for. It's supposed to be an editor/viewer for any property on any object. I think i should add to the top post.
arconaut
thanks for the answer, and thanks for the link. i'm going to try this out
arconaut
+1. Very helpful snippet. I adjusted returning the value to support period separated paths. Other than that, it's perfect for my needs.
statenjason
A: 
<DataTemplate DataType={x:Type local:DummyClass}>
  <TextBlock Text={Binding Path=FieldValuePath} />
</DataTemplate>

Should be the correct method. If you're listening for changes in FieldValuePath, you will need to make sure that DummyClass inherits from INotifyPropertyChanged and that the property changed event is being fired when FieldValuePath changes.

Jeff Wain
sorry, but this would set the text to be equal to the FieldValuePath value. So if you have dummyObject.FieldValuePath=="Property1" you'll get TextBlock.Text== "Property1"
arconaut
In that case, are all your properties declared as DependencyProperties? You should be able to bind to the property itsel if that is the case.This might help... http://geekswithblogs.net/thibbard/archive/2008/04/22/wpf-custom-control-dependency-property-gotcha.aspx
Jeff Wain
+2  A: 

I would recommend using a converter:

 <DataTemplate DataType={x:Type local:DummyClass}>
    <TextBlock Text={Binding Converter={StaticResource PropertyNameToValueConverter, ConverterParameter=FieldValuePath}} />
 </DataTemplate>

The converter would get the class and the property name and from there it would return the value using reflection.

gcores
But it would return it only once.
arconaut