1

I use the following NEW BindableViewModel class(Old one I also post at the bottom of this topic) to make a object observable(from MS sample):

public abstract class BindableBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

I want use this to create a ViewModel in order to bind those TextBox Controls, such as this:

public class TextBoxModel : BindableBase
{
    private string _text = string.Empty;
    // I'm going to bind this field to TextBox.Text property
    public string Text { get { return _text; } set { SetProperty(ref _text, value); } }

    private bool _isEnabled = true;
    // I'm going to bind this field to TextBox.IsEnabled property
    public bool IsEnabled { get { return _isEnabled; } set { SetProperty(ref _isEnabled, value); } }
}

Then, in my Page ViewModel, I use TextBoxModel to define fields:

public class Page1ViewModel : BindableBase
{
    private TextBoxModel _firstName = null;
    public TextBoxModel FirstName { get { return _firstName; } set { 
        if (SetProperty(ref _firstName, value))
            {
                SubmitCommand.RaiseCanExecuteChanged();
            } } }

    private TextBoxModel _surname = null;
    public TextBoxModel Surname { get { return _surname; } set { 
        if (SetProperty(ref _surname, value))
            {
                SubmitCommand.RaiseCanExecuteChanged();
            } } }

    private DelegateCommand _submitCommand = null;
    public DelegateCommand SubmitCommand { get { return _submitCommand??(_submitCommand=new DelegateCommand(SubmitExecute, SubmitCanExecute)); } }
    private void SubmitExecute()
    {
        MessageBox.Show($"{FirstName.Text} {Surname.Text}");
    }
    private bool SubmitCanExecute()
    {
        if(string.IsNullOrEmpty(FirstName.Text))
            return false;
        else if(string.IsNullOrEmpty(Surname.Text))
            return false;
        else
            return true;
    }
}

In my XAML View, I set textbox binding as usual:

<TextBox Text="{Binding FirstName.Text, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding FirstName.IsEnabled}"/>
<TextBox Text="{Binding Surname.Text, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding Surname.IsEnabled}"/>
<Button Content="Submit" Command{Binding SubmitCommand} />

When I run this, I've found text changing is not work. It didn't trigger if(SetProperty(ref _firstName, value)) or if(SetProperty(ref _surname, value)) in the setter. If I don't combine the properties in a TextBoxModel, everything works fine. If I use OLD ViewModelBase, TextBoxModel worked fine, too.

So I think I must missed something when using the NEW BindableBase class? Looking for your help, thanks!

OLD ViewModelBase:

[Obsolete("Please use BindableBase。")]
public abstract class ObservableModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public static string GetPropertyName<T>(System.Linq.Expressions.Expression<Func<T>> e)
    {
        var member = (System.Linq.Expressions.MemberExpression)e.Body;
        return member.Member.Name;
    }

    protected virtual void RaisePropertyChanged<T>
        (System.Linq.Expressions.Expression<Func<T>> propertyExpression)
    {
        RaisePropertyChanged(GetPropertyName(propertyExpression));
    }

    protected void RaisePropertyChanged(String propertyName)
    {
        System.ComponentModel.PropertyChangedEventHandler temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}
3
  • I don't see how this could work. You're not setting FirstName or Surname when the text changes (or, in fact, at all!), so SetProperty won't be called. You need to observe the property changes within TextBoxModel. Commented May 16, 2016 at 9:15
  • @CharlesMager what do you mean? I do changed the Text property of FirstName object, but it didn't trigger if(SetProperty(ref _firstName, value)). But, it triggered Text property inside of FirstName object. Why didn't trigger if(SetProperty(ref _firstName, value)) even Text property inside of it triggered? Commented May 16, 2016 at 9:21
  • Your binding is to the Text property, which is a string. Trying to set that value will not magically new up a TextBoxModel and set FirstName as well. It will just silently fail because FirstName is null. Commented May 16, 2016 at 9:26

2 Answers 2

2

What you trying to do makes sense, but how you are doing it is needlessly complicated. You have a TextBoxModel which is trying to mimic a real TextBox which you simply don't need to do, I think your approach is all wrong.

Firstly, the Model represents data, not UI. Instead of creating a TextBoxModel that represents a TextBox, you should instead create a model that represents your data structure, such as a PersonModel, or a UserModel. Here is an example of what I mean:

public class PersonModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Note: You could put INotifyPropertyChanged stuff in the Model, but that isn't really a UI concern, it's just data. The best place for the property changed implementation is in the View Model, which I'll explain further down.

Ok, now the data layer is sorted, you simply need to expose this model to the View via the View Model. Firstly, here is a simple property changed base that I will use for this example:

public abstract class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyOfPropertyChange([CallerMemberName]string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Expose the model properties like this:

public class PersonViewModel : PropertyChangedBase
{
    private PersonModel _Person;

    public string FirstName
    {
        get { return _Person.FirstName; }
        set
        {
            _Person.FirstName = value;
            NotifyOfPropertyChange();
        }
    }

    public string LastName
    {
        get { return _Person.LastName; }
        set
        {
            _Person.LastName = value;
            NotifyOfPropertyChange();
        }
    }

    //TODO: Your command goes here

    public PersonViewModel()
    {
        //TODO: Get your model from somewhere.
        _Person = new PersonModel();
    }
}

Now you can simply bind your TextBox to the FirstName or LastName view model property:

<TextBox Text="{Binding FirstName}" ... />

It is really as simple as that. You do not need to recreate a TextBox in your data layer, in fact all UI concerns should be completely separate from the model layer.

Using this method, you also do not need to worry about raising the CanExecuteChanged method because it's already handled by INotifyPropertyChanged.

Remember, UI is UI, Data is Data.

Sign up to request clarification or add additional context in comments.

1 Comment

hi @MikeEason thank you for your answer but it didn't solve my problem(I think). I have a form contains about 80 fields need to deploy on UI, every field required IsEnabled & IsFocused properties that I need to control them in ViewModel, with Value property itself, that at least 80x3 = 240 properties in my ViewModel. So I decide to combine these properties by same Control for a easy coding, that all.
1

Your binding will never set FirstName or LastName, and nothing else you've shown does either. These will both be null. A binding to Text will only set that value, it won't create a new TextBoxModel and set that.

If you want this to work while organised this way, you'll need to do something like this:

public class Page1ViewModel : BindableBase
{
    private readonly TextBoxModel _firstName = new TextBoxModel();

    public Page1ViewModel()
    {
        _firstName.PropertyChanged += 
            (sender, args) => SubmitCommand.RaiseCanExecuteChanged();
    }

    public TextBoxModel FirstName 
    {
        get { return _firstName; }
    }
}

2 Comments

Hi @CharlesMager, your code worked, thank you very much. Before I accept your answer, I want to understand something: "_firstName.PropertyChanged += (sender, args) => SubmitCommand.RaiseCanExecuteChanged();" this means when _firstName object changed, than call SubmitCommand.RaiseCanExecuteChanged(), it's ok I understand. But, I did add the same code in setter, also means when _firstName object changed, than call SubmitCommand.RaiseCanExecuteChanged(). why mine not work? (I changed the code to _firstName = new TextBoxModel(); to avoid it null at the start)
@OhMyDog It means when a _firstName property changes it will call that method. Putting in the setter means it will only be called when the object is replaced. It will never be replaced (you can see there's no issue caused by removing the setter entirely).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.