Home > Enterprise >  Fody dosent change behavior of button, the button dosent get reevaluated to see if it can get presse
Fody dosent change behavior of button, the button dosent get reevaluated to see if it can get presse

Time:10-21

For some reason, I can't activate my Button.

In my app, I have a TextBox, that gets populated with the file path that the user chooses from a dialog. I also have a ComboBox to let the user choose a language.

What I want to do, is to activate my button Start, when the FilePath is not empty and the user has selected a language.

[AddINotifyPropertyChangedInterface]
public class AudioPageViewModel {

    public string? FilePath { get; set; }

    public bool IsWorking { get; set; }

    public bool CanPress { get; set; }

    public string? SelectedItem { get; set; }

    public List<string>? Languages { get; set; }

    public Visibility CanShow { get; set; }

    public DialogHelper Dialog { get; }

    public Command PickFileCommad { get; set; }

    public Command StartCommand { get; set; }

    public AudioPageViewModel() {
        InitListLanguages();
        Dialog = new DialogHelper();
        CanShow = Visibility.Hidden;
        PickFileCommad = new Command(PickFileAction);
        StartCommand = new Command(StartAction, CanStartAction);
    }

    private bool CanStartAction(object arg) {
        if (string.IsNullOrEmpty(SelectedItem) ||
            string.IsNullOrEmpty(FilePath)) {
            return false;
        }
        return true;
    }

    private void StartAction(object obj) {
        throw new NotImplementedException();
    }

    private void PickFileAction() {
        var filePath = Dialog.GetFilePath(ConstantsHelpers.AUDIO);
        FilePath = filePath;
    }

    private void InitListLanguages() {

        Languages = new List<string>() {
            "English",
            "Spanish"
        };
<Grid Margin="20">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>

    <Button
        VerticalAlignment="Top"
        Command="{Binding PickFileCommad}"
        Content="Pick a file"
        FontWeight="Bold" />

    <Label
        Grid.Row="1"
        Margin="0,5,0,0"
        VerticalContentAlignment="Top"
        Content="language of the file" />

    <TextBox
        Grid.Column="1"
        Margin="10,0,10,0"
        VerticalAlignment="Top"
        IsReadOnly="True"
        Text="{Binding FilePath}" />

    <ComboBox
        Grid.Row="1"
        Grid.Column="1"
        Margin="10,0,10,0"
        SelectedItem="{Binding SelectedItem}"
        HorizontalAlignment="Stretch"
        ItemsSource="{Binding Languages}">
    </ComboBox>

    <Label
        Grid.Row="3"
        Grid.ColumnSpan="2"
        Margin="10,0,10,0"
        HorizontalContentAlignment="Center"
        VerticalContentAlignment="Center"
        Content="This will not take long"
        Visibility="{Binding CanShow}" />

    <ui:ProgressRing
        Grid.Row="2"
        Grid.ColumnSpan="2"
        Width="60"
        Height="60"
        Margin="10,0,10,0"
        IsActive="{Binding IsWorking}" />

    <Button
        Grid.Row="4"
        Grid.ColumnSpan="2"
        Margin="10,0,10,10"
        HorizontalAlignment="Stretch"
        VerticalAlignment="Bottom"
        Content="Start"
        Command="{Binding StartCommand}"
        IsEnabled="{Binding CanPress}" />
</Grid>

CodePudding user response:

A key issue is that you use both a Command and an IsEnabled binding. You should only use one, as they interfere with each other. A command binding will set the enabled state of a button depending on the result of its CanExceute method. Binding IsEnabled too, will override this. I would suggest to use commands only and move the logic from CanPress to the CanStartAction method of StartCommand.

Using Commands

Commands signal if they can be executed by their CanExecute method result. However, there are different implementations of commands that trigger the reevaluation of this method differently.

Relay Command With Command Manager

If you use a command implementation that uses the RequerySuggested event, e.g.:

public event EventHandler CanExecuteChanged
{
   add { CommandManager.RequerySuggested  = value; }
   remove { CommandManager.RequerySuggested -= value; }
}

Then removing the IsEnabled binding is enoungh, as it is triggered by input in the user interface.

Relay Command With Explicit Method

The other variant is a command implementation, where a method is exposed to explicitly reevalute its state, e.g. RaiseCanExcuteChanged. In this case, the CanExecuteChanged event is raised explicity and the UI element that binds the command is triggered to execute CanExecute again and update its own enabled state.

Unfortunately, the Fody PropertyChanged library does not support attributes for commands that would trigger reevaluation of can execute. There was already a request, but it was declined and they will not implement it.

A workaround is to create On<Property>Changed methods, which will be called by Fody on changes of the corresponding <Property> automatically, see On_PropertyName_Changed in the Wiki. These methods then call the explicit reevaluation method.

public void OnSelectedItemChanged()
{
   StartCommand.RaiseCanExecuteChanged();
   Debug.WriteLine("SelectedItem Changed");
}

public void OnFilePathChanged()
{
   StartCommand.RaiseCanExecuteChanged();
   Debug.WriteLine("FilePath Changed");
}

Using IsEnabled

When using IsEnabled only, you have to move the CanStartAction logic to be executed in CanPress. In this case this would be an explicit implentation that cannot use Fody.

Additionally, you could not use a command, so a Click handler would be an ugly alternative. This is why I strongly recommend you to use commands instead.

  • Related