Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I bind the Binding.Path property to underlying data?

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?

like image 587
arconaut Avatar asked Apr 22 '09 16:04

arconaut


2 Answers

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.

like image 60
Martin Harris Avatar answered Oct 07 '22 12:10

Martin Harris


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.

like image 30
gcores Avatar answered Oct 07 '22 12:10

gcores