I'm having trouble getting the RelayCommand to enable/disable the attached control properly.
I've got an EventToCommand element attached to a button. The command is databound to the ViewModel. Initially, the button is disabled (expected behavior), but I cannot seem to get the CanExecute logic to check it's value. When CurrentConfigFile is set and exists, the button should be enabled. I've executed code and checked the file's value in debug to make sure it's set, but the control is still disabled. I've tried CommandManager.InvalidateRequerySuggested() and command.RaiseCanExecuteChanged(), but it will not enable.
I've wondered if lambdas don't work correctly for the CanExecute behavior (even though the examples use them) or that the CanExecute behavior needs to be databound to another element.
Here's my code:
// The FileInfo being checked for existence before the button should be enabled
public const string CurrentConfigFilePN = "CurrentConfigFile";
public FileInfo CurrentConfigFile
{
get
{
return _currentConfigFile;
}
set
{
if (_currentConfigFile == value)
{
return;
}
var oldValue = _currentConfigFile;
_currentConfigFile = value;
// Update bindings, no broadcast
RaisePropertyChanged(CurrentConfigFilePN);
}
}
public MainViewModel()
{
// snip //
SaveCommand = new RelayCommand(SaveConfiguration,
() => CurrentConfigFile != null && CurrentConfigFile.Exists);
}
private void SaveConfiguration()
{
// export model information to xml document
ExportXMLConfiguration(CurrentConfigFile);
}
and markup
<Button x:Name="SaveButton" Content="Save" Width="75" Margin="20,5">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft:EventToCommand x:Name="SaveETC"
Command="{Binding SaveCommand}"
MustToggleIsEnabledValue="true" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Update:
As per Isak Savo's suggestion, I bound the RelayCommand directly to the button with
<Button x:Name="SaveButton" Content="Save" Width="75" Margin="20,5"
Command="{Binding SaveCommand}"/>
and it started disabled and correctly enabled when the FileInfo was set. Guess I should remember not to fix what isn't broken!
Why don't you just bind to the Command directly from the Button?
<Button Command="{Binding SaveCommand}" Content="Save" />
Maybe the EventToCommand thing you are using is messing things up with the command's CanExecute notification.
And regarding the CanExecute problem - are you sure that your CanExecute handler is called after the CurrentConfigFile property is set? I've found that even though WPF mostly does a good job of requerying CanExecute, I still sometimes need to force a requery through the CommandManager.
EDIT: As pointed out in the comments, the OP has already tried the command manager approach.
In msdn is written:
When first called, FileInfo calls Refresh and caches information about the file. On subsequent calls, you must call Refresh to get the latest copy of the information.
However, I would not do such a check in the CanExecute-handler. This may slow down your UI because CanExecute is called a lot of times and I could imagine that such IO-checks can become slow, for example if the file lies on a network share.
Related
In my current C#/WPF project I use a toggle button with Interaction.Triggers to start and stop a measurement and it works as intended. You press the start button, it starts to measure, you press the stop button and it stops and resets the properties so you can do it again.
The process in the GUI looks like this:
XAML code:
<ToggleButton x:Name="ButtonMeasurementConnect"
Grid.Row="5" Grid.Column="3"
VerticalAlignment="Center"
Content="{Binding ButtonDataAcquisitionName}"
IsChecked="{Binding IsDataAcquisitionActivated, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding StartMeasurementCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding StopMeasurementCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ToggleButton>
Now I added the option to set a timer so the program automatically stops the measurement after a desired time like in this example.
You can see that when I click the start button, the timer stops at 1 second, the start button name property resets correctly to "Start" again instead of "Stop".
Here's finally the problem: If I want to repeat it I have to press the "Start" button twice. First time pressing again will just result in invoking the StopMeasurementCommand.
How can I tell the toggle button that it should reset to using the StartMeasurementCommand binding the next time it gets used inside the code behind, not by pressing the button manually?
EDIT:
Here's the code inside the view model, first the obvious handling of the commands:
StartMeasurementCommand = new DelegateCommand(OnStartMeasurementExecute);
StopMeasurementCommand = new DelegateCommand(OnStopMeasurementExecute);
Here the OnStopMeasurementExecute:
try
{
if (stopWatch.IsRunning)
{
stopWatch.Stop();
}
_receivingDataLock = true;
// Stop writing/consuming and displaying
_sourceDisplay.Cancel();
if (IsRecordingRequested)
{
_sourceWriter.Cancel();
} else
{
_sourceConsumer.Cancel();
}
// Sending stop command to LCA
_dataAcquisition.StopDataAcquisition();
// Flags
ButtonRecordingVisibility = Visibility.Hidden;
IsDataAcquisitionActivated = false;
IsDataAcquisitionDeactivated = true;
ButtonDataAcquisitionName = "Start";
if (IsRecordingRequested)
{
StatusMessage = "Recording stopped after " + CurrentTime;
}
else
{
StatusMessage = "Live data stopped after " + CurrentTime;
}
if (IsRecordingRequested) _recordedDataFile.Close();
}
catch (Exception e)
{
Console.WriteLine("Exception in OnStopMeasurementExecute: " + e.Message);
}
If a timer is set it gets invoked by the timer function as well:
void Stopwatch_Tick(object sender, EventArgs e)
{
if (stopWatch.IsRunning)
{
TimeSpan ts = stopWatch.Elapsed;
CurrentTime = String.Format("{0:00}:{1:00}:{2:00}", ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
if ((IsTimerActivated) && (MeasureTime>0)) {
if (ts.Seconds >= MeasureTime) OnStopMeasurementExecute();
}
}
}
The cause of your issue is that the IsDataAcquisitionActivated is reset in the view model, but you do not raise a property changed notification. Therefore the bindings will not be notified of the change and hold their old value, which is false. This means, although the text on the button changes, it is still in Unchecked state, resulting in StopMeasurementCommand being executed.
I noticed the OnPropertyChanged(); was commented out for the IsDataAcquisitionActivated! [...] I remember why it was commented out [...]: The StopMeasurement function seems to get fired twice now which i can see because the "Live data stopped after ..." status message gets triggered twice now.
Correct. Let us review the sequence of events in this scenario to find the issue.
The ToggleButton is clicked.
The measurement is started, IsDataAcquisitionActivated is set to true from the ToggleButton.
A property changed notification is raised.
The ToggleButton changes its state to Checked.
The Checked event is raised and invokes the StartMeasurementCommand starting the timer.
The timer runs out and invokes OnStopMeasurementExecute. (First time).
The method sets IsDataAcquisitionActivated to false.
A property changed notification is raised.
The ToggleButton changes its state to Unchecked.
The Unchecked event is raised and invokes the StopMeasurementCommand (Second time).
...and so on.
The fundamental issue is to rely on events here while binding the IsChecked state two-way. It is way easier to do one or the other, if there is no requirement against it.
In the event sequence you see that the timer invokes the OnStopMeasurementExecute method twice through executing it and indirectly triggering the OnStopMeasurementExecute command from the ToggleButton. If you do not call the method, but only set the IsDataAcquisitionActivated property instead, it will only be called once.
if (ts.Seconds >= MeasureTime) IsDataAcquisitionActivated = false;
This does not require much adaption in your code, although I would prefer an approach that does not wire events to commands, since it is harder to comprehend and track.
Here are two alternative approaches with explicit event to command bindings.
1. Unify the Commands and Handle the State There
The ToggleButton only cares about the IsDataAcquisitionActivated property to display the correct state, Checked or Unchecked. It does not have to set the state, so let the command handle that. Let us use a one-way binding, since the view model is the source of truth here. Then let us combine the two separate commands to one, ToggleMeasurementCommand.
<ToggleButton x:Name="ButtonMeasurementConnect"
Grid.Row="5" Grid.Column="3"
VerticalAlignment="Center"
Content="{Binding ButtonDataAcquisitionName}"
IsChecked="{Binding IsDataAcquisitionActivated, Mode=OneWay}"
Command="{Binding ToggleMeasurementCommand}"/>
The ToggleMeasurementCommand now only delegates to a the start or stop method depending on the IsDataAcquisitionActivated property.
ToggleMeasurementCommand = new DelegateCommand(OnToggleMeasurementExecute);
private void OnToggleMeasurementExecute()
{
if (IsDataAcquisitionActivated)
OnStartMeasurementExecute();
else
OnStopMeasurementExecute();
}
Adapt the start and stop methods to set the correct state for IsDataAcquisitionActivated.
private void OnStartMeasurementExecute()
{
IsDataAcquisitionActivated= false;
//... your other code.
}
private void OnStopMeasurementExecute()
{
IsDataAcquisitionActivated= true;
//... your other code.
}
The property is set once from the view model and the ToggleButton only updates based on the property changed notifications it gets from the view model.
Another thought on ToggleButton: You could reconsider if you really need a ToggleButton. The text states an action Start or Stop, not a state (although there is one implicitly). Consequently with the single command, you could just use a simple Button, no need bind any state.
2. Act On Property Changes
You could react to property changes. Remove the commands and leave the two-way binding.
<ToggleButton x:Name="ButtonMeasurementConnect"
Grid.Row="5" Grid.Column="3"
VerticalAlignment="Center"
Content="{Binding ButtonDataAcquisitionName}"
IsChecked="{Binding IsDataAcquisitionActivated, Mode=TwoWay}">
</ToggleButton>
Now the only indication when to start or stop the measurement is a change of the property IsDataAcquisitionActivated or in other words, when its setter is called with a changed value.
public bool IsDataAcquisitionActivated
{
get => _isDataAcquisitionActivated;
set
{
if (_isDataAcquisitionActivated == value)
return;
_isDataAcquisitionActivated= value;
OnPropertyChanged();
if (_isDataAcquisitionActivated)
OnStartMeasurementExecute();
else
OnStopMeasurementExecute();
}
}
Then of course, your timer would not call OnStopMeasurementExecute anymore, but only set the property, since the method will be called automatically then.
if (ts.Seconds >= MeasureTime) IsDataAcquisitionActivated = false;
The mistake was that the function to implement INotifyPropertyChanged was not invoked in the property bound to the IsChecked attribute of the toggle button.
Now that it is set the timer in the code behind resets the button correctly as pressing the Stop button does.
One downside came up with this. For some reason the method invoked by the StopMeasurementCommand gets fired twice in a row but that is a different issue.
Explanation
Aim: In my wpf desktop application I want to add AccessText to a button that is bound to a command that has a predicate which evaluates if the window / control is busy and thus enables/disables the button.
Status: The button command works fine and is properly enabled prior to adding AccessText. (I'm using MvvmLight for my ICommand support.)
Problem: After adding AccessText the command still binds but the CanExecute seems to no longer get queried and the button is always enabled. (My ideal solution would be no code-behind just XAML.)
Example
XAML:
Working:
<Button Command="{Binding NavToStoresSearchCmd}" Content="Stores" Height="30"/>
Not Working:
<Button Command="{Binding NavToStoresSearchCmd}" Height="30">
<AccessText>S_tores</AccessText>
</Button>
C# (ViewModel):
public ICommand NavToStoresSearchCmd { get => new RelayCommand(OnNavToStoresSearch, () => IsNotBusy); }
(The code for IsNotBusy and the OnNavToStoresSearch command work fine, as does CanExecute UNTIL I add the AccessText.)
Having just tested this, it appears to be working as expected.
My code:
XAML:
<Button Command="{Binding BrowseCommand}">
<AccessText>_Browse</AccessText>
</Button>
C# (ViewModel):
In constructor:
BrowseCommand = new RelayCommand( BrowseCommandHandler, () => CanBrowse );
'CanBrowse' property:
private bool _canBrowse;
public bool CanBrowse
{
get { return _canBrowse; }
set { _canBrowse = value; BrowseCommand.RaiseCanExecuteChanged(); }
}
My guess is you're not calling 'RaiseCanExecuteChanged()' on your RelayCommand?
(Edit: Just seen the comments come in before I hit submit. Sorry folks!)
As said in the comments, RaiseCanExecuteChanged is needed here. WPF's CommandManager calls CanExecute when it detects UI changes, such binding updates, state changes, etc. It is unreliable. You may have been lucky in your earlier testing in how the CommandManager was reacting for you, but (in my opinion) it's better to explicitly call RaiseCanExecuteChanged when you know it's changed.
(Further edit)
I did a quick bit of searching around and it appears you can get around needing to call 'RaiseCanExecuteChanged' by changing a namespace entry. Change...
using GalaSoft.MvvmLight.Command;
for
using GalaSoft.MvvmLight.CommandWpf;
I quickly ripped the 'RaiseCanExecuteChanged()' code out from my test app and shockingly it appears to work.
Maybe I've been doing it wrong all these years...
I know there are a handful of related questions but none of those helped me finding the issue.
Most answers suggest to implement CanExecuteChanged as shown in this answer. Well, that's not the solution to my problem. I've got an implementation of RelayCommand similar to Josh Smith's implemenation. (Similar, because our implementation adds more details but the core implementation is the same.)
While searching the Internet I also learned that if there is no focused element, the routing will stop at the ContextMenu and wouldn't reach the MenuItem. A solution that would help in that case is shown here.
However, I checked with Snoop if there really isn't any focused element and learned this is not the issue. And the fix didn't help anyway.
Besides, I simulated that issue in a test project and was able to fix it. So the fix generally works, it's just not helping me. I think there's still a chance, however, that I have to adapt the fix slightly to get it working. I tried MyControl instead of ContextMenu as AncestorType and I tried PlacementTarget.Tag instead of just PlacementTarget as Path but I wouldn't know what else to try to get it working (assuming that this is the bug).
Funny enough, it even doesn't work when I call CommandManager.InvalidateRequerySuggested() manually. I added a command that is raised on ContextMenuOpening. I thought that this would force the CanExecute to be executed but it seems I'm mistaken.
So, I'm now looking for further reasons why a CanExecute handler isn't raised when a ContextMenu is opened and how I would fix that.
Here's my XAML code (including EventTrigger for ContextMenuOpening):
<MyControl>
<MyControl.ContextMenu>
<ContextMenu>
<MenuItem Header="..."
Command="{Binding MyCommand}"
CommandParameter="{Binding}"
CommandTarget="{Binding Path=PlacementTarget,
RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</MyControl.ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuOpening">
<i:InvokeCommandAction Command="{Binding OnContextMenuOpening}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MyControl>
Here's the definition of MyCommand and the (Can)Execute handlers:
internal static readonly ICommandEx MyCommand =
new RelayCommand(OnMyCommand, OnCanMyCommand);
private static void OnMyCommand(object parameter) { ... }
private static bool OnCanMyCommand(object parameter) { ... }
Here's my OnContextMenuOpening handler where I tried to force MyCommand's CanExecute to be raised:
private static void OnContextMenuOpening(object parameter)
{
CommandManager.InvalidateRequerySuggested();
}
You are incorrectly listening on OnContextMenuOpening on the ContextMenu control. It will never fire! Instead, listen on this very event on your MyControl control.
Another strange behavior in my MVVM-Adventures I can't explain and didn't find a reasoning:
I have a Command in my VM, which can be executed, when a Color is selected:
public ICommand SetSingleColor
{
get
{
return new RelayCommand(f =>
{
var visualLed = f as VisualLed;
visualLed.Color = SelectedColor.Value;
}, () => SelectedColor.HasValue);
}
}
My DataTemplate is looking like this:
<DataTemplate DataType="{x:Type ml:VisualLed}" x:Key="DtVisualLed">
<Button
Background="DarkCyan"
Command="{Binding Path=DataContext.SetSingleColor, RelativeSource={RelativeSource AncestorType=v:LedDesignView}}"
CommandParameter="{Binding}"
Style="{StaticResource StyleBtnLed}">
</Button>
</DataTemplate>
As u can see, I pass the VisualLed itself back to the VM, so I can set the Color. I just added the Background-Property for testing purposes.
What I really don't get: The Style and also the Background are only applied, if the Command can be executed! So if I load the View, the Button-Background is the Default-Gray, and as soon as I select a Color, it becomes DarkCyan.
Unfortunately, I can't give more Informations, but I didn't find anything on the Topic, how ButtonBase.Command influences other Properties. It also seems like a expected behavior, since I don't get any Binding-Errors or such.
There is relation between Button.Command and Button.IsEnabled property. If Command.CanExecute returns false, button becomes disabled. Now, you often cannot control everything with your own style, if control developer did not plan for this. In this case, disabled button background is fixed by developers of the Button and you cannot override it with your own style (unless you change Template of the button).
I have following command:
<Button x:Name="bOpenConnection" Content="Start Production"
Grid.Row="0" Grid.Column="0"
Height="30" Width="120" Margin="10"
HorizontalAlignment="Left" VerticalAlignment="Top"
Command="{Binding Path=StartProductionCommand}"/>
StartProductionCommand = new RelayCommand(OpenConnection, CanStartProduction);
private bool CanStartProduction()
{
return LogContent != null && !_simulationObject.Connected;
}
CanStartProduction is checked only when I re-size the UI and not updated on the fly.
Any idea why it's not updated every time they change the values ?
The CommandManager has no way of knowing that the command depends on LogContent and _simulationObject.Connected, so it can't reevaluate CanExecute automatically when these properties change.
You can explicitly request a reevaluation by calling CommandManager.InvalidateRequerySuggested. Note that it will reevaluate CanExecute for all commands; if you want to refresh only one, you need to raise the CanExecuteChanged event on the command itself by calling StartProductionCommand.RaiseCanExecuteChanged.
You can call RaiseCanExecuteChanged in the For example PropertyChanged Eventhandler.
Command states are not refreshed very often.
Sometime ago I read a good article about it. I will post it later on.
see also http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/
see also Refresh WPF Command