Better solution for timer in ViewModel? - c#

I have a DispatcherTimer in a ViewModel for a graph component, to periodically update it (roll it).
Recently I discovered this is a massive resource leak since the ViewModel is created newly every time I navigate to the graph view and the DispatcherTimer is preventing the GC from destroying my ViewModel, because the Tick-Event holds a strong reference on it.
I solved this with a Wrapper around the DispatcherTimer which uses the FastSmartWeakEvent from Codeproject/Daniel Grunwald to avoid a strong reference to the VM and destroys itself once there are no more listeners:
public class WeakDispatcherTimer
{
/// <summary>
/// the actual timer
/// </summary>
private DispatcherTimer _timer;
public WeakDispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback, Dispatcher dispatcher)
{
Tick += callback;
_timer = new DispatcherTimer(interval, priority, Timer_Elapsed, dispatcher);
}
public void Start()
{
_timer.Start();
}
private void Timer_Elapsed(object sender, EventArgs e)
{
_tickEvent.Raise(sender, e);
if (_tickEvent.EventListenerCount == 0) // all listeners have been garbage collected
{
// kill the timer once the last listener is gone
_timer.Stop(); // this un-registers the timer from the dispatcher
_timer.Tick -= Timer_Elapsed; // this should make it possible to garbage-collect this wrapper
}
}
public event EventHandler Tick
{
add { _tickEvent.Add(value); }
remove { _tickEvent.Remove(value); }
}
FastSmartWeakEvent<EventHandler> _tickEvent = new FastSmartWeakEvent<EventHandler>();
}
This is how I use it. This was exactly the same without the "weak" before:
internal class MyViewModel : ViewModelBase
{
public MyViewModel()
{
if (!IsInDesignMode)
{
WeakDispatcherTimer repaintTimer = new WeakDispatcherTimer(TimeSpan.FromMilliseconds(300), DispatcherPriority.Render, RepaintTimer_Elapsed, Application.Current.Dispatcher);
repaintTimer.Start();
}
}
private void RepaintTimer_Elapsed(object sender, EventArgs e)
{
...
}
}
It seems to work good, but is this really the best/easiest solution or am I missing something?
I found absolutely nothing on google and can't believe I'm the only person using a timer in a ViewModel to update something and have a resource leak... That doesn't feel right!
UPDATE
As the graph component (SciChart) provides a method for attaching Modifiers (Behaviours), i wrote a SciChartRollingModifier, which is basically what AlexSeleznyov suggested in his answer. With a Behaviour it would have also been possible, but this is even simpler!
If anyone else needs a rolling SciChart LineGraph, this is how to do it:
public class SciChartRollingModifier : ChartModifierBase
{
DispatcherTimer _renderTimer;
private DateTime _oldNewestPoint;
public SciChartRollingModifier()
{
_renderTimer = new DispatcherTimer(RenderInterval, DispatcherPriority.Render, RenderTimer_Elapsed, Application.Current.Dispatcher);
}
/// <summary>
/// Updates the render interval one it's set by the property (e.g. with a binding or in XAML)
/// </summary>
private static void RenderInterval_PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
SciChartRollingModifier modifier = dependencyObject as SciChartRollingModifier;
if (modifier == null)
return;
modifier._renderTimer.Interval = modifier.RenderInterval;
}
/// <summary>
/// this method actually moves the graph and triggers a repaint by changing the visible range
/// </summary>
private void RenderTimer_Elapsed(object sender, EventArgs e)
{
DateRange maxRange = (DateRange)XAxis.GetMaximumRange();
var newestPoint = maxRange.Max;
if (newestPoint != _oldNewestPoint) // prevent the graph from repainting if nothing changed
XAxis.VisibleRange = new DateRange(newestPoint - TimeSpan, newestPoint);
_oldNewestPoint = newestPoint;
}
#region Dependency Properties
public static readonly DependencyProperty TimeSpanProperty = DependencyProperty.Register(
"TimeSpan", typeof (TimeSpan), typeof (SciChartRollingModifier), new PropertyMetadata(TimeSpan.FromMinutes(1)));
/// <summary>
/// This is the timespan the graph always shows in rolling mode. Default is 1min.
/// </summary>
public TimeSpan TimeSpan
{
get { return (TimeSpan) GetValue(TimeSpanProperty); }
set { SetValue(TimeSpanProperty, value); }
}
public static readonly DependencyProperty RenderIntervalProperty = DependencyProperty.Register(
"RenderInterval", typeof (TimeSpan), typeof (SciChartRollingModifier), new PropertyMetadata(System.TimeSpan.FromMilliseconds(300), RenderInterval_PropertyChangedCallback));
/// <summary>
/// This is the repaint interval. In this interval the graph moves a bit and repaints. Default is 300ms.
/// </summary>
public TimeSpan RenderInterval
{
get { return (TimeSpan) GetValue(RenderIntervalProperty); }
set { SetValue(RenderIntervalProperty, value); }
}
#endregion
#region Overrides of ChartModifierBase
protected override void OnIsEnabledChanged()
{
base.OnIsEnabledChanged();
// start/stop the timer only of the modifier is already attached
if (IsAttached)
_renderTimer.IsEnabled = IsEnabled;
}
#endregion
#region Overrides of ApiElementBase
public override void OnAttached()
{
base.OnAttached();
if (IsEnabled)
_renderTimer.Start();
}
public override void OnDetached()
{
base.OnDetached();
_renderTimer.Stop();
}
#endregion
}

I might be not getting exactly what you're after, but to me it looks like you're putting more functionality into ViewModel than it can handle. Having a timer in view model makes unit testing somewhat harder.
I'd have those steps extracted to a separate component which would notify ViewModel that timer interval elapsed. And, if implemented as an Interactivity Behavior, this separate component woudl know exactly when View is created/destroyed (via OnAttached/OnDetached methods) and, in turn, can start/stop timer.
One more benefit here is that you can unit-test that ViewModel with ease.

You could bind your View's Closing event to a Command in your ViewModel, calling Stop() method on your DispatchTimer. This would allow the timer and ViewModel to be CG:ed.
Consider View
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding CloseCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
and ViewModel
public class MyViewModel : ViewModelBase
{
public MyViewModel()
{
DispatcherTimer timer = new DispatcherTimer(
TimeSpan.FromSeconds(1),
DispatcherPriority.Render,
(sender, args) => Console.WriteLine(#"tick"),
Application.Current.Dispatcher);
timer.Start();
CloseCommand = new RelayCommand(() => timer.Stop());
}
public ICommand CloseCommand { get; set; }
}
Other solution could be making timer static or holding static reference to your VM in ViewModelLocator or similar place.

Related

Property change in Base ViewModel with prism

I'm using prism to develop an android app.
I'm trying to make a Base ViewModel. Inside this ViewModel I would like to set common properties to all my ViewModels.
public class BaseViewModel : BindableBase
{
protected INavigationService _navigationService;
protected IPageDialogService _dialogService;
public BaseViewModel(INavigationService navigationService, IPageDialogService dialogService)
{
_navigationService = navigationService;
_dialogService = dialogService;
}
private string _common;
/// <summary>
/// Common property
/// </summary>
public string CommonProperty
{
get { return _common; }
set
{
_common = value;
SetProperty(ref _common, value);
}
}
}
My problem is: when I set the common property in the constructor, works fine.
But when I´m setting the common property in OnNavigatingTo and using async, doesn´t work. The SetProperty is triggered when calling the OnNavigatingTo, but my binded label with this common property doesn´t refresh the value.
namespace TaskMobile.ViewModels.Tasks
{
/// <summary>
/// Specific view model
/// </summary>
public class AssignedViewModel : BaseViewModel, INavigatingAware
{
public AssignedViewModel(INavigationService navigationService, IPageDialogService dialogService) : base(navigationService,dialogService)
{
CommonProperty= "Jorge Tinoco"; // This works
}
public async void OnNavigatingTo(NavigationParameters parameters)
{
try
{
Models.Vehicle Current = await App.SettingsInDb.CurrentVehicle();
CommonProperty= Current.NameToShow; //This doesn´t works
}
catch (Exception e)
{
App.LogToDb.Error(e);
}
}
}
when you use SetProperty, you should not set value for the backfield.
so you should remove this line:
_common = value;
Because you are doing asynchronous invocation on a separate thread the UI is not being notified of the change.
The async void of OnNavigatingTo, which is not an event handler, means it is a fire and forget function running in a separate thread.
Reference Async/Await - Best Practices in Asynchronous Programming
Create a proper event and asynchronous event handler to perform your asynchronous operations there
For example
public class AssignedViewModel : BaseViewModel, INavigatingAware {
public AssignedViewModel(INavigationService navigationService, IPageDialogService dialogService)
: base(navigationService, dialogService) {
//Subscribe to event
this.navigatedTo += onNavigated;
}
public void OnNavigatingTo(NavigationParameters parameters) {
navigatedTo(this, EventArgs.Empty); //Raise event
}
private event EventHandler navigatedTo = degelate { };
private async void onNavigated(object sender, EventArgs args) {
try {
Models.Vehicle Current = await App.SettingsInDb.CurrentVehicle();
CommonProperty = Current.NameToShow; //On UI Thread
} catch (Exception e) {
App.LogToDb.Error(e);
}
}
}
That way when the awaited operation is completed the code will continue on the UI thread and it will get the property changed notification.

Progressbar progress during long task

It's my first post here, so I hope I'm doing everything correct.
I'm using the .NET Framework 4 Client Profile.
I want to load data from a .doc file into my program and work with this information. This can take a lot of time since I need to run through the tables of the document and check what's inside. That is already working, the only problem here is the screen is freezing and you can't see if something is happening.
Also I know this would be faster and way easier in excel, but since this type of data is and was always stored in word-documents in our company I have to keep it like that.
So what I want to do is count all rows from the tables that I have to read, set this as my Maximum Value for the Progress-Bar and then after each row I would count the value + 1.
I have my load Button with the Command bound to LoadWordDocCmd and the progress bar:
<Button Name="btnLoadFile"
Content="Load" Height="23"
Command="{Binding LoadWordDocCmd}"
HorizontalAlignment="Right" Margin="0,22,129,0"
VerticalAlignment="Top" Width="50"
Visibility="{Binding VisModeAddNew}"
/>
<ProgressBar HorizontalAlignment="Left" Height="24" Margin="574,52,0,0"
VerticalAlignment="Top" Width="306"
Name="prgBarAddNewLoadWord"
Minimum="0"
Maximum="{Binding AddNewProgressBarMaxVal, Mode=OneWay}"
Value="{Binding AddNewProgressBarValue, Mode=OneWay}"
Visibility="{Binding AddNewProgressBarVisible}"/>
Here is the RelayCommand:
/// <summary>
/// Relaycommand for Function loadWordDocument
/// </summary>
public RelayCommand LoadWordDocCmd
{
get
{
if (this.m_loadWordDocCmd == null)
{
this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
}
return m_loadWordDocCmd;
}
private set
{
this.m_loadWordDocCmd = value;
}
}
/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
bool ret = false;
if (this.m_fileSelected)
{
ret = true;
}
return ret;
}
What I already did was to work with a BackgroundWorker.
I was able to bind the Button-Command to a function that has a RelayCommand with the BackgroundWorker, but then I wasn't able to check the canExecute function anymore.
I used this to test the Progress-Bar, that was working :
xaml:
<Button ...
Command="{Binding Path=InstigateWorkCommand}"
/>
cs :
private BackgroundWorker worker;
private ICommand instigateWorkCommand;
public ProggressbarSampleViewModel()
{
this.instigateWorkCommand = new
RelayCommand(o => this.worker.RunWorkerAsync(), o => !this.worker.IsBusy);
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private int _currentProgress;
public int CurrentProgress
{
get { return this._currentProgress; }
private set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
private void DoWork(object sender, DoWorkEventArgs e)
{
// do time-consuming work here, calling ReportProgress as and when you can
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);
_currentProgress = i;
OnPropertyChanged("CurrentProgress");
}
}
But how can I get this to work with the canExecute ? Here is my function-Header:
/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)
Here is the Relay-Command Class:
public class RelayCommand : ICommand
{
private readonly Action<object> methodToExecute;
private readonly Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
public RelayCommand(Action<object> execute)
: this(execute, null) { }
public RelayCommand(Action<object> methodToExecute, Func<object, bool> canExecute)
{
this.methodToExecute = methodToExecute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
// wird keine canExecute-Funktion übergeben, so liefert diese
// true zurück, ansonsten wird die custom canExecute-Funktion
// mit den übergebenen Parametern aufgerufen.
return canExecute == null ? true : canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
methodToExecute(parameter);
}
}
Thank you for your help and I hope I posted this question correct!
I hope I understand your issue correctly.
The basic rule for a GUI application is: don't use the GUI thread for (time-consuming) data processing. You have to perform this task on a background thread.
Since you're using .NET 4.0 Client Profile, the async/await feature is not available to you. That would be the easiest solution, however.
You can do this with a ThreadPool instead. The BackgroundWorker is not recommended anymore.
In your XAML, you're binding the ProgressBar.Value property to a AddNewProgressBarValue property, so I assume you have a view-model with that property already. You have to ensure that changing AddNewProgressBarValue will raise the PropertyChanged event. And the good news is, the WPF Binding Engine automatically marshals the property value transfer operation to the GUI thread, so you don't need to care about which thread is changing a property your progress bar is bound to.
So the solution might look like this (not a production code, just an idea!):
class ViewModel : INotifyPropertyChanged
{
private bool isProcessing;
public bool AddNewProgressBarVisible
{
get { return this.isProcessing; }
// SetProperty here is a PRISM-like helper to set the backing field value
// and to raise the PropertyChanged event when needed.
// You might be using something similar.
private set { this.SetProperty(ref this.isProcessing, value, "AddNewProgressBarVisible");
}
private int progressValue;
public int AddNewProgressBarValue
{
get { return this.progressValue; }
private set { this.SetProperty(ref this.progressValue, value, "AddNewProgressBarValue");
}
// This is your command handler
private void LoadWordDocument(object parameter)
{
if (this.isProcessing)
{
// don't allow multiple operations at the same time
return;
}
// indicate that we're staring an operation:
// AddNewProgressBarVisible will set isProcessing = true
this.AddNewProgressBarVisible = true;
this.AddNewProgressBarValue = 0;
// Notify the bound button, that it has to re-evaluate its state.
// Effectively, this disables the button.
this.LoadWordDocCmd.RaiseCanExecuteChanged();
// Run the processing on a background thread.
ThreadPool.QueueUserWorkItem(this.DoLoadWordDocument);
}
private void DoLoadWordDocument(object state)
{
// Do your document loading here,
// this method will run on a background thread.
// ...
// You can update the progress bar value directly:
this.AddNewProgressBarValue = 42; // ...estimate the value first
// When you're done, don't forget to enable the button.
this.AddNewProgressBarVisible = false;
// We have to marshal this to the GUI thread since your ICommand
// implementation doesn't do this automatically
Application.Current.Dispatcher.Invoke(() => this.LoadWordDocCmd.RaiseCanExecuteChanged());
}
// this is your command enabler method
private bool CanLoadWordDoc(object parameter)
{
// if we're already loading a document, the command should be disabled
return this.m_fileSelected && !this.isProcessing;
}
}
I think that your ProggressbarSampleViewModel code sample is ok. I tested it and it works.
I am assuming that you want to change LoadWordDocCmd to have the behavior of InstigateWorkCommand. If you put the code from ProgressbarSampleViewModel into your actual ViewModel, you should have no problem accessing loadWordDocument and canLoadWordDoc. In addition, as mm8 mentioned, in your DoWork method you need to call RaiseCanExecuteChanged or else WPF will not check the CanExecute method.
Your ViewModel should look like bellow. See comments in upper case.
private BackgroundWorker worker;
private RelayCommand instigateWorkCommand; //CHANGE HERE
bool isBusy = false; // ADD THIS
public ProggressbarSampleViewModel()
{
//CHANGE NEXT LINE
this.instigateWorkCommand = new RelayCommand(
o => this.worker.RunWorkerAsync(),
o => !isBusy && canLoadWordDoc(null));
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
//REMOVE
//this.worker.ProgressChanged += this.ProgressChanged;
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private int _currentProgress;
public int CurrentProgress
{
get { return this._currentProgress; }
private set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
}
//REMOVE
//private void ProgressChanged(object sender, ProgressChangedEventArgs e)
//{
// this.CurrentProgress = e.ProgressPercentage;
//}
private void DoWork(object sender, DoWorkEventArgs e)
{
//ADD NEXT LINES
isBusy = true;
Application.Current.Dispatcher.BeginInvoke(
(Action)instigateWorkCommand.RaiseCanExecuteChanged);
// do time-consuming work here, calling ReportProgress as and when you can
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(10);
_currentProgress = i;
OnPropertyChanged("CurrentProgress");
}
//ADD NEXT LINES
isBusy = false;
Application.Current.Dispatcher.BeginInvoke(
(Action)instigateWorkCommand.RaiseCanExecuteChanged);
}
bool m_fileSelected = true; //CHANGE TO SEE THE EFFECT
//REMOVE
//RelayCommand m_loadWordDocCmd;
///// <summary>
///// Relaycommand for Function loadWordDocument
///// </summary>
//public RelayCommand LoadWordDocCmd
//{
// get
// {
// if (this.m_loadWordDocCmd == null)
// {
// this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
// }
// return m_loadWordDocCmd;
// }
// private set
// {
// this.m_loadWordDocCmd = value;
// }
//}
/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
bool ret = false;
if (this.m_fileSelected)
{
ret = true;
}
return ret;
}
/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)
{
}
Hope this helps.

WPF Silverlight Datagrid in real time, refresh? Timer?

I have certain datagrid which I need to "refresh" every... lets say 1 min.
Is a timer the best option?
public PageMain()
{
InitializeComponent();
DataGridFill();
InitTimer();
}
private void InitTimer()
{
disTimer = new Timer(new TimeSpan(0, 1, 0).TotalMilliseconds);
disTimer.Elapsed += disTimer_Elapsed;
disTimer.Start();
}
void disTimer_Elapsed(object sender, ElapsedEventArgs e)
{
DataGridFill();
}
private void DataGridFill()
{
var items = GetItems(1);
ICollectionView itemsView =
CollectionViewSource.GetDefaultView(items);
itemsView.GroupDescriptions.Add(new PropertyGroupDescription("MyCustomGroup"));
// Set the view as the DataContext for the DataGrid
AmbientesDataGrid.DataContext = itemsView;
}
Is there a less "dirty" solution?
The best way to "Refresh" a DataGrid is to bind it to a collection of items, and update the source collection of items every X minutes.
<DataGrid ItemsSource="{Binding MyCollection}" ... />
This way you never have to reference the DataGrid itself, so your UI logic and application logic stay separated, and if your refresh takes a while you can run it on a background thread without locking up your UI.
Because WPF can't update objects created on one thread from another thread, you may want to get your data and store in a temporary collection on a background thread, then update your bound collection on the main UI thread.
For the timing bit, use a Timer or possibly a DispatcherTimer if needed.
var timer = new System.Windows.Threading.DispatcherTimer();
timer.Tick += Timer_Tick;
timer.Interval = new TimeSpan(0,1,0);
timer.Start();
private void Timer_Tick(object sender, EventArgs e)
{
MyCollection = GetUpdatedCollectionData();
}
My prefered approach:
public sealed class ViewModel
{
/// <summary>
/// As this is readonly, the list property cannot change, just it's content so
/// I don't need to send notify messages.
/// </summary>
private readonly ObservableCollection<T> _list = new ObservableCollection<T>();
/// <summary>
/// Bind to me.
/// I publish as IEnumerable<T>, no need to show your inner workings.
/// </summary>
public IEnumerable<T> List { get { return _list; } }
/// <summary>
/// Add items. Call from a despatch timer if you wish.
/// </summary>
/// <param name="newItems"></param>
public void AddItems(IEnumerable<T> newItems)
{
foreach(var item in newItems)
{
_list.Add(item);
}
}
/// <summary>
/// Sets the list of items. Call from a despatch timer if you wish.
/// </summary>
/// <param name="newItems"></param>
public void SetItems(IEnumerable<T> newItems)
{
_list.Clear();
AddItems(newItems);
}
}
Don't like lack of decent AddRange/ReplaceRange in ObservableCollection<T>? Me neither, but here is an descendant to ObservableCollection<T> to add a message efficient AddRange, plus unit tests:
ObservableCollection Doesn't support AddRange method, so I get notified for each item added, besides what about INotifyCollectionChanging?

How to propagate PropertyChanged changes in DependencyProperty

I have a class which implements INotifyPropertyChanged. An instance of this class is declared as a DependencyProperty in a Window, e.g.,
public IMyClass MyClass
{
get { return (IMyClass)GetValue(MyClassProperty); }
set { SetValue(MyClassProperty, value); }
}
public static readonly DependencyProperty MyClassProperty=
DependencyProperty.Register("MyClass", typeof(IMyClass), typeof(MainWindow), new UIPropertyMetadata(null));
In the XAML, I have an element which is bound to this class using
Text="{Binding MyClass, Converter={StaticResource someConverter}}
Whenever I change a property in MyClass, I would like someConverter to be triggered. However, it only happens when I completely swap out MyClass. Is there a way to tie DependencyProperty updates to my MyClass PropertyChanged?
Update. In the spirit of AresAvatar's solution, here's what we have so far. The issue remaining is how to call InvalidateProperty (without having MyClass track it...)
public IMyClass MyClass
{
get { return (IMyClass)GetValue(MyClassProperty); }
set { SetValue(MyClassProperty, value); }
}
public static readonly DependencyProperty MyClassProperty =
DependencyProperty.Register("MyClass", typeof(IMyClass), typeof(MainWindow),
new UIPropertyMetadata(null, new PropertyChangedCallback(OnMyClassChanged)));
private static void OnMyClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
((IMyClass)e.OldValue).PropertyChanged -= ((MainWindow)d).MyClass_PropertyChanged;
}
if (e.NewValue != null)
{
((IMyClass)e.NewValue).PropertyChanged += ((MainWindow)d).MyClass_PropertyChanged;
}
}
private void MyClass_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.InvalidateProperty(MyClassProperty); <----- still does not refresh binding, but called.
}
Converters should not do more work than simple conversions, your question sounds like the converter uses a lot of properties of the object to create some combined value. Use a MultiBinding instead which hooks into all the different properties on the object you need, that way the MultiValueConverter on that MultiBinding will fire if any of those properties change.
Further, since you seem to create text you might be able to get away without using any converter at all as the StringFormat might be enough.
The only technique I've found is to call the binding's UpdateSource method in a strategically placed event handler, such as LostFocus.
private void mycontrol_LostFocus(object sender, RoutedEventArgs e)
{
if (mycontrol.IsModified)
{
var binding = mycontrol.GetBindingExpression(MyControl.FooBarProperty);
binding.UpdateSource();
}
}
If you don't care about chattiness or if your control doesn't take input focus, you could do this in mycontrol_PropertyChanged event or similar. However, forcing a conversion cycle on every property change or every keystroke may interfere with validation.
In MyClass, implement a NotifyPropertyChanged event. Then add a property changed callback to your MyClass DependencyProperty. In the DP's property changed callback, hook your new MyClass NotifyPropertyChanged event to a second callback function (and unhook the previous value, if any, with a -= operator). In the second callback function, call DependencyObject.InvalidateProperty so that the binding gets updated.
Edit: you may need to trigger a binding update with:
BindingExpressionBase exp = BindingOperations.GetBindingExpressionBase(this, Container.MyClassProperty);
if (exp != null)
exp.UpdateTarget();
class MyClass : INotifyPropertyChanged
{
/// <summary>
/// Event raised when a property is changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the property changed event
/// </summary>
/// <param name="e">The arguments to pass</param>
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
/// <summary>
/// Notify for property changed
/// </summary>
/// <param name="name">Property name</param>
protected void NotifyPropertyChanged(string name)
{
OnPropertyChanged(new PropertyChangedEventArgs(name));
}
/// <summary>
/// The parent container object
/// </summary>
public Container Parent { get; set; }
// Some data
int x;
}
class Container : DependencyObject
{
public static readonly DependencyProperty MyClassProperty = DependencyProperty.Register("MyClass", typeof(MyClass), typeof(Container), new FrameworkPropertyMetadata(MyClassPropChanged));
public MyClass MyClass
{
get { return (MyClass)GetValue(MyClassProperty); }
set { SetValue(MyClassProperty, value); }
}
void MyClassPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Container ct = d as Container;
if (ct == null)
return;
MyClass oldc = e.OldValue as MyClass;
if (oldc != null)
{
oldc.PropertyChanged -= new PropertyChangedEventHandler(MyClass_PropertyChanged);
oldc.Parent = null;
}
MyClass newc = e.NewValue as MyClass;
if (newc != null)
{
newc.Parent = ct;
newc.PropertyChanged += new PropertyChangedEventHandler(MyClass_PropertyChanged);
}
}
void MyClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MyClass mc = sender as MyClass;
if (mc == null || mc.Parent == null)
return;
mc.Parent.InvalidateProperty(Container.MyClassProperty);
}
}

Handling the window closing event with WPF / MVVM Light Toolkit

I'd like to handle the Closing event (when a user clicks the upper right 'X' button) of my window in order to eventually display a confirm message or/and cancel the closing.
I know how to do this in the code-behind: subscribe to the Closing event of the window then use the CancelEventArgs.Cancel property.
But I'm using MVVM so I'm not sure it's the good approach.
I think the good approach would be to bind the Closing event to a Command in my ViewModel.
I tried that:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding CloseCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
With an associated RelayCommand in my ViewModel but it doesn't work (the command's code is not executed).
I would simply associate the handler in the View constructor:
MyWindow()
{
// Set up ViewModel, assign to DataContext etc.
Closing += viewModel.OnWindowClosing;
}
Then add the handler to the ViewModel:
using System.ComponentModel;
public void OnWindowClosing(object sender, CancelEventArgs e)
{
// Handle closing logic, set e.Cancel as needed
}
In this case, you gain exactly nothing except complexity by using a more elaborate pattern with more indirection (5 extra lines of XAML plus Command pattern).
The "zero code-behind" mantra is not the goal in itself, the point is to decouple ViewModel from the View. Even when the event is bound in code-behind of the View, the ViewModel does not depend on the View and the closing logic can be unit-tested.
This code works just fine:
ViewModel.cs:
public ICommand WindowClosing
{
get
{
return new RelayCommand<CancelEventArgs>(
(args) =>{
});
}
}
and in XAML:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding WindowClosing}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
assuming that:
ViewModel is assigned to a DataContext of the main container.
xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL5"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
This option is even easier, and maybe is suitable for you. In your View Model constructor, you can subscribe the Main Window closing event like this:
Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing);
void MainWindow_Closing(object sender, CancelEventArgs e)
{
//Your code to handle the event
}
All the best.
Here is an answer according to the MVVM-pattern if you don't want to know about the Window (or any of its event) in the ViewModel.
public interface IClosing
{
/// <summary>
/// Executes when window is closing
/// </summary>
/// <returns>Whether the windows should be closed by the caller</returns>
bool OnClosing();
}
In the ViewModel add the interface and implementation
public bool OnClosing()
{
bool close = true;
//Ask whether to save changes och cancel etc
//close = false; //If you want to cancel close
return close;
}
In the Window I add the Closing event. This code behind doesn't break the MVVM pattern. The View can know about the viewmodel!
void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
IClosing context = DataContext as IClosing;
if (context != null)
{
e.Cancel = !context.OnClosing();
}
}
Geez, seems like a lot of code going on here for this. Stas above had the right approach for minimal effort. Here is my adaptation (using MVVMLight but should be recognizable)... Oh and the PassEventArgsToCommand="True" is definitely needed as indicated above.
(credit to Laurent Bugnion http://blog.galasoft.ch/archive/2009/10/18/clean-shutdown-in-silverlight-and-wpf-applications.aspx)
... MainWindow Xaml
...
WindowStyle="ThreeDBorderWindow"
WindowStartupLocation="Manual">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
In the view model:
///<summary>
/// public RelayCommand<CancelEventArgs> WindowClosingCommand
///</summary>
public RelayCommand<CancelEventArgs> WindowClosingCommand { get; private set; }
...
...
...
// Window Closing
WindowClosingCommand = new RelayCommand<CancelEventArgs>((args) =>
{
ShutdownService.MainWindowClosing(args);
},
(args) => CanShutdown);
in the ShutdownService
/// <summary>
/// ask the application to shutdown
/// </summary>
public static void MainWindowClosing(CancelEventArgs e)
{
e.Cancel = true; /// CANCEL THE CLOSE - let the shutdown service decide what to do with the shutdown request
RequestShutdown();
}
RequestShutdown looks something like the following but basicallyRequestShutdown or whatever it is named decides whether to shutdown the application or not (which will merrily close the window anyway):
...
...
...
/// <summary>
/// ask the application to shutdown
/// </summary>
public static void RequestShutdown()
{
// Unless one of the listeners aborted the shutdown, we proceed. If they abort the shutdown, they are responsible for restarting it too.
var shouldAbortShutdown = false;
Logger.InfoFormat("Application starting shutdown at {0}...", DateTime.Now);
var msg = new NotificationMessageAction<bool>(
Notifications.ConfirmShutdown,
shouldAbort => shouldAbortShutdown |= shouldAbort);
// recipients should answer either true or false with msg.execute(true) etc.
Messenger.Default.Send(msg, Notifications.ConfirmShutdown);
if (!shouldAbortShutdown)
{
// This time it is for real
Messenger.Default.Send(new NotificationMessage(Notifications.NotifyShutdown),
Notifications.NotifyShutdown);
Logger.InfoFormat("Application has shutdown at {0}", DateTime.Now);
Application.Current.Shutdown();
}
else
Logger.InfoFormat("Application shutdown aborted at {0}", DateTime.Now);
}
}
The asker should use STAS answer, but for readers who use prism and no galasoft/mvvmlight, they may want to try what I used:
In the definition at the top for window or usercontrol, etc define namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And just below that definition:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding WindowClosing}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Property in your viewmodel:
public ICommand WindowClosing { get; private set; }
Attach delegatecommand in your viewmodel constructor:
this.WindowClosing = new DelegateCommand<object>(this.OnWindowClosing);
Finally, your code you want to reach on close of the control/window/whatever:
private void OnWindowClosing(object obj)
{
//put code here
}
I would be tempted to use an event handler within your App.xaml.cs file that will allow you to decide on whether to close the application or not.
For example you could then have something like the following code in your App.xaml.cs file:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Create the ViewModel to attach the window to
MainWindow window = new MainWindow();
var viewModel = new MainWindowViewModel();
// Create the handler that will allow the window to close when the viewModel asks.
EventHandler handler = null;
handler = delegate
{
//***Code here to decide on closing the application****
//***returns resultClose which is true if we want to close***
if(resultClose == true)
{
viewModel.RequestClose -= handler;
window.Close();
}
}
viewModel.RequestClose += handler;
window.DataContaxt = viewModel;
window.Show();
}
Then within your MainWindowViewModel code you could have the following:
#region Fields
RelayCommand closeCommand;
#endregion
#region CloseCommand
/// <summary>
/// Returns the command that, when invoked, attempts
/// to remove this workspace from the user interface.
/// </summary>
public ICommand CloseCommand
{
get
{
if (closeCommand == null)
closeCommand = new RelayCommand(param => this.OnRequestClose());
return closeCommand;
}
}
#endregion // CloseCommand
#region RequestClose [event]
/// <summary>
/// Raised when this workspace should be removed from the UI.
/// </summary>
public event EventHandler RequestClose;
/// <summary>
/// If requested to close and a RequestClose delegate has been set then call it.
/// </summary>
void OnRequestClose()
{
EventHandler handler = this.RequestClose;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
#endregion // RequestClose [event]
Basically, window event may not be assigned to MVVM. In general, the Close button show a Dialog box to ask the user "save : yes/no/cancel", and this may not be achieved by the MVVM.
You may keep the OnClosing event handler, where you call the Model.Close.CanExecute() and set the boolean result in the event property.
So after the CanExecute() call if true, OR in the OnClosed event, call the Model.Close.Execute()
I haven't done much testing with this but it seems to work. Here's what I came up with:
namespace OrtzIRC.WPF
{
using System;
using System.Windows;
using OrtzIRC.WPF.ViewModels;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private MainViewModel viewModel = new MainViewModel();
private MainWindow window = new MainWindow();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
viewModel.RequestClose += ViewModelRequestClose;
window.DataContext = viewModel;
window.Closing += Window_Closing;
window.Show();
}
private void ViewModelRequestClose(object sender, EventArgs e)
{
viewModel.RequestClose -= ViewModelRequestClose;
window.Close();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
window.Closing -= Window_Closing;
viewModel.RequestClose -= ViewModelRequestClose; //Otherwise Close gets called again
viewModel.CloseCommand.Execute(null);
}
}
}
We use AttachedCommandBehavior for this. You can attach any event to a command on your view model avoiding any code behind.
We use it throughout our solution and have almost zero code behind
http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/
Using MVVM Light Toolkit:
Assuming that there is an Exit command in view model:
ICommand _exitCommand;
public ICommand ExitCommand
{
get
{
if (_exitCommand == null)
_exitCommand = new RelayCommand<object>(call => OnExit());
return _exitCommand;
}
}
void OnExit()
{
var msg = new NotificationMessageAction<object>(this, "ExitApplication", (o) =>{});
Messenger.Default.Send(msg);
}
This is received in the view:
Messenger.Default.Register<NotificationMessageAction<object>>(this, (m) => if (m.Notification == "ExitApplication")
{
Application.Current.Shutdown();
});
On the other hand, I handle Closing event in MainWindow, using the instance of ViewModel:
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (((ViewModel.MainViewModel)DataContext).CancelBeforeClose())
e.Cancel = true;
}
CancelBeforeClose checks the current state of view model and returns true if closing should be stopped.
Hope it helps someone.
You could easily do it with some code behind;
In Main.xaml set:
Closing="Window_Closing"
In Main.cs:
public MainViewModel dataContext { get; set; }
public ICommand CloseApp
{
get { return (ICommand)GetValue(CloseAppProperty); }
set { SetValue(CloseAppProperty, value); }
}
public static readonly DependencyProperty CloseAppProperty =
DependencyProperty.Register("CloseApp", typeof(ICommand), typeof(MainWindow), new PropertyMetadata(null));
In Main.OnLoading:
dataContext = DataContext as MainViewModel;
In Main.Window_Closing:
if (CloseApp != null)
CloseApp .Execute(this);
In MainWindowModel:
public ICommand CloseApp => new CloseApp (this);
And finally:
class CloseApp : ICommand
{
public event EventHandler CanExecuteChanged;
private MainViewModel _viewModel;
public CloseApp (MainViewModel viewModel)
{
_viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Console.WriteLine();
}
}
I took inspiration from this post, and have adapted it into a library I'm building for my own use (but will be public located here: https://github.com/RFBCodeWorks/MvvmControls
While my approach does somewhat expose the View to the ViewModel via the 'sender' and 'eventargs' being passed to the handler, I used this approach just in case its needed for some other sort of handling. For example, if the handler was not the ViewModel, but was instead some service that recorded when windows were opened/closed, then that service may want to know about the sender. If the VM doesn't want to know about the View, then it simply doesn't examine the sender or args.
Here is the relevant code I've come up with, which eliminates the Code-Behind, and allows binding within xaml:
Behaviors:WindowBehaviors.IWindowClosingHandler="{Binding ElementName=ThisWindow, Path=DataContext}"
/// <summary>
/// Interface that can be used to send a signal from the View to the ViewModel that the window is closing
/// </summary>
public interface IWindowClosingHandler
{
/// <summary>
/// Executes when window is closing
/// </summary>
void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e);
/// <summary>
/// Occurs when the window has closed
/// </summary>
void OnWindowClosed(object sender, EventArgs e);
}
/// <summary>
/// Attached Properties for Windows that allow MVVM to react to a window Loading/Activating/Deactivating/Closing
/// </summary>
public static class WindowBehaviors
{
#region < IWindowClosing >
/// <summary>
/// Assigns an <see cref="IWindowClosingHandler"/> handler to a <see cref="Window"/>
/// </summary>
public static readonly DependencyProperty IWindowClosingHandlerProperty =
DependencyProperty.RegisterAttached(nameof(IWindowClosingHandler),
typeof(IWindowClosingHandler),
typeof(WindowBehaviors),
new PropertyMetadata(null, IWindowClosingHandlerPropertyChanged)
);
/// <summary>
/// Gets the assigned <see cref="IWindowLoadingHandler"/> from a <see cref="Window"/>
/// </summary>
public static IWindowClosingHandler GetIWindowClosingHandler(DependencyObject obj) => (IWindowClosingHandler)obj.GetValue(IWindowClosingHandlerProperty);
/// <summary>
/// Assigns an <see cref="IWindowClosingHandler"/> to a <see cref="Window"/>
/// </summary>
public static void SetIWindowClosingHandler(DependencyObject obj, IWindowClosingHandler value)
{
if (obj is not null and not Window) throw new ArgumentException($"{nameof(IWindowClosingHandler)} property can only be bound to a {nameof(Window)}");
obj.SetValue(IWindowClosingHandlerProperty, value);
}
private static void IWindowClosingHandlerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window w = d as Window;
if (w is null) return;
if (e.NewValue != null)
{
w.Closing += W_Closing;
w.Closed += W_Closed;
}
else
{
w.Closing -= W_Closing;
w.Closed -= W_Closed;
}
}
private static void W_Closed(object sender, EventArgs e)
{
GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosed(sender, e);
}
private static void W_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosing(sender, e);
}
#endregion
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
MessageBox.Show("closing");
}

Categories