Search code examples
c#wpfdata-bindingfody

Why isn't my button enabling when text is added to the textbox with Fody.PropertyChanged?


I am using Fody.PropertyChanged to try and reduce some of the boilerplate code in my WPF project.

When I tried to create a button click event, it appeared that Fody would not handle this in the same way that it handled the binding between, for example, a label and a string property.

I have a textbox and I want a button to be enabled / disabled depending on whether this textbox has text. My XAML looks like:

<Window x:Class="MyProject.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyProject"
        mc:Ignorable="d">
   <Label Content="Enter text:" HorizontalAlignment="Left" Margin="21,43,0,0" VerticalAlignment="Top"/>

   <TextBox Height="100" 
      TextWrapping="Wrap" 
      Text="{Binding Test}" 
      VerticalAlignment="Top" 
      Padding="5, 3, 1, 1"
      AcceptsReturn="True" Margin="161,10,10,0"/>

   <Button Content="Go" 
      IsEnabled="{Binding Path=GoButtonIsEnabled}"
      Command="{Binding Path=ButtonClick}"
      HorizontalAlignment="Left" 
      Margin="64,158,0,0" 
      VerticalAlignment="Top" Width="75"/>
</Window>

The ViewModel it is bound to is:

class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public ButtonCommandBinding ButtonClick { get; set; }
    public bool GoButtonIsEnabled => !String.IsNullOrWhiteSpace(Test);

    public string Test { get; set; }

    public MainWindowViewModel()
    {
        ButtonClick = new ButtonCommandBinding(OnGoButtonClick);
    }

    private void OnGoButtonClick()
    {
        System.Windows.MessageBox.Show("Hello, world!");
    }

    protected void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The ButtonCommandBinding:

public class ButtonCommandBinding : ICommand
{
    private readonly Action handler;
    private bool isEnabled;

    public ButtonCommandBinding(Action handler)
    {
        this.handler = handler;
    }

    public bool IsEnabled
    {
        get { return isEnabled; }
        set
        {
            if (value != isEnabled)
            {
                isEnabled = value;
                CanExecuteChanged?.Invoke(this, EventArgs.Empty);
            }
        }
    }

    public bool CanExecute(object parameter)
    {
        return IsEnabled;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        if (IsEnabled)
        {
            handler();
        }
    }
}

I believe the reason that typing in the textbox isn't enabling the button is because the property changed notifier isn't being called.. but I am not calling it because I am using Fody. I had not implemented NotifyPropertyChanged in my ViewModel until I tried getting to button click working; before implementing the button click this was handled by Fody (when I was just binding a label to text).

So I have two questions:

  1. Am I misunderstanding, or grossly misusing, the purpose of Fody?
  2. Why doesn't the button enable when the Test property is no longer null or whitespace?

EDIT: Thanks to ANu for telling me to add the [AlsoNotifyFor(nameof(GoButtonIsEnabled))] attribute. However, the issue I now have is that when I load the textbox with text set in the ViewModel:

public MainWindowViewModel()
{
    Test = "Enter text here";
    ButtonClick = new ButtonCommandBinding(OnButtonClick);
    ButtonClick.IsEnabled = true;
}

...and I run the app and then delete the text from the textbox, the Go button doesn't disable until I click it. After I click it, the action doesn't run (as it shouldn't, because textbox is empty) and the button disabled. When I enter text into the textbox, it does not re-enable.

It seems the binding still isn't working properly.

public bool GoButtonIsEnabled => !String.IsNullOrWhiteSpace(Test);

[AlsoNotifyFor(nameof(GoButtonIsEnabled))]
public string Test { get; set; }

public MainWindowViewModel()
{
    Test = "Enter text";
    ButtonClick = new ButtonCommandBinding(OnButtonClick);
    ButtonClick.IsEnabled = true;
}

XAML:

<Button Content="Go" 
   IsEnabled="{Binding Path=GoButtonIsEnabled}"
   Command="{Binding Path=ButtonClick}"
   HorizontalAlignment="Left" 
   Margin="64,158,0,0" 
   VerticalAlignment="Top" Width="75"/>

Solution

  • Fody would inject notify code for each Property as you desired. However, you also want to Notify a depended property(GoButtonIsEnabled) when another property(Text) is changed..

    You would need to use AlsoNotifyFor attribute to allows the injection of notify code that points to a different property.

    [AlsoNotifyFor(nameof(GoButtonIsEnabled ))]
    public string Test { get; set; }