How do I data bind the result of a view-model method to a TextBox property?

6.6k views Asked by At

In my view-model and model I have a method with the signature of bool IsPropertyReadOnly(string propertyName). This method determines if the currently logged in user can edit a propery value. A few users will be able to edit property values and most of the others will have read-only access.

Instead of creating a property to return the read-only status of each of the model's properties, I want to bind the result of the IsPropertyReadOny to the TextBox.IsReadOnly property.

This is how I envision the syntax:

<TextBox Text="{Binding Address, Mode=TwoWay}" 
         IsReadOnly="{Binding MethodName=IsPropertyReadOnly MethodParameter=Address}"
/>

The DataContext contains the view-model, so basically I need to bind IsReadOnly to the result of the call ((Class)this.DataContext).IsPropertyReadOnly("Address")

There is much documentation in using an ObjectDataProvider, but the object data provider creates a new object instance which is not what I want. Moreover, to use an existing instance I must make the assignment in code-behind. Again, not what I want to do.

From my research, it seems that a solution that inherits from Binding or MarkupExtension is better suited to my needs.

Any help would be greatly appreciated.

3

There are 3 answers

2
Seekeer On BEST ANSWER

I suggest using a converter. Here is example. Suppose you have a simple ViewModel class:

class ViewModel
{
    public string Read
    { get; set; }

    public string ReadWrite
    { get; set; }

    public bool IsPropertyReadOnly(string propertyName)
    {
        return propertyName != "ReadWrite";
    }
}

To solve your problem you need to write a converter, such as:

public class Converter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var vm = value as ViewModel;
        var functionName = (string)parameter;

        var result = vm.IsPropertyReadOnly(functionName);
        return result;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException("This method should never be called");
    }
}

And that's all; now you can use this converter in XAML, like:

<Window.Resources>
    <temp:Converter x:Key="ReadOnlyMethodConverter"/>
</Window.Resources>
<StackPanel>
    <TextBox Text="{Binding Read, Mode=TwoWay}" 
             IsReadOnly="{Binding Path=.,
        Converter={StaticResource ReadOnlyMethodConverter}, ConverterParameter=Read}"
    />
    <TextBox Text="{Binding ReadWrite, Mode=TwoWay}" 
             IsReadOnly="{Binding Path=.,
        Converter={StaticResource ReadOnlyMethodConverter}, ConverterParameter=ReadWrite}"
    />
</StackPanel>

And in code-behind we just create ViewModel and set it as DataContext:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }
}
1
sellmeadog On

You should be able to do this by using an ObjectDataProvider to execute the method and then bind the attached property to the provider's return value.

First you'll need to configure the provider as a resource:

<Window.Resources>
  <ObjectDataProvider x:Key="readOnlyProvider" ...>
    <ObjectDataProvider.MethodParameters>
      ...
    </ObjectDataProvider.MethodParameters>
  </ObjectDataProvider>
</Window.Resources>

Then use the provider as the source of your attached property binding:

<TextBox Text="{Binding PoolNum, Mode=OneWay}" Windows:AttachedProperties.IsReadOnlyOn="{Binding Source={StaticResource readOnlyProvider}}" />

The tricky part of this process will be "passing" a value to the ObjectDataProviders.MethodParameters. This can be done in XAML and there are several resources out there to show you how it's done; here's one to get started: http://weblogs.asp.net/psheriff/archive/2010/02/23/bind-objectdataprovider-method-parameters-in-wpf.aspx

UPDATE

Per your comment, here are two ways the ObjectDataProvider can execute the method on your view's DataContext without creating a new object.

First, make your view model method static and use the ObjectType property:

<ObjectDataProvider x:Key="readOnlyProvider"
  ObjectType="{x:local MyDataContext}"
  MethodName="IsPropertyReadOnly">
  ...
</ObjectDataProvider>

Or, set the ObjectInstance of the provider to the view's DataContext when the view loads:

public class MyWindow : Window
{
  public MyWindow()
  {
    InitializeComponent();

    var readOnlyProvider = this.Resources["readOnlyProvider"] as ObjectDataProvider;
    readOnlyProvider.ObjectInstance = this.DataContext;
  }
}

There is no way to bind to methods in XAML and the only workarounds I know of make use of the ObjectDataProvider.

2
brunnerh On

Moreover, to use an existing instance I must make the assignment in code-behind. Again, not what I want to do.

That is not true, your choices however will be limited.


How about indexers?

private readonly Dictionary<string, bool> _PropertyReadOnlyDictionary = new Dictionary<string, bool>();
public Dictionary<string, bool> PropertyReadOnlyDictionary { get { return _PropertyReadOnlyDictionary; } }
<TextBox Text="{Binding Address, Mode=TwoWay}"
        IsReadOnly="{Binding PropertyReadOnlyDictionary[Address]}" />

You could of course wrap your method in a new class which allows access via an indexer as well if you don't want to use a dictionary.

private readonly PropertyIsReadOnlyResolver _PropertyIsReadOnlyResolver = new PropertyIsReadOnlyResolver();
public PropertyIsReadOnlyResolver PropertyIsReadOnlyResolver { get { return _PropertyIsReadOnlyResolver; } }
public class PropertyIsReadOnlyResolver
{
    public bool this[string propertyName]
    {
        get
        {
            return IsPropertyReadOnly(propertyName);
        }
    }

    public bool IsPropertyReadOnly(string propertyName)
    {
        //...
    }
}
<TextBox Text="{Binding Address, Mode=TwoWay}"
        IsReadOnly="{Binding PropertyIsReadOnlyResolver[Address]}" />