Prevent using Dispatcher.Invoke in WPF code - c#

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.

Related

How to update UI change when property changed in MVVM

I'm trying to accomplish a correct MVVM architecture in WPF.
I have a player, in the "Model" section there is a Boolean property that says if we are "playing" right now.
public bool IsPlaying
{
get
{
return isPlaying;
}
set
{
isPlaying = value;
OnPropertyChanged("IsPlaying");
}
}
(Notice I implemented the "INotifyPropertyChanged" interface, so the OnPropertyChanged function reports the change)
in my ViewModel, I have a ImageSource property called "ToggleButtonIcon":
public ImageSource ToggleButtonIcon
{
get
{
if (Model.IsPlaying)
return pauseIcon;
else
return playIcon;
}
}
Which I bind to a "TogglePlayButton" in the view:
<cc:IconButton x:Name="TogglePlayButton"
Style="{StaticResource playButton}"
ImageSource="{Binding Path=ToggleButtonIcon,
UpdateSourceTrigger=PropertyChanged}"
Command="{Binding Path=TogglePlayCommand}"/>
(It's a custom control, but it's working, I checked)
Of course I want the button to change its image icon according to if it is playing (pause) and if it is not playing (play).
Problem is the ToggleButtonIcon does not notify when it changes, and I can't implement the INotifyValueChanged in the ViewModel section because a. I understood that's not a part of the MVVM architexture, and b. I don't know when it changes since it depends on the IsPlaying property of Model.
I thought about putting the ToggleButtonIcon property on the Model section, but that's not "Business Logic" so I don't think that's the right way.
I also thought about using a converter and bind the IconButton directly to "IsPlaying", which would probably work, but I read here: How can WPF Converters be used in an MVVM pattern? that you should not use converters at all in MVVM because you can do any convertion you want in the "ViewModel" Section.
What's the best way to accomplish this in MVVM architecture?
To me, IsPlaying should be in the ViewModel, with change notification implemented, as it represents an application state of sorts.
To solve the issue I would recommend taking the ToggleButtonIcon out of the ViewModel, and creating a DataTrigger on the IconButton control (via its Style), that binds to the IsPlaying property (on the ViewModel) and alters the ImageSource property based on that.
The model of MVVM should only hold class entities and those entities can on occasion have an INotiftPropertyChanged, but generally they do not.
What your intent though is that it is to convey a status and that should be on your viewmodel.
I would recommend that you have the status of IsPlaying be on the View Model (VM) and bind to that. Then in the command of TogglePlayCommand, it will set that property on the VM.
That way both items update propertly on a change to either. You can still new up your original object in the model and on the Setter of the VM's IsPlaying set the class instance property to its value if needed.
Take a look at my blog post Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding. Take note on how I use OnPropertyChanged to push change messages on other operations which can all for the flexibility you seek as well as having the viewmodel hold statuses and not the models.
You should put bool on class which implements interface INotifyPropertyChange:
Here an example:
public class Game : INotifyPropertyChanged
{
private bool _isPlaying;
public string IsPlaying
{
get { return _isPlaying; }
set {
_isPlaying = value;
this.NotifyPropertyChangedBool();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChangedBool()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs("IsPlaying"));
}
}

WPF quick notification control

Developing a WPF application using MVVM, I have hit a point where I've got a "save" button on an editable datagrid which does behind the scenes processing but doesn't actually update anything in the UI, so the user has no way of knowing that the save has been successful.
I'm pretty new to WPF, and I presumed there would be a simple flash message control that one could use to notify the user of success and then faded away without them having to do anything. But it seems there's nothing in vanilla WPF that can do this, and there don't seem to be a lot of custom solutions either.
I don't want to use any kind of messagebox because it forces the user to take an action to dismiss the alert - I need something that breifly flashes a message without interfering with their workflow. I'm after something a bit like the JavaScript Toastr library -
http://codeseven.github.io/toastr/demo.html
can anyone either point me at an existing control for this, or where I might start at building one?
I think you don't need any third party controls. You always may create a custom control, paste it in a layout and bind layouts Visibility property to your view model. Another option is to use StatusBar to notify clients like in Word or VisualStudio. There is a just brief example:
Somewhere in xaml:
// ..
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<Label Content="{Binding Message}"></Label>
</StatusBarItem>
</StatusBar>
// ..
Somewhere in your code (I like to use async/await with WPF):
// ..
statusBarViewModel.Message = "Processing the file..."; // assumed that you bind this view model to the view
await DoWork(); // do much work
await statusBarViewModel.ShowMessageAndHide("File saved"); // show final message and hide it after some time
// ..
And StatusBarViewModel:
public class StatusBarViewModel : INotifyPropertyChanged
{
private string message = string.Empty;
public string Message
{
get { return message; }
set
{
if (value == message) return;
message = value;
OnPropertyChanged();
}
}
public async Task ShowMessageAndHide(string message)
{
Message = message;
await Task.Delay(5000);
Message = string.Empty;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The way I do simple notifications about the end of processing in WPF is by displaying a tooltip. You can open the tooltip from code:
https://stackoverflow.com/a/1911721/3391743
ToolTip tooltip = new ToolTip{ Content = "My Tooltip" };
NameTextBox.ToolTip = tooltip;
tooltip.IsOpen = true;
Then you can use a timer like here:
https://stackoverflow.com/a/1091753/3391743
As for IImplementPropertyChanged, there's a nice attribute [PropertyChanged] in Fody Property Changed assembly available via NuGet, which does all the plumbing for you.
Look here for reference on Fody: https://github.com/Fody/PropertyChanged
You could use the "NotifyIcon" from hardcodet to achieve a toast-like notification in WPF.
On a side note, in order for your UI to update, the binding engine will need to know when something has changed. In .NET, the INotifyPropertyChanged interface serves this purpose, and you'll need to implement that on the members you want to update in your UI when they are changed.
Here is a SO post which covers how to implement it.
There are a couple of parts to what you are asking:
To have code that is executed "behind the scenes", it sounds like you are asking about asynchronous programming. See async/await.
To have a custom visual that can interact with the view-model, you should take a look at Microsoft Prism's IInteractionRequest implementation.
Here is a good example of a codeproject post talking about how to do this with the Microsoft Prism framework, however you can adapt the code to work for you if you are not using that framework.

WPF C# ObservableCollection Not updating GUI

Thanks to the advice of tencntraze this question has been solved.
See the bottom of this question and tencntraze's answer for the solution.
I have a rather strange bug in my program that I'm currently wrestling with.
I'm developing an MVVM application, and I have a simple ListBox in my View with its ItemsSource bound to an ObservableCollection of strings. Whenever I add an item to this collection, it /does/ fire an event to notify that the collection has changed, yet this isn't reflected on the GUI during run-time unless I attempt to manually resize the window as the program is running, almost as if a resizing the window forces the GUI to refresh.
The XAML for the relevant control is as follows
<ListBox ItemsSource="{Binding Path=LogWindow}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0,0,0,0" />
Which as you can see is perfectly normal. In my ViewModel for this view, I have a public property called LogWindow defined as follows
public ObservableCollection<string> LogWindow
{
get
{
return _logWindow;
}
set
{
_logWindow = value;
OnPropertyChanged("LogWindow");
}
}
The private member for this is as follows
private ObservableCollection<string> _logWindow;
In the ViewModel constructor I initialize _logWindow and wire up LogWindow CollectionChanged event to a handler which in turn fires a PropertyChanged notification.
My code for this is as follows
public MainWindowViewModel(IDialogService dialogService)
{
... skipped for brevity
LogWindow = new ObservableCollection<string>();
LogWindow.CollectionChanged += LogWindow_CollectionChanged;
...
}
The CollectionChanged event handler is as follows
void LogWindow_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("LogWindow");
}
And finally when my View receives notification that it has received a new message from the Model, I add a new string to the observable collection as follows
LogWindow.Add(_vehicleWeightManager.SerialMessage);
When LogWindow.Add happens, my event listener fires and my breakpoint is hit to confirm the code gets that far as illustrated below
After this my code calls the OnPropertyChanged function which is inherited from my ViewModelBase, this functions fine for my other GUI elements such as labels and the like, so that almost certainly isn't the issue.
I applied a watch to LogWindow to keep track that the collection was indeed being added to, and it is as illustrated below
So at this stage I'm at a loss. My GUI will only update if I resize the window in some way, otherwise there is no visual update. The data is present in the bound property, and afaik the GUI is alerted when a new item is added to the property... Does anyone have any insight that may help?
Thanks to the advice of tencntraze and kallocain this has been solved.
The problem was caused by another thread attempting to add to the LogWindow collection which was causing erroneous behaviour. Rather than use Dispatcher as suggested, I used a TaskFactory code as follows.
TaskFactory uiFactory;
...
...
public MainWindowViewModel(IDialogService dialogService)
{
...
_logWindow = new ObservableCollection<string>();
uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
}
...
...
void _vehicleWeightManager_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
if (e.PropertyName == "VehicleWeight")
{
// Marshal the SerialMessage property to the GUI thread.
uiFactory.StartNew(() => LogWindow.Add(_vehicleWeightManager.SerialMessage.Replace("\n", ""))).Wait();
}
}
Further reading for the above method can be found here
If you're receiving the message on another thread, you'll need to invoke it back to the UI thread, otherwise there can be unpredictable behavior. Sometimes this is an exception, other times it works out to do nothing.
this.Dispatcher.BeginInvoke(new Action(() =>
{
LogWindow.Add(_vehicleWeightManager.SerialMessage);
}));

RejectChanges() with ViewModel and MVVM design pattern and UI update

I am having a problem with DomainContext.RejectChanges() and reflecting the rollback in the UI. Here is my scenario.
I have a Model (Entity) generated for use with RIA services (I'll call it Foo)
I have a ViewModel that wraps Foo and extends it (I'll call it FooViewModel)
I have a View that is using Binding to display and update data using the FooViewModel
I have an "outer" ViewModel that holds an ObservableCollection of FooViewModels
The "outer" View has a list box bound to the ObservableCollection
So essentially there is a listbox of FooViewModels on one screen...when you select an item a childwindow is displayed to edit that particular FooViewModel. The FooViewModel is serving both the listbox and the childwindow.
Editing works just fine. A change in the childwindow reflects in the listbox immediately because I am calling RaisePropertyChanged() when the viewmodel properties are updated.
However, If I perform a DomainContext.RejectChanges()...the underlying entity gets rolled back (all changes reverted as expected)...however the FooViewModel isn't aware that this change has occurred and thus the UI isn't updated. If I reselect the item in the listbox on the first screen, the childwindow is displayed with the rolled back changes (which is what I want). The listbox still isn't updated though.
When I reject changes, if I kludge a RaiseProperyChanged() for the field that I changed...the UI listbox does update.
How do I get the UI to update when the underlying entity is rejected?? And how do I do it without tracking what properties of the viewmodel were rolledback? There has to be an easy way to accomplish this that I am just missing.
Something you could try is use the PropertyChanged event on the underlying entity Foo to trigger a RaisePropertyChanged pass on the FooViewModel properties.
so making some assumptions (so this code make sense):
You have a private variables in your FooViewModel
private Foo _foo;
private DomainContext _context;
You have a method on your FooViewModel that is calling RejectChanges() on your domain context.
Like so:
public void RejectChanges()
{
_context.RejectChanges();
}
We have a method that raises the PropertyChanged event on our FooViewModel
Like so:
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(propertyName);
}
Ok, now we have that established, lets have a look at exactly what happens when you call RejectChanges() on a domain context.
When you call RejectChanges() this bubbles down through the DomainContext to its EntityContainer, then to each EntitySet in that container and then to each Entity in the set.
Once there (and in the EntitySet), it reapplies the original values if there was any, removes the entity if it was added, or adds it if it was deleted. If there was changes to the values, then it applies them back to the properties.
So theoretically, all the RaisePropertyChanged(), that are generated in the entity properties, should be triggered.
NOTE: I haven't actually tested this. If this isn't the case, then none of this works :P
So we can hook into PropertyChanged event of the Foo entity, and raise the PropertyChanged event on our FooViewModel.
so our RejectChanges() method might look like this:
public void RejectChanges()
{
Func<object, PropertyChangedEventArgs> handler = (sender, e) =>
{
RaisePropertyChanged(e.PropertyName);
};
_foo.PropertyChanged += handler;
_context.RejectChanges();
_foo.PropertyChanged -= handler;
}
So we hook up an event handler to our Foo entity, which calls the FooViewModel.RaisePropertyChanged method with the property name that is changing on the Foo entity.
Then we reject changes (which triggers the property changes),
then we unhook the event handler.
Pretty long winded, but I hope this helps :)
I presume that the call to DomainContext.RejectChanges() is happening within the ViewModel as you probably bound that to some command or method called from the parent ViewModel. Since all your bindings to the data is done on the ViewModel properties you will have to raise the property change event on them when you directly manipulate the model outside of those properties. Which you probably doing already.
public void RejectChanges()
{
DomainContext.RejectChanges();
RaisePropertyChangeOnAll();
}
How you implement RaisePropertyChangeOnAll() can be done simply with a list of RaisePropertyChange("...") for each property or you could do it through reflection (if Silverlight permissions allow, not too sure about it) by adding an Attribute on each property you want to raise. Find all the properties that are tagged with it and call RaisePropertyChanged on the MemberInfo.Name value.
[Raiseable]
public string SomeValue
{
...
}
Just an idea but may not be a perfect solution.

C#, WPF, MVVM and INotifyPropertyChanged

I'm getting confused; I thought I understood INotifyPropertyChanged.
I have a small WPF app with a frontend MainWindow class, a viewmodel in the middle and a model at the back.
The model in my case is the Simulator class.
The SimulatorViewModel is nearly transparent and just interfaces properties between MainWindow and Simulator.
Simulator implements INotifyPropertyChanged and each property setter in Simulator calls the RaisePropertyChanged method:
private string serialNumber;
public string SerialNumber
{
get { return serialNumber; }
set
{
serialNumber = value;
RaisePropertyChanged("SerialNumber");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
In the xaml, I have a TextBox with binding like this:
Text="{Binding Path=SerialNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
and the DataContext is the SimulatorViewModel (but see my comment about changing the DataContext to the model)
The ViewModel just passes the properties through:
public string SerialNumber
{
get { return Simulator.SerialNumber; }
set { Simulator.SerialNumber = value; }
}
Programmatic updates to the property SerialNumber in Simulator are not propagating up to the UI although curiously the initial value, set in the Simulator constructor, is getting there.
If I break on the SerialNumber setter in the Simulator and follow into RaisePropertyChanged, I find that PropertyChanged is null and so the event is not propagating upwards to the GUI.
Which leads me to several questions:
What exactly should be hooking into the PropertyChanged event? I'm looking to be more specific than just "WPF". What's the link between that event and the Binding statement in the xaml?
Why is the initial property value getting up to the UI at startup, but not subsequently?
Am I right to have Simulator (the model) implementing INotifyPropertyChanged, or should it be the ViewModel doing that? If the ViewModel does it, then programmatic changes in the model don't trigger PropertyChanged, so I'm unclear on the correct pattern. I realise my ViewModel is virtually redundant, but that's due to the simplicity of the project; a more complex one would make the ViewModel concept work harder. My understanding is that the ViewModel is the place to interface my unit tests.
The problem is that you're raising PropertyChanged on your model, but your view is bound to ViewModel. So your View subscribes only to ViewModel events (not Model's) - that is why textbox is not updated - because it never receives PropertyChanged event. One of possible solutions is to listen in ViewModel for Model PropertyChanged events and raise same event on ViewModel accordingly.
Initial value is being propagated because your setters/getters in ViewModel are correct, the problem is in events.
Yes, you are correct (see my #1), you should raise PropertyChanged on your ViewModel because View is bound to it. These events will not be triggered automatically after changes in Model, so you should listen for Model.PropertyChanged in ViewModel.
Simplest dirty fix to understand the idea:
SimulatorViewModel(Simulator model)
{
// this will re-raise Model's events on ViewModel
// VM should implement INotifyPropertyChanged
// method OnPropertyChanged should raise INPC for a given property
model.PropertyChanged += (sender, args) => this.OnPropertyChanged(args.PropertyName);
}
Always make sure that the instance of the class which has been set as Data-context should implement INotifyPropertyChanged
In your case Simulator implements INotifyPropertyChanged but SimulatorViewModel is set as DataContext, in this case the UI will come to know only if there are changes in the SimulatorViewModel but not in the Simulator.
What you can do is that:
You can expose some events or delegates in the Simulator class and hook to some methods in SimulatorViewModel class. Then when ever values in Simulator class changes, invoke the event or the delegate that will get Execute on SimulatorViewModel, now you can get the updated values from the simulator and update the properties in SimulatorViewModel. Make sure that you use different properties in both the class

Categories

Resources