Background:
I have an application that has list of CheckBoxes and Button.
If the user selects a (or multiple) CheckBox and Click on the button, the Button event handler checks which CheckBox is checked. Based on that, it runs a process (ie. gpupate).
Then, I have an image (Visibility = "hidden") next to CheckBox in XAML. After the button is clicked, the code behind sets it to Visibility.Visible.
After the process is done, the Source of the image is changed to a different image. Bascically, the first image shows the process is running, second image is a check showing its completed.
I am trying to implement INotifyPropertyChanged Interface, so the UI is updated automatically after I change the visibility. Sometimes, UI is not updated after the the visibility is changed because there are number of CheckBoxes.
However, since I am not using any property, I cannot really bind it to something (or may be I am missing something) and INPC interface.
How can I implement the interface (or similar functionality).
XAML Code
<StackPanel Orientation="Horizontal">
<Image x:Name="oneImage"
Source="{StaticResource inProcessImage}"
Visibility="Hidden" />
<CheckBox x:Name="oneCheckBox"
Content="CheckBox"
Style="{StaticResource normalCheckBox}"/>
</StackPanel>
Code Behind inside Button Event Handler
if (oneCheckBox.IsChecked ?? false)
{
oneImage.Visibility = Visibility.Visible;
await Task.Run(() =>
{
//run GPUpdate
});
deleteHistoryImage.Source = (ImageSource)Resources["doneCheckImage"];
}
I do not have anything regarding the implementation of interface because I do not know what do I need to bind Visibility modifier with.
This is not what you're looking for, but it will update the GUI manually.
public void UpdateUI()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { }));
}
call UpdateUI(); after you change the visibility.
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.
Thanks for visiting.
I am having trouble refreshing a Telerik grid when the Refresh button is clicked. In other words, if the user changes the data and decides not to save it, clicking the refresh button should reload the grid with the original values.
Here is my XAML in the View for the Refresh:
<Button Content="Refresh" Grid.Row="1" Margin="92,5,0,11" Command="{x:Bind ViewModel.RefreshDataCommand}"/>
Here is my XAML in the View for the Grid:
<tg:RadDataGrid ColumnDataOperationsMode="Flyout" x:Name="grid" ItemsSource="{Binding Source,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{x:Bind ViewModel.CurrentUserData, Mode=TwoWay}" UserEditMode="Inline" Grid.ColumnSpan="4" Grid.Row="1" AutoGenerateColumns="False">
Notice that in the above code the grid is bound to the Source property in the ViewModel. Here is the bound property in the ViewModel:
public ObservableCollection<UserData> Source
{
get
{
try
{
udCol = GetUserData(FileName).Result;
return udCol;
}
catch (AggregateException ex)
{
return null;
}
}
set
{
udCol = value;
}
}
The above property automatically loads the data in the grid when the page is navigated to.
Here is the function that the Refresh button is bound to:
private void RefreshDataCommandAction()
{
udCol[0].Name = "test1";
CurrentUserData = udCol[0];
Source = udCol;
RaisePropertyChanged("Source");
}
I was experimenting in the above function, that is why the code looks redundant but no matter what I do the new assignment in this function does not update the UI. Ideally, the user will update the cell in the UI and click Refresh to go back to original or just reload the data. The ViewModel inherits ViewModelBase which contains INotifyPropertyChanged and I thought that that should be enough to propagate the changes to the UI when the property is changed. I don't want to break the MVVM pattern just to update the UI.
I would really appreciate some help. Thanks a lot in advance!!!
EDIT:
I changed my XAML in the View back to this because it broke my ADD functionality:
<tg:RadDataGrid ColumnDataOperationsMode="Flyout" x:Name="grid" ItemsSource="{x:Bind ViewModel.Source}" SelectedItem="{x:Bind ViewModel.CurrentUserData, Mode=TwoWay}" UserEditMode="Inline" Grid.ColumnSpan="4" Grid.Row="1" AutoGenerateColumns="False">
Source does not implement Change Notification, so the UI has no way of knowing you assigned a different instance.
When binding any form of list in MVVM, 3 parts need change notification:
the propery you expose the list on (source)
the list itself (observable collection takes care of that)
each property of the type you are exposing (every property of UserData)
I have a window with a button in it, and I need to remove it OR not depending on the argument passed to the window:
public MainWindow(bool removeControl)
{
InitializeComponent();
if (removeControl)
{
//code to remove the button
}
}
In the XAML file I declare a normal button:
<Button Width="120" Height="25" Content="Click" Name="ClickButton"></Button>
I know this can be done by doing the reverse thing which means add the button depending of the Boolean parameter, but I need to do so.
You can do:
mybutton.Visibility = Visibility.Collapsed;
...or if you really want it to be removed from the "logical tree"...then it all depends what "container"/parent that Button is in, in how you remove it.
Disconnecting an element from any/unspecified parent container in WPF
Remove Control from Window in WPF
http://joe-bq-wang.iteye.com/blog/1613370
I have a class MyWindow which inherits from Window. Within MyWindow, I have the following method to execute once my OK button is clicked:
private void OKButton_Click(object sender, RoutedEventArgs e)
{
var be = NameBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
this.Close();
}
XAML:
<Button Content="OK"
Click="OKButton_Click"
HorizontalAlignment="Left"
Margin="175,473,0,0"
VerticalAlignment="Top"
Width="75"
RenderTransformOrigin="-0.04,0.5"/>
In a separate class where I initialize my UI window, I say
MainWindow window = new MainWindow(ViewModel);
window.Show();
However, as soon as window.Show() is executed, the subsequent code is executed and I cannot actually interact with my window to do what I need to do. I feel like this is just a misunderstanding in how to actually use WPF in a larger context...any help?
Window.ShowDialog is what is needed to view the page. But one doesn't get the binding information as you did; which should be changed as well.
When the textbox loses focus it will update the binding so the code
var be = NameBox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
is not needed. (Is this a leftover form winform programming?) So I suggestion one not update a binding as such.
The only possible thing to do if the binding is not updated is to change the binding to use the mode of TwoWay which ensures a back and forth data transfer between the variable bound to and the textbox on the screen.
How can I display a Message box or some other similar notice show while the application is busy in the background, similar to this mockup:
There is a Busy Indicator in the WPF Extended Toolkit which I've used quite a lot:
The toolkit is conveniently available through NuGet which makes it really easy to add it as a reference to your project. I've personally used it (along with many of the other useful controls in that toolkit) in almost all of my recent WPF projects.
To use it, surround your controls in the XAML code with the busy indicator:
<extToolkit:BusyIndicator ...>
<Grid>
<Button Content="Click to do stuff" />
<!-- your other stuff here -->
</Grid>
</extToolkit:BusyIndicator>
Then you just have to set the IsBusy property to true when you want the popup to appear and false when it should be hidden. In a proper MVVM architecture, you would typically databind the property in XAML to a property in your viewmodel which you then set to true/false accordingly:
<extToolkit:BusyIndicator IsBusy="{Binding IsBusy}" >
But if you aren't using MVVM, you can of course set it manually from your code behind, typically in the button click handler:
Give the control a name to be able to refer to it from code:
<extToolkit:BusyIndicator x:Name="busyIndicator" >
And then, in your xaml.cs file:
void myButton_Click(object sender, RoutedEventArgs e)
{
busyIndicator.IsBusy = true;
// Start your background work - this has to be done on a separate thread,
// for example by using a BackgroundWorker
var worker = new BackgroundWorker();
worker.DoWork += (s,ev) => DoSomeWork();
worker.RunWorkerCompleted += (s,ev) => busyIndicator.IsBusy = false;
worker.RunWorkerAsync();
}
If you code MVVM it's easy:
1.)Add a Boolean Flag "IsBusy" to your ViewModel with change notification.
public bool IsBusy {get {return _isBusy;} set{_isBusy=value;OnPropertyChanged("IsBusy");}}
private bool _isBusy;
2.) Add two events to your Command "Started" and "Completed"
public event Action Completed;
public event Action Started;
3.) In your ViewModel, subscribe to those events and set the busy status.
LoadImagesCommand.Started += delegate { IsBusy = true; };
LoadImagesCommand.Completed += delegate { IsBusy = false; };
4.) In your Window, you can now bind to that status
<Popup Visibility="{Binding Path=IsBusy,Converter={StaticResource boolToVisibilityConverter}}"/>
Note that for the last step you must instanciate the boolToVisibilityConverter, so:
5.) Add the following to any loaded Resource Dictionary:
<BooleanToVisibilityConverter x:Key="boolToVisibilityConverter"/>
That's it! You can fill your Popup with the life you want...