I have a combo box and a button. I want whenever I choose 1 of the item from it, the button is not disabled
XAML :
<Border Style="{StaticResource borderMain}"
Grid.Row="7"
Grid.Column="0">
<ComboBox ItemsSource="{Binding Source={StaticResource portNames}}"
SelectedItem="{Binding SelectedPort, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
x:Name="Port_Selector" Grid.Column="0"
Text="Port Selector" Background="White"/>
</Border>
<Border Style="{StaticResource borderMain}"
Grid.Column="1"
Grid.Row="7">
<Button Content="Connect"
Command="{Binding OpenPortCommand}"
CommandParameter="{Binding SelectedPort}"
Style="{StaticResource buttonMain}"
Margin="5"/>
</Border>
Command :
public class OpenPortCommand : ICommand
{
public OpenPortVM OpenPortVM{ get; set; }
public OpenPortCommand(OpenPortVM OpenPortVM)
{
this.OpenPortVM = OpenPortVM;
}
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested = value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object? parameter)
{
string? portCom = parameter as string;
if (!string.IsNullOrEmpty(portCom))
return true;
return false;
}
public void Execute(object? parameter)
{
OpenPortVM.ConnectPort();
}
}
I already debug it and check the value from variable that I using for binding SelectedPort and it has a value on it but somehow, the CommandParameter for my button not detected so CanExecute method is not run properly. Am i missing something?
Update
Update 2
CodePudding user response:
Your OpenPortCommand is missing the possibility to actually raise the CanExecuteChanged event. I suggest following:
public class OpenPortCommand : ICommand
{
public OpenPortVM OpenPortVM{ get; set; }
public OpenPortCommand(OpenPortVM OpenPortVM)
{
this.OpenPortVM = OpenPortVM;
}
// Leave this event as is, don't do an explicit implementation
public event EventHandler? CanExecuteChanged;
public void RaiseCanExecuteChange() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
public bool CanExecute(object? parameter) => !string.IsNullOrEmpty(parameter as string);
// rest ommitted
}
In your ViewModel's SelectedPort property:
public string SelectedPort
{
get => _selectedPort;
set
{
_selectedPort = value;
// call your OnPropertyChanged( ... );
OpenPortCommand?.RaiseCanExecuteChanged();
}
}
In the XAML I've simplified the binding of the ComboBox.SelectedItem property like this:
<ComboBox ... SelectedItem="{Binding SelectedPort}"/>
CodePudding user response:
Your code has two issues:
- The most critical one, which actually prevents your code from executing properly, is the way you raise the
INotifyPropertyChanged.PropertyChangedevent. Instead of passing in the property name, you currently pass in the name of the backing field.
Instead of
OnPropertyChanged(nameof(portSelected));
it must be
OnPropertyChanged(nameof(SelectedPort));
To simplify the event invocation your invocator should use the CallerMemberName attribute:
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName);
Then call the method OnPropertyChanged without any parameter, as the property name is now automatically detected:
OnPropertyChanged();
- The method
RaiseExecuteChangedCommandis redundant. YourICommandimplementation of theCanExecuteChangedevent delegates event handlers to theCommandManager.RequerySuggestedevent (which is correct). TheCommandManager.RequerySuggestedevent is raised automatically by the WPF framework and because of the event delegation the framework will also raiseICommand.CanExecuteChanged(because you attach allICommand.CanExecuteChangedhandlers to theCommandManager.RequerySuggestedevent usingCommandManager.RequerySuggested = value). If you don't want the framework to raise the CanExecuteChanged event for you you must remove theCommandManager.RequerySuggested = valuepart i.e. don't attach client handlers to theCommandManager.RequerySuggestedevent. Because now you will raise it explcitly from yourRaiseExecuteChangedCommandmethod. So you can't have both. Either raiseICommand.CanExecuteChangedexplicitly or let theCommandManagerdo it for you.




