RelayCommand CanExecute behavior - c#

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

Related

How to reset Interaction.Triggers in toggle button correctly with code behind?

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.

wpf xaml button with AccessText seems not to query CanExecute

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...

wpf databinding with Delay property

I have specified a TextBox with Delay set to 100 as follows:
<TextBox x:Name="SearchTextBox"
Grid.Column="1"
PreviewKeyDown="SearchTextBox_PreviewKeyDown"
KeyUp="SearchTextBox_KeyUp"
Margin="0,2,0,0" FontSize="14"
Padding="3"
Text="{Binding Path=SearchText, Delay=100, UpdateSourceTrigger=PropertyChanged}">
I have an event wired up for KeyUp, my idea is when the user hits the Enter Key i want to perform something.
Sometimes I have noticed that when I hit Enter, the whole text is NOT being updated in the property SearchText in my ViewModel, is it because the binding has not yet updated by the time I hit ENTER? because of the delay? Is there a way around it?
Thanks!
In the key handler you could make sure that the binding is updated by getting the binding expression for the Text binding and calling UpdateSource first.

Listen for generic events

I have a function I would like to run on after update of a lot of different text boxes, is it possible to listen for a generic after update event rather than the specific events?
So rather than 100 individual calls to the function, just one listener?
Edit: It would appear we are using a combination of MVVM and traditional code behind.
Here is one of the textboxes:
<TextBox Text="{Binding APhaseFrom}" x:Name="txtFromWhereA" TabIndex="26" HorizontalContentAlignment="Center" HorizontalAlignment="Left" Height="48" TextWrapping="NoWrap" VerticalAlignment="Top" Width="261" FontSize="26" FontWeight="Bold" BorderBrush="Black" BorderThickness="1" Margin="289,656,0,0" GotMouseCapture="txtFromWhereA_GotMouseCapture" GotFocus="txtFromWhereA_GotFocus" Grid.Row="3" />
The code from the view Model:
public string APhaseFrom
{
get { return new string((char[])_f.Rows[1].GetValue("Alpha09")); }
set
{
if (value.Length <= 35)
{
_f.Rows[1].SetValue("Alpha09", value);
}
else
{
MessageBox.Show("Error: String length Longer than 35 Characters.");
}
}
}
We also are using some commands for other processes:
public ICommand Updatesql
{
get;
internal set;
}
private void CreateUpdatesql()
{
Updatesql = new RelayCommand(UpdatesqlExecute);
}
private void UpdatesqlExecute()
{
_f.Update();
}
Should I be using commands or just link the events to functions in the viewmodel?
Since you are using WPF, and if I understand your problem correctly, then the RoutedEvents that WPF uses may help you here. Essentially, events like the LostFocus event of a TextBox will bubble up your UI hierarchy and can be handled by a common parent control. Consider this snippet of XAML and codebehind:
<StackPanel TextBox.LostFocus="TextBoxLostFocus">
<TextBox></TextBox>
<TextBox></TextBox>
<TextBox></TextBox>
</StackPanel>
Codebehind:
private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
MessageBox.Show("Lost Focus!");
}
You will find that the event handler is called for any of the three textboxes when focus is lost. The sender parameter or e.Source can be used to find the textbox that fired the event.
This pattern holds true for any RoutedEvent, so things like Button.Click or TextBox.TextChanged and many more can be caught in this manner.
Really and truthfully you should be using a single design pattern... ie MVVM when writing WPF applications, each textbox would be bound to a property which implements the INotifyPropertyChange interface.
In the setter of each property you would essentially update the value, fire a property changed event and then either make a call to your method or simply add an event handler on the view model for the PropertyChanged event.
Also... MessageBox.Show is a bad idea in your view models, its hard to unit test it.
Update
I removed my previous ideas because I now understand more clearly what you are looking for.
But you definitely need to use the LostFocus event.
<TextBox Text="{Binding APhaseFrom}" x:Name="txtFromWhereA" LostFocus="OnLostFocus" />

RelayCommand CanExecute behavior not working

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.

Categories

Resources