Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 361
AMissico Avatar asked Feb 04 '12 00:02

AMissico


2 Answers

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();
    }
}
like image 73
Seekeer Avatar answered Oct 19 '22 01:10

Seekeer


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]}" />
like image 24
H.B. Avatar answered Oct 19 '22 03:10

H.B.