-1

In WPF, I have a TextBox

<TextBox Name="MyText" Text="{Binding SomeText}" />

Binding its text to SomeText.

public string SomeText { get; set; }

public bool EnableSubmit=> !string.IsNullOrWhiteSpace(SomeText);

And I have written another property: EnableSubmit.

So I created a binding with a button:

<Button Content="Submit" IsEnabled="{Binding EnableSubmit}"/>

That I expect that only when the text in the textbox is not empty shall the button be enabled.

But after I start the app:

wpf

No matter what I do, the button is always disabled.

So what's wrong with my logic? What's the correct way to enable a button with an expression?

thatguy
  • 21,059
  • 6
  • 30
  • 40
Anduin Xue
  • 3,266
  • 2
  • 22
  • 43
  • [How to: Implement Property Change Notification](https://docs.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-property-change-notification?view=netframeworkdesktop-4.8). The introduction says: *To support OneWay or TwoWay binding to enable your binding target properties to automatically reflect the dynamic changes of the binding source (for example, to have the preview pane updated automatically when the user edits a form), your class needs to provide the proper property changed notifications. This example shows how to create a class that implements INotifyPropertyChanged.* – Olivier Jacot-Descombes Feb 27 '21 at 17:09

2 Answers2

2

You are not binding to an expression, you are binding to a computed property. This property is not automatically reevaluated. In your case, it is retrieved once when the button is loaded, never again. You have to implement the INotifyPropertyChanged interface and raise the PropertyChanged event in order to trigger an update of the current value of your property in the user interface. As a side note, it is the same for the SomeText property, if you tried to change it in code, it would not update the text box.

Here is a sample implementation of the interface. Note that you have to trigger the property change for the EnableSubmit property in the setter of the SomeText property, because it depends on it.

public class YourViewModel : INotifyPropertyChanged
{
   // ...your other code.

   private string _someText;
   public string SomeText
   {
      get => _someText;
      set
      {
         if (_someText == value)
            return;
   
         _someText = value;

         // This will trigger an update wherever SomeText is bound in the UI, if you need it
         OnPropertyChanged();

         // This will trigger the IsEnabled binding update
         OnPropertyChanged(nameof(EnableSubmit));
      }
   }

   public event PropertyChangedEventHandler PropertyChanged;
   
   protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
   {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   }
}

In the specific case of enabling the button only if the text is not empty, you could also use a style.

<Button Content="Submit">
   <Button.Style>
      <Style TargetType="{x:Type Button}">
         <Setter Property="IsEnabled" Value="True"/>
         <Style.Triggers>
            <DataTrigger Binding="{Binding Text.Length, ElementName=MyText}" Value="0">
               <Setter Property="IsEnabled" Value="False"/>
            </DataTrigger>
         </Style.Triggers>
      </Style>
   </Button.Style>
</Button>

Since you are using a button which eventually triggers an action, probably the best way is to use a command instead. A command implements the ICommand interface which contains a CanExecute method, which determines if the command can be executed or not. There are many implementations, e.g. RelayCommand in this related post. In your view model, you would add:

public class YourViewModel : INotifyPropertyChanged
{
   public YourViewModel()
   {
      // ...other constructor code.
      
      DoSomething = new RelayCommand(ExecuteDoSomething, CanExecuteDoSomething);
   }

   // ...your other code.
   
   public RelayCommand DoSomething { get; }

   private bool CanExecuteDoSomething(object obj)
   {
      return !string.IsNullOrWhiteSpace(SomeText);
   }

   private void ExecuteDoSomething(object obj)
   {
      // ...your action.
   }
}

In your XAML code, you would remove the IsEnabled binding and add the command instead.

<Button Content="Submit" Command="{Binding DoSomething}"/>

That is it, no need for exposing an EnableSubmit property. If you do not really need the text from the text box in your view model apart from the action, you could even remove the SomeText property and pass the text directly as CommandParameter in XAML (which is passed to the execute and can execute methods).

thatguy
  • 21,059
  • 6
  • 30
  • 40
1

For data bindings to update, the data source of the binding needs to support change notification. There are two methods of doing this:

  1. Use a DependencyObject with dependency properties, or
  2. Implement the INotifyPropertyChanged interface.

Both of the above provide a way to tell the binding when a property has been updated; normal properties (like the ones shown in your code) do not. That's why the binding never updates: it never gets told it needs to.


The above alone will solve your problem, but I feel I should also introduce you to WPF's commands. What you are doing- enabling or disabling a button based on whether the associated action can be performed- is a feature that's baked into commands.

Commands let you take the logic of whether an action can be done, and what that action is, and seperate it from the UI. They're generally preferred over binding IsEnabled and handling the Click event (when practical). Instead, you bind Button.Command to the desired command object. The Button will automatically enable or disable itself based on whether the command can execute, and will run that command when clicked.

Keith Stein
  • 6,235
  • 4
  • 17
  • 36