0

I need to activate a button when a textbox has a specific type/format of text input (FilterString) based on which radiobutton is selected without breaking mvvm pattern.

Basically in the CanExecute method of the RelayCommand instance of the button do something like

public bool CanDoStuff(object param)
        {
            if (rb_1.IsChecked == true) {
                if (string.IsNullOrEmpty(FilterString)) {
                    return false;
                } else
                    return true;
            }
            
            else if (rb_2.IsChecked == true) {
                DateTime searchDate;
                if (DateTime.TryParseExact(FilterString, "dd-MM-yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out searchDate)) {
                    return true;
                } else
                    return false;
            }
            
            else if (rb_3.IsChecked == true) {
                Decimal k;
                if (Decimal.TryParse(FilterString, out k)) {
                    return true;
                } else
                    return false;
            }
            else
                return true;
        }

But I can't access radiobuttons like above in a proper mvvm setup. Is there a different approach for this like using IDataErrorInfo or something and how do I do that?

Tamal Banerjee
  • 503
  • 3
  • 20
  • 1
    Does this answer your question? [WPF + MVVM + RadioButton : Handle binding with single property](https://stackoverflow.com/questions/37790017/wpf-mvvm-radiobutton-handle-binding-with-single-property) –  Nov 25 '22 at 08:43
  • @MickyD Not exactly, I'm struggling to figure out to apply it in my case scenario.. – Tamal Banerjee Nov 25 '22 at 09:14
  • Define a enum and add a property of that enum type to view model and then bind that property with `RadioButton`s. https://stackoverflow.com/questions/397556/how-to-bind-radiobuttons-to-an-enum – emoacht Nov 25 '22 at 10:05
  • @emoacht What about the textbox text, it is represented/bound by a single string property in the view model `FilterString`, how do I incorporate that ? – Tamal Banerjee Nov 25 '22 at 10:29
  • Call your logic from property setter of the string property as well as the enum property. – emoacht Nov 25 '22 at 11:09

1 Answers1

1

It's recommended to not interpret button states directly. Rather abstract them to a meaningful state value in context of their purpose.

  1. You can always bind each RadioButton to a dedicated property.
  2. You can use a IValueConverter to convert the button's state to a meaningful value e.g. an enum.
  3. You can, of course, use an ICommand to send the state as a meaningful value e.g. an enum.
  4. Attach a Click event handler

Solution 3) is good because it eliminates the more complex Binding definitions introduced e.g. by 2) which is uses a IValueConverter. It makes the code more readable.
Because grouped RadioButton are mutual exclusive, you basically need to create a ICommand for each RadioButton group.
You can find a common reusable ICommand implementation, the RelayComand, here: Microsoft Docs: Relaying Command Logic.

For example, if you want to use an array of RadioButton to allow the user to pick a currency, you could first implement an enum to create constants that represent a currency. Then use a enum value for each RadioButton and send it using the ButtonBase.CommandParameter property:

Currency.cs

public enum Currency
{
  Default = 0,
  Euro,
  BritishPound,
  JapaneseYen,
  UsDollar
}

MainViewModel.cs

class MainViewModel : INotifyPropertyChagned
{
  public Currency SelectedCurrency { get; private set; }

  // E.g., binding source for a TextBox
  // TODO::Raise INotifyPropertyChanged.PropertyChanged event
  public string Value { get; set; }

  public ICommand SelectCurrencyCommand { get; private set; }
  public ICommand SaveCommand { get; private set; }

  public MainViewModel()
  {
    this.SelectCurrencyCommand = new RelayCommand(ExecuteSelectCurrencyCommand, commandParameter => true);
    this.SaveCommand = new RelayCommand(ExecuteSaveCommand, CanExecuteSaveCommand);
  }

  private void ExecuteSelectCurrencyCommand(object commandParameter)
  {
    this.SelectedCurrency = (Currency)commandParameter;
  }
  
  // Your CanExecute handler, that depends on the state of the radio buttons.
  private bool CanExecuteSaveCommand(object commandParameter)
  {
    decimal decimalValue;
    switch (this.SelectedCurrency)
    {
      case Currency.Euro:
          return Decimal.TryParse(this.Value, out decimalValue);
      case Currency.BritishPound:
          return Decimal.TryParse(this.Value, out decimalValue);
      case Currency.JapaneseYen:
          break;
      case Currency.UsDollar:
          break;
      case Currency.Default:
          break;
      default:
        return false;
    }
  }
}

MainWindow.xaml

<Window>
  <Window.DataContext>
    <MainViewModel />
  </Window.DataContext>

  <StackPanel>
    <RadioButton Command="{Binding SelectCurrencyCommand}"
                 CommandParameter="{x:Static Currency.Euro}" />
    <RadioButton Command="{Binding SelectCurrencyCommand}"
                 CommandParameter="{x:Static Currency.BritishPound}" />
    <RadioButton Command="{Binding SelectCurrencyCommand}"
                 CommandParameter="{x:Static Currency.JapaneseYen}" />

    <TextBox Text="{Binding Value}" />
    <Button Content="Save"
            Command="{Binding SaveCommand}" />
  </StackPanel>
</Window>
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Can you show me c#5 compatible version of the above code? It would be really helpful – Tamal Banerjee Nov 26 '22 at 11:24
  • I'm still getting errors in the ICommand lines...btw I have a RelayCommand class already (https://codeshare.io/eV1YB4) ... is it because of that? Also do I have to declare a say `Decimal k;` before the switch statement in the `CanExecuteSaveCommand` and make `return Decimal.TryParse(this.Value, out k);` ? I'm also getting error there. – Tamal Banerjee Nov 26 '22 at 11:46
  • C# 5 is a very antiquated version. It lacks all the nice features like property initializers. --- 1) Since property initializers are not supported, you have to initialize the property in the constructor (see above example). 2) Since discards are not supported, you have to define the out parameter explicitly (see above example). – BionicCode Nov 26 '22 at 12:23