In my MainPage.xaml I have a ListBox, with a datatemplate like the following:
<DataTemplate>
<TextBlock Name="DateTextBlock" Text="{Binding Modified, Converter={StaticResource RelativeTimeConverter}}"/>
</DataTemplate>
When the app is launched, the conversion is done once, and then until I relaunch the app, the textblock's text remains the same, even if I navigate to another page and return to MainPage.xaml .
All I want is to use the converter continually, and not only once when the Modified property changes, so as to show that time goes by, as the user is using my app. How could this be accomplished?
Do you use INotifyPropertyChanged? Please see this article explaining data binding pretty clear.
EDIT: According to new information from comments you have use INotifyPropertyChanged, but searching for solution to notify UI about property has changed on regular basis. So consider using some sort of timers, for example DispatcherTimer:
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(4);
timer.Tick += delegate(object s, EventArgs args)
{
foreach (YourItem item in ViewModel.Items)
{
item.NotifyPropertyChanged("Modified");
};
}
timer.Start();
Also (of course) add this method to YourItem class:
public void NotifyPropertyChanged(string propertyName)
{
OnPropertyChanged(propertyName);
}
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.
In my code, I have an UIElement variable I set with certain button presses.
Now, I have this variable:
public UIElement currentMenu;
Set to this value:
currentMenu = (UIElement)Resources["Home"];
I get it from the Resources so I don't have to manage it messily in the code-behind, I will export the Resources to a seperate ResourceDictionary once I get this problem solved.
My SplitView looks like this:
<SplitView x:Name="NavPane" OpenPaneLength="250" CompactPaneLength="50" Content="{x:Bind currentMenu}" DisplayMode="CompactOverlay" IsPaneOpen="False" PaneClosing="NavPane_PaneClosing">
The prblem comes in at this point, the Binding crashes the entire application with an unhndled win32 exception. I get no description and the error code changes every time. I have checked with break points whether this behaviour is actually caused by the binding, and it is.
If you have any suggestions on what might be going wrong here, please post an answer. I will supply any additional information needed (if reasonable, I'm not going to send you my entire project files)
Any help is appreciated!
Your problem that you are using a variable, not a property.
private UIElement currentMenu;
public string CurrentMenu
{
get { return currentMenu; }
set {
currentMenu=value);
OnPropertyChanged("CurrentMenu");
}
}
So the basic rules to bind Control to a "varaible":
Variable should be a property, not a field.
it should be public.
Either a notifying property (suitable for model classes) or a dependency property (suitable for view classes)
To notify UI you should implement INotifyPropertyChanged:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Update:
Your Bidning should looks like:
<SplitView x:Name="NavPane" OpenPaneLength="250" CompactPaneLength="50"
Content="{Binding CurrentMenu}" DisplayMode="CompactOverlay" IsPaneOpen="False"
PaneClosing="NavPane_PaneClosing">
I have found the answer to my question!
Namely, this was not the way to do it. Instead, I declared a Frame inside the Content of the SplitView:
<SplitView.Content>
<Frame x:Name="activeMenu"/>
</SplitView.Content>
Then, I use the Frame.Navigate() function to load my menus into the Frame:
public MainPage()
{
DataContext = this;
this.InitializeComponent();
SetMenu(0);
}
private void SetMenu(int key)
{
switch (key)
{
case 0:
activeMenu.Navigate(typeof(HomeMenu));
break;
//You can add in as many cases as you need
}
}
What you then need is to set all the menus you want as separate Pages within your project files. in this example, HomeMenu.xaml contains the grid for the menu people see upon starting up the app.
This solved the problem, but thank you to everyone (StepUp) who contributed to the original (unfortunately unsuccessful) solution!
The question is very simple, yet every time I see similar questions answered here, the answers don't explain a way to do it with a simple example. Here's my code:
xaml:
<ListBox Name="ListBox_PuntosIntermedios" MaxHeight="80" Height="80" ScrollViewer.VerticalScrollBarVisibility="Auto">
</ListBox>
And here it is the list of items:
List<string> Lista_punto_intermedio = new List<string>();
All I did in the load method of the wpf window, was this:
Lista_punto_intermedio.Add("testing...");
ListBox_PuntosIntermedios.ItemsSource = Lista_punto_intermedio;
It displays the item "testing..." correctly, but when I add a new item in the list, it's not shown in the Listbox. How can I correct my code to show the items without using ListBox_PuntosIntermedios.Items.Refresh(); that sometimes gives me errors that are not even displayed by the debugger.
I've seen other answers, that say "use inotify..." "use mvvm..." but they don't show you an easy way to do it for noobs like myself.
Thanks in advance for your help
The easiest thing to do in your example is to use an ObservableCollection<string> (in the System.Collections.ObjectModel namespace) instead of a List. ObservableCollection<> will notify your ListBox when items are added/removed to the collection and your UI will update via the magic of WPF data binding. This gets you halfway there as your UI will update when the collection changes (new items will appear in the ListBox and removed items will be removed from the ListBox).
ObservableCollection<string> Lista_punto_intermedio = new ObservableCollection<string>();
Then, you probably want your ListBox to update when one of the strings change as well (example: if "testing..." was updated to "working...", you probably want your ListBox to display "working..."). For this to work with WPF data binding, you need to implement IPropertyNotifyChanged on the objects in your ObservableCollection. To do that, you can introduce a new class with a string property for your text. Maybe something like this:
public class MyNotifyableText : INotifyPropertyChanged
{
private string _myText;
public string MyText {
get { return this._myText; }
set
{
if(this._myText!= value)
{
this._myText= value;
this.NotifyPropertyChanged("MyText");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
This object will send notifications to the WPF data bindings when the "MyText" property is changed and this will allow your ListBox to update accordingly. To tie this new object and your ListBox together, you will have to change your XAML so the ListBox displays your "MyText" property and change the ObservableCollection<string> to be ObservableCollection<MyNotifyableText>
Here's a final code sample:
XAML (note the DisplayMemberPath attribute):
<ListBox Name="ListBox_PuntosIntermedios" DisplayMemberPath="MyText" MaxHeight="80" Height="80" ScrollViewer.VerticalScrollBarVisibility="Auto" >
</ListBox>
List of items:
ObservableCollection<MyNotifyableText> Lista_punto_intermedio = new ObservableCollection<MyNotifyableText>();
Load:
Lista_punto_intermedio.Add(new MyNotifyableText(){ MyText="testing..." });
ListBox_PuntosIntermedios.ItemsSource = Lista_punto_intermedio;
Lastly, I found this tutorial helpful in learning about WPF data binding: http://www.wpf-tutorial.com/data-binding/responding-to-changes/
I created a very simple sample solution here.
If you are developing with WPF and also want to use 2-way binding, your best bet is MVVM. There is a learning curve but it is worth it.
In the sample I used MVVMLight toolkit to simplify a few things. Hope this helps.
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" />
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...