WPF multibinding button.isEnabled with textboxes
Asked Answered
A

5

8

I am new to WPF and I couldn't find solution on the web. My problem is that I want my button to be enabled only when four textboxes will not have validity errors. My code is:

<Button Content="Action" Click="Action_Click" >
      <Button.IsEnabled>
          <MultiBinding Converter="{StaticResource ValCon}">
               <Binding ElementName="textBox1" />
               <Binding ElementName="textBox2"/>
               <Binding ElementName="textBox3"/>
               <Binding ElementName="textBox4"/>
          </MultiBinding>
       </Button.IsEnabled>
</Button>

and my multi value converter is like:

class ValidityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        bool b = true;
        foreach (var x in values)
        {
            TextBox y = x as TextBox;
            if (Validation.GetHasError(y))
            {
                b = false;
                break;
            }
        }
        return b;
    }
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

I have noticed that this is working only at the beginning. When user modifies one of the textboxes it doesn't. Does my value converter need to implement INotifyPropertyChange? What is the correct solution of doing something like this in WPF? Thanks for your help.

EDIT

I have already done something like this and it's working:

                       <Button Click="Button_Click"  >
                            <Button.Style>
                                <Style TargetType="Button">
                                    <Setter Property="IsEnabled" Value="False"/>
                                    <Style.Triggers>
                                        <MultiDataTrigger>
                                            <MultiDataTrigger.Conditions>
                                                <Condition Binding="{Binding Path=(Validation.HasError), ElementName=textBox}" Value="False"/>
                                                <Condition Binding="{Binding Path=(Validation.HasError), ElementName=textBox1}" Value="False"/>
                                                <Condition Binding="{Binding Path=(Validation.HasError), ElementName=textBox2}" Value="False"/>
                                                <Condition Binding="{Binding Path=(Validation.HasError), ElementName=textBox3}" Value="False"/>
                                            </MultiDataTrigger.Conditions>
                                            <Setter Property="IsEnabled" Value="True"/>
                                        </MultiDataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Button.Style>
                        </Button>
Aseity answered 6/5, 2014 at 16:7 Comment(3)
None of the arguments to the MultiBinding are changing; they are always the same four text boxes. Those bindings evaluate once, so your IMultiValueConverter will only be evaluated once. You might consider using a BindingGroup to pull all your validation rules into a single scope.Ohaus
what does it means it works only in the beginning? is the button enabled when all textboxes are valid and stays true even if you change a certain textbox to be invalid?Asphyxia
As Mike Strobel said the bindings evaluated only once in the beginning. Button is enabled in the beginning because Text property is correct and it doesn't change when user sets text to something that is not valid. Thanks for suggestion I will try to do something with the BindingGroupAseity
M
3

What i have done is use your MultiTrigger, solution but in my case i wanted a button to get activated only when 3 textboxes had zero length so my solution worked with the code below..

<Button.Style>
    <Style TargetType="Button">
        <Setter Property="IsEnabled" Value="True"/>
        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding Path=Text.Length, ElementName=activationKeyTextbox}" Value="0"/>
                </MultiDataTrigger.Conditions>
                <Setter Property="IsEnabled" Value="False"/>
            </MultiDataTrigger>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding Path=Text.Length, ElementName=serialDayStartbox}" Value="0"/>
                </MultiDataTrigger.Conditions>
                <Setter Property="IsEnabled" Value="False"/>
            </MultiDataTrigger>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding Path=Text.Length, ElementName=serialNumdaysbox}" Value="0"/>
                </MultiDataTrigger.Conditions>
                <Setter Property="IsEnabled" Value="False"/>
            </MultiDataTrigger>
        </Style.Triggers>
    </Style>
</Button.Style>
Morphology answered 14/5, 2015 at 17:57 Comment(0)
S
2

I would not implement IMultiValueConverter as we are not converting anything here. BindingGroup may serve your purpose.

Equally effective approach using MVVM/INotifyPropertyChanged can be as follows :-

  1. Bind the button's IsEnabled property to a boolean property(say IsValid).
  2. Bind all the textboxes to different properties.
  3. OnPropertyChange of any of these textboxes, invoke a common function(say Validate())
  4. IsValid can be set to true or false based on Validate(), which will in turn toggle(enable/disable) the state of button.
Sliver answered 6/5, 2014 at 17:25 Comment(1)
it would be nicer if you would have given a code example. As its hard to understand for us as we are new to WPF and .NET feild.Capybara
W
1

I know this is old, but I was in a similar need and didn't want to write a bindinggroup and validation... Note this is my first wpf app and first time using mvvm!

<Button Name="AddBtn" Content="Add" Grid.Row="2" Grid.Column="2" Height="30" Width="100" Command="{Binding AddCmd}" CommandParameter="{Binding Pending}" >
  <Button.IsEnabled>
     <MultiBinding Converter="{StaticResource multiButtonEnabled}" >
          <Binding ElementName="tb1" Path="(Validation.Errors)[0]" />
          <Binding ElementName="tb2" Path="(Validation.Errors)[0]"  />
          <Binding ElementName="tb3" Path="(Validation.Errors)[0]" />
     </MultiBinding>
  </Button.IsEnabled>
</Button>

Converter looks like this

[ValueConversion(typeof(ValidationError), typeof(bool))]
class TxtBoxMultiEnableConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        int i=0;
        foreach (var item in values)
        {
            if (item as ValidationError != null)
            {
                i++;
            }
        }
        if (i!=0)
        {
            return false;
        }
        return true;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

I was using IDataErrorInfo to catch errors in the model, and was binding to a double. WPF is nice and throws parsing errors for you when binding, but they don't really give you a way to hook into them to change the message displayed to the user or give you a property to check to see if there was an error flagged which is annoying, but this lets you catch errors in parsing without having to check for parsing errors in the validation rules. I'm sure my ignorance is showing, but we all have to start somewhere!

Winni answered 12/6, 2017 at 6:43 Comment(0)
S
0

Exactly what Mike said, the textbox is not changing.

You should probably bind on the textbox.Text property.

In your example :

<Button Content="Action" Click="Action_Click" >
      <Button.IsEnabled>
          <MultiBinding Converter="{StaticResource ValCon}">
               <Binding ElementName="textBox1" Path="Text"/>
               <Binding ElementName="textBox2" Path="Text" />
               <Binding ElementName="textBox3" Path="Text"/>
               <Binding ElementName="textBox4" Path="Text"/>
          </MultiBinding>
       </Button.IsEnabled>
</Button>
Sunlight answered 6/5, 2014 at 21:18 Comment(0)
E
0

Disclaimer

This is pretty must my first StackOverflow answer, so it might be inaccurate or in need of an adjustment to follow StackOverflow protocol.

As for this comment: https://mcmap.net/q/1352937/-wpf-multibinding-button-isenabled-with-textboxes (Couldn't comment directly)

I've attempted to use this suggestion; however, it's not enough without another mechanism to update the MultiBinding manually. My proposal is:

FormConverter.cs:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp.Converters
{
    /// <summary>
    /// This class will be used to determine whether the form submission button should be enabled
    /// </summary>
    [ValueConversion(typeof(Control), typeof(bool))]
    public class FormConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            foreach (Control control in values)
            {
                //Relevant inside the scope of the current iteration only
                string text = null;

                //Any validation error
                if (Validation.GetHasError(control))
                    return false;

                //No value selected
                if (control is DatePicker datePicker)
                    if (datePicker.SelectedDate == DateTime.Today)
                        return false;
                    else continue;
                if (control is ComboBox comboBox)
                    if (comboBox.SelectedIndex == -1)
                        return false;
                    else continue;

                //Left blank
                if (control is TextBox textBox)
                    text = textBox.Text;
                else if (control is PasswordBox passwordBox)
                    text = passwordBox.Password;
                if (text != null && text.Length == 0)
                    return false;
            }
            return true;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}

MainWindow.xaml.cs:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApp
{
    class MainWindow : Window
    {
        private MultiBindingExpression buttonBinding = null;
        public MainWindow()
        {
            InitializeComponent();
            buttonBinding = BindingOperations.GetMultiBindingExpression(button, button.IsEnabledProperty);
        }

        private void UpdateButton(object sender, RoutedEventArgs e)
        {
            if (registerButtonBinding == null)
                return;
            buttonBinding.UpdateTarget();
        }
    }
}

MainWindow.xaml:

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:converters="clr-namespace:WpfApp.Converters">
  <Window.Resources>
    <converters:formConverter x:Key="formConverter"/>
  </Window.Resources>

  <StackPanel HorizontalAlignment="Center">

    <TextBlock Text="First name"/>
    <TextBox x:Name="firstNameTextBox"/>

    <Button x:Name="button">
      <Button.IsEnabled>
        <MultiBinding Converter="{StaticResource formConverter}">
          <Binding ElementName="firstName"/>
        </MultiBinding>
      </Button.IsEnabled>
    </Button>

  </StackPanel>

</Window>

Sourced:

Elzaelzevir answered 2/3, 2023 at 18:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.