I'm reading data from a serial port using an event listener from the SerialPort class. In my event handler, I need to update many (30-40) controls in my window with xml data coming over the serial port.
I know that I must use myControl.Dispatcher.Invoke() to update it since it's on a different thread, but is there a way to update lots of controls together, rather than doing a separate Invoke call for each (i.e. myCon1.Dispatcher.Invoke(), myCon2.Dispatcher.Invoke(), etc)?
I'm looking for something like calling Invoke on the container, and updating each child control individually, but I can't seem to work out how to accomplish this.
Thanks!
What you need to do is use MVVM.
You bind your controls to public properties on a ViewModel. Your VM can listen to the serial port, parse out the xml data, update its public properties, and then use INotifyPropertyChanged to tell the UI to update its bindings.
I'd suggest this route as you can batch notifications and, if you have to, use the Dispatcher to invoke the event on the UI thread.
UI:
<Window ...>
<Window.DataContext>
<me:SerialWindowViewModel />
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding LatestXml}/>
</Grid>
</Window>
SerialWindowViewModel:
public class SerialWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string LatestXml {get;set;}
private SerialWatcher _serialWatcher;
public SerialWindowViewModel()
{
_serialWatcher = new SerialWatcher();
_serialWatcher.Incoming += IncomingData;
}
private void IncomingData(object sender, DataEventArgs args)
{
LatestXml = args.Data;
OnPropertyChanged(new PropertyChangedEventArgs("LatestXml"));
}
OnPropertyChanged(PropertyChangedEventArgs args)
{
// tired of writing code; make this threadsafe and
// ensure it fires on the UI thread or that it doesn't matter
PropertyChanged(this, args);
}
}
And, if that isn't acceptable to you (and you want to program WPF like its a winforms app) you can use Dispatcher.CurrentDispatcher to Invoke once while you manually update all controls on your form. But that method stinks.
Related
I just noticed what I can update binding source from another thread and it just works.
So I prepared a demo below and my questions are:
Why binding works? Why rising notification from another thread doesn't throws?
Is it legal to update source from another thread like this?
I was always using and telling to others to use Dispatcher.Invoke, but maybe I simply don't know something? Maybe binding is always guaranteed to update its target in UI thread or something like this?
<TextBox x:Name="textBox" Text="{Binding Text}" />
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Text { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Task.Run(() =>
{
Thread.Sleep(3000); // just wait long enough to ensure window is shown
// works
Text = "123";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
// will crash with
// System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it'
textBox.Text = "123";
});
}
}
It's perfectly legal to set a source property on a background thread. The framework handles the marshaling for you under the hood.
If you however try to add or remove items from a source collection, it's a different story:
How to update only a property in an observable collection from thread other than dispatcher thread in WPF MVVM?
Target properties, or more exactly properties of DependencyObjects, can only be accessed on the thread on which the object was originally created though. But you don't need to use a dispatcher to set view model properties from a background thread.
I'm a web and backend programmer by nature. Normally I try to avaoid making windows programs. Now I have to make a WPF client.
I have a background task that raises an event every often time. (It is working like a poller and when the criteria are met an event is raised). Noob as I am I wrote this code that was attached to the event to update the UI.
private void IsDisconnectedEvent()
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
}
This gives an exception because I am not on the same thread. After some googling I found that I should change the code with:
private void IsDisconnectedEvent()
{
Dispatcher.Invoke(() =>
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
});
}
This works, but this is not the only event and thus makes my code horrible ugly. Are there better ways to do this?
Regarding this:
This works, but this is not the only event and thus makes my code
horrible ugly
Yes, your WPF-based code will definitely be extremely horrible unless you understand and embrace The WPF Mentality.
Basically, all interactions between your custom logic (AKA Business logic or Application Logic) and the WPF UI should manifest in the form of Declarative DataBinding as opposed to the traditional imperative approach.
This means that there should be nothing like this:
UserWindow.Visibility = Visibility.Hidden;
anywhere in your code, simply because introducing things like that makes your code dependent on the UI and thus only executable on the UI thread.
Instead, the WPF approach to that would be to declaratively DataBind the Visibility propety of the UI element (IN XAML) to a relevant bool property that you can operate from the outside, like this:
<UserWindow Visibility="{Binding ShowUserWindow, Converter={my:BoolToVisibilityConverter}}">
<!-- ... -->
</UserWindow>
Then, you would need to create a relevant class that contains the properties the UI is expecting to bind to. This is called a ViewModel.
Notice that in order to properly support Two-Way WPF DataBinding, your ViewModels must Implement the INotifyPropertyChanged interface.
When doing so, it is also convenient to have the PropertyChanged event from that interface marshalled to the UI thread, so that you no longer have to worry about setting the ViewModel's properties by using the Dispatcher.
Therefore our first step is to have all our ViewModels inherit from a class like this:
(taken from this answer):
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
Once we have our Property Change Notification Dispatch to the UI Thread in place, we can proceed to create a relevant ViewModel that suits, in this case, the UserWindow and it's DataBinding expectations:
public class UserViewModel: PropertyChangedBase
{
private bool _showUserWindow;
public bool ShowUserWindow
{
get {return _showUserWindow; }
set
{
_showUserWindow = value;
OnPropertyChanged("ShowUserWindow"); //This is important!!!
}
}
}
Finally, you would need to set the Window's DataContext to an instance of it's corresponding ViewModel. One simple way to do that is in the Window's constructor:
public UserWindow() //Window's Constructor
{
InitializeComponent(); //this is required.
DataContext = new UserViewModel(); //here we set the DataContext
}
As you can see in this example, there is literally no need to manipulate the UI element's properties in procedural code. This is good not only because it resolves the Thread Affinity issues (because now you can set the ShowUserWindow property from any thread), but also because it makes your ViewModels and logic completely decoupled from the UI and thus testable and more scalable.
This same concept applies to EVERYTHING in WPF.
One detail that I need to mention is that I'm making use of a technique of Combining MarkupExtension and IValueConverter in order to reduce the the XAML boilerplate involved in using Converters.
You can read more about that in the link and also the MSDN DataBinding page linked above.
Let me know if you need further details.
I'm working on converting some code to a more proper MVVM implementation using DataTemplates and am having problems with certain kinds of UI validation.
I've got no problems with validation in the View Models -- IDataErrorInfo is implemented and everything is fine. What I've got a problem with is UI binding errors where they might put letters in a TextBox bound to an int.
Previously, I used :
System.Windows.Controls.Validation.AddErrorHandler(userControl, handler)
... and kept a count of errors added and removed to know whether all the form's data was OK.
But now that I'm doing MVVM I don't have access to the userControl to set up this handler. So I don't really have a hook to get this started.
Is there some sort of global DataTemplateApplied event handler available where I could do something like:
void OnDataTemplateApplied(object data, Control template)
{
if (data is MyViewModelBase)
{
Validation.AddErrorHandler(template, handler);
}
}
Alternatively, maybe I can call AddErrorHandler once in the bootstrapper for the outer Shell window, and then each time the event is fired somehow figure out which ViewModel is powering that particular control?
I know some people like making all VM fields strings and doing lots of type conversion in the VM -- that's not going to be realistic for our system for a variety of reasons.
You might be interested in this answer: https://stackoverflow.com/a/13335971/1094526
The main idea is exactly what you said (subscribe to the error handler). As I understand, the problem is you don't have access to the control from the ViewModel, but it isn't hard to solve
In a project I'm working, I exposed two methods from my ViewModel: AddUIError and RemoveUIError. I create an event handler in my View and there I cast the DataContext to the type of my ViewModel and call AddUIError or RemoveUIError depending on what happened.
I am using DataTemplates to associate a View with a ViewModel, so when the template is applied, the DataContext is automatically set to the ViewModel. If you want, you can store your ViewModel in a private field (in the View) and update the reference each time the DataContext changed (there is a DataContextChanged event)
If this will be done in multiple ViewModels, you can put both methods (AddUIError and RemoveUIError) in a class like ViewModelBase and move the ValidationError event handling to a Behavior and use it in each view.
More info about the behavior part:
The Behavior class is part of the Expression Blend SDK, so you will need it if you want to follow this way.
Behaviors are useful to attach some common functionality to many components without creating derived classes, for example.
First, we need to define the AddUIError and RemoveUIError in a class named ViewModelBase (which is, of course, the base class for all other ViewModels):
class ViewModelBase {
public void AddUIError(...) {/* Details ommitted */ }
public void RemoveUIError(...) {/* Details ommitted */ }
}
Then, create a Behavior by subclassing Behavior. We use FrameworkElement as the template argument so this behavior can be attached to any FrameworkElement (or derived class) instance:
class NotifyDataErrorsBehavior : Behavior<FrameworkElement>
{
// Called when the the Behavior is attached
protected override void OnAttached()
{
base.OnAttached();
// Initialize the handler for the Validation Error Event
_handler = new RoutedEventHandler(OnValidationRaised);
// Add the handler to the event from the element which is attaching this behavior
AssociatedObject.AddHandler(System.Windows.Controls.Validation.ErrorEvent, _handler);
}
protected override void OnDetaching()
{
base.OnDetaching();
// Remove the event handler from the associated object
AssociatedObject.RemoveHandler(System.Windows.Controls.Validation.ErrorEvent, _handler);
}
private RoutedEventHandler _handler = null;
private void OnValidationRaised(object sender, RoutedEventArgs e)
{
var args = (System.Windows.Controls.ValidationErrorEventArgs)e;
ViewModelBase viewModel = AssociatedObject.DataContext as ViewModelBase;
if (viewModel != null)
{
// You can add only Exception validation errors if you want..
if (args.Action == ValidationErrorEventAction.Added)
viewModel.AddUIValidationError(...);
else if (args.Action == ValidationErrorEventAction.Removed)
viewModel.RemoveUIValidationError(...);
else
throw new NotSupportedException("ValidationErrorEventAction has changed");
}
}
}
And finally just use it in XAML:
1. Add a reference to the namespace where NotifyDataErrorsBehavior is located, and also a reference to System.Windows.Interactivity namespace (from Expression Blend SDK):
<UserControl
...
xmlns:behavior="clr-namespace:MyApp.Behaviors"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
>
2. Add the behavior (at the same level as the content of your UserControl:
<i:Interaction.Behaviors>
<behavior:NotifyDataErrorsBehavior/>
</i:Interaction.Behaviors>
Ex:
<UserControl
...
xmlns:behavior="clr-namespace:MyApp.Behaviors"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
>
<i:Interaction.Behaviors>
<behavior:NotifyDataErrorsBehavior/>
</i:Interaction.Behaviors>
<Grid>
...
</Grid>
</UserControl>
Consider the following code:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Slider ValueChanged="slider_ValueChanged/>
<TextBox x:Name="counter"/>
</StackPanel>
</Window>
and
namespace Project1
{
public partial class Window1 : Window
{
public MainWindow() { InitializeComponent(); }
void slider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
counter.Text = e.NewValue.ToString();
}
}
}
Slider will raise its ValueChanged event during initialization while counter is still null.
This is an example of a larger problem that I've been running into using WPF, that UI events can fire at any time, and that there is no single place where I can put my initialization code so that it's guaranteed to run after all the pointers owned by the WPF system have been initialized but before any UI events have fired.
What is the most elegant way to deal with this? The fact that this specific example should use data binding is beside the point.
There are many ways to deal with this, depending on your situation
First off, you could simply recognize the fact that the object might not be initialized and check for that before processing. For example,
if (counter.Text != null)
counter.Text = e.NewValue.ToString();
Second, you could attach your events in the Loaded event of the object so they don't fire until after the object has been initialized.
void Counter_Loaded(object sender, EventArgs e)
{
slider.ValueChanged += Slider_ValueChanged;
}
void Counter_Unloaded(object sender, EventArgs e)
{
slider.ValueChanged -= Slider_ValueChanged;
}
And last of all, you can use WPF's Dispatcher to run events on the UI thread at a different DispatcherPriority. The default is Normal, which runs after Loaded, Render, and DataBind operations
Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
new Action(delegate() { counter.Text = e.NewValue.ToString(); }));
The true answer to this question is to use the MVVM pattern where window code behind files contain little to no initialization code.
In this pattern, the UI is connected to the rest of the code with data binding only. You write special view-model classes that implement INotifyPropertyChanged and take your business logic and expose it as a series of properties that UI binds to.
Naturally, you fully control how your view-models initialize.
I am little new to Command binding so this might be a trivial question to many. I know that we can add Command bindings in xaml of a window and give its correspondng property in viewmodel. This viewmodel will be given to the DataContext of the window. Something like the following
--app.xaml.cs
mainWindow.DataContext = viewModel;
-- xaml
lt;Button Grid.Row="1" HorizontalAlignment="Right" Margin="0,3,18,3" Name="button1" Width="110"
Command="{Binding LoadCommand}">_Load</Button>
-- viewmodel
/// <summary>
/// Gets the load command.
/// </summary>
/// <value>The load command.</value>
public ICommand LoadCommand
{
get
{
if (m_LoadCommand == null)
{
m_LoadCommand = new RelayCommand(param => CanLoad(), param => Load());
}
return m_LoadCommand;
}
}
Here the relaycommand is a class which implements ICommand interface. CanLoad() and Load() are the methods which will get executed for canexecute and execute action of the relaycommand respectively. This is the click event of the button which is handled.
I have a user control which has a custom routedevent registered in it and the user control is then used on a window. I am currently adding the event handler explicitly in code.
//hook up event listeners on the actual UserControl instance
this.ucCustomEvent1.CustomClick += new RoutedEventHandler(ucCustomEvent_CustomClick);
//hook up event listeners on the main window (Window1)
this.AddHandler(UserControlThatCreatesEvent.CustomClickEvent, new RoutedEventHandler(ucCustomEvent_CustomClick));
I dont want to hook up the routedevent explicitly in code but in the xaml in the similar way as in the button example. I have uploaded the working sample code here for your perusal.
I'm not sure I fully understand your question but I hope one of my answers below helps you out.
To attach a "direct" event handler in XAML, just do the following:
<c:MyUserControl x:Name="uc1" CustomClick="uc1_CustomClickHandler"/>
To hook up a handler for the (routed) event of one element (e.g. the CustomClick event in your example) to another element (e.g. the parent window):
<Window c:MyUserControl.CustomClick="ucCustomEvent_CustomClick"/>
Now, if you want to tie up an event in your UI to a Command in your ViewModel, you will need attached behaviors to do that. There are lots of frameworks around featuring different implementations of this. Here's one you can try out: http://sachabarber.net/?p=514. It will allow you to do something like the following in your code:
<c:MyUserControl local:CommandBehavior.RoutedEventName="MyCustomClick"
local:CommandBehavior.TheCommandToRun="{Binding MyViewModelCommand}"/>
Hope this helps.