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

3

6

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.

Seniority answered 4/2, 2012 at 0:1 Comment(1)
Here might be an answer for your question (the last answer that uses converter): Bind to a method in WPF?Judon
R
4

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();
    }
}
Reinold answered 6/2, 2012 at 12:19 Comment(2)
Even though I used a suggestion by H.B. in another answer, I accepted and voted on this answer because I think it fully answers the question on binding to a method with parameters on the view model.Seniority
I think there were too many answers in the previous comment. :O)Seniority
H
4

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]}" />
Helsinki answered 6/2, 2012 at 14:56 Comment(2)
Interesting, we did use an indexer at first. I decided against it because we need to use IDataErrorInfo. I like the first indexer with the dictionary because it fits into our implementation better.Seniority
We used the PropertyReadOnlyDictionary implementation and it integrated perfectly with our design. Thank you very much.Seniority
S
0

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.

Shadchan answered 4/2, 2012 at 0:12 Comment(1)
But, I do not want the ObjectDataProvider creating a new object. It would be nice if I can set the provider's object.Seniority

© 2022 - 2024 — McMap. All rights reserved.