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 S topMeasurementCommand
.
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();
}
}
}
2条答案
按热度按时间uqjltbpv1#
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 isfalse
. This means, although the text on the button changes, it is still in Unchecked state, resulting inStopMeasurementCommand
being executed.I noticed the
OnPropertyChanged();
was commented out for theIsDataAcquisitionActivated
! [...] I remember why it was commented out [...]: TheStopMeasurement
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.
ToggleButton
is clicked.IsDataAcquisitionActivated
is set totrue
from theToggleButton
.ToggleButton
changes its state to Checked.Checked
event is raised and invokes theStartMeasurementCommand
starting the timer.OnStopMeasurementExecute
. (First time).IsDataAcquisitionActivated
tofalse
.ToggleButton
changes its state to Unchecked.Unchecked
event is raised and invokes theStopMeasurementCommand
(Second time).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 theOnStopMeasurementExecute
command from theToggleButton
. If you do not call the method, but only set theIsDataAcquisitionActivated
property instead, it will only be called once.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 theIsDataAcquisitionActivated
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
.The
ToggleMeasurementCommand
now only delegates to a the start or stop method depending on theIsDataAcquisitionActivated
property.Adapt the start and stop methods to set the correct state for
IsDataAcquisitionActivated
.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 aToggleButton
. 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 simpleButton
, 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.
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.Then of course, your timer would not call
OnStopMeasurementExecute
anymore, but only set the property, since the method will be called automatically then.jvlzgdj92#
错误在于未在绑定到切换按钮的
IsChecked
特性的属性中调用实现INotifyPropertyChanged
的函数。现在,它已经设置好了,后面代码中的计时器会像按下停止按钮一样正确地重置按钮。
这样做的一个缺点是,由于某种原因,
StopMeasurementCommand
调用的方法连续被激发了两次,但这是一个不同的问题。