MVVM multiple Dialogs headache - wpf

I am using caliburn micro. My problem is how to manage dialogs.
The biggest problem is that because when not using window your code doesnt stop and wait.
So i did something like this.
public void ShowDialog(IScreen dialogModel, Action<object> callback = null)
{
ActivateItem(dialogModel);
if (callback != null)
dialogModel.Deactivated += delegate { callback(dialogModel); };
}
This has lots of problem.For example in case i want to show dialog A and then at callback i want to show dialog B under certain cases there comes a problem.I have to write an extra function for DoSomething in order not to duplicate.And i loose all the other local variables..The problem is bigger when more levels are required..
showDialog(A, (cb)=>{
if(...) {
showDialog(B,(cb2)=>{
DoSomething();
});
}
else{
DoSomething();
}
});
Also because i wanted to show one dialog at a time i extended Collection.OneActive . But this had problem too. In deactivate event callback i couldnt close all if i wanted to! Because it keeps in memory the next reference after Deactivated is triggered and even if you clear it it comes again..

How about using a class to track state information as you move between dialogs, rather than nesting closures as shown in your original example?
I think you're on the right track, but it seems like you have two problems:
The amount of nesting that you're doing is not good for code clarity.
You need a better way to capture local variables and state information between dialogs.
To solve the first problem, I'd recommend breaking apart your logic into different methods. Every time a dialog is deactivated, you could have a method to handle the logic that should be executed afterward.
To solve the second problem, you might try creating a class that is responsible for storing the information that you want to pass between dialogs. An instance of this class could be passed in as an argument into each method that is to be executed upon dialog deactivation.
Here's how you could accomplish this:
Conductor Class
public class DialogTestsViewModel : Conductor<object>.Collection.OneActive
{
/// <summary>
/// Shows a dialog and executes its callback if necessary.
/// </summary>
/// <param name="dialogModel">The dialog view model to be shown.</param>
/// <param name="callback">The callback to be executed when dialog is closed.</param>
public void ShowDialog(IScreen dialogModel, Action callback = null)
{
// Show the dialog.
ActivateItem(dialogModel);
// If there is a callback, call it when dialog is closed / deactivated.
if (callback == null) return;
dialogModel.Deactivated += (sender, args) => callback();
}
/// <summary>
/// This method kicks off the dialog chain.
/// </summary>
public void ShowFirstDialog()
{
// Create a new context. This will hold state information
// as it is passed between dialogs.
var context = new TestDialogContext();
// Create the first dialog's view model.
var viewModel = new FirstDialogViewModel();
// Show the first dialog.
ShowDialog(viewModel, () => OnFirstDialogDeactivated(viewModel, context));
}
/// <summary>
/// Logic to be executed when the first dialog is closed.
/// </summary>
/// <param name="viewModel">The first dialog's view model.</param>
/// <param name="context">The state information.</param>
private void OnFirstDialogDeactivated(FirstDialogViewModel viewModel, TestDialogContext context)
{
// Check the view model here and store state information inside the context.
if (viewModel.SomethingIsChecked)
{
context.ShouldShowSecondDialog = true;
}
// Use information in the view model or the context to decide if we should show the next dialog.
// You could also make a decision about which dialog to show next here.
if (context.ShouldShowSecondDialog)
{
var secondDialog = new SecondDialogViewModel();
ShowDialog(secondDialog, () => OnSecondDialogDeactivated(context));
}
}
/// <summary>
/// Logic to be executed when the second dialog is closed.
/// </summary>
/// <param name="context">The state information.</param>
private void OnSecondDialogDeactivated(TestDialogContext context)
{
// Do more stuff.
}
}
Dialog Context Class
Here is where you would store the state information that needed to be passed between dialogs. I've only included one property here as an example, but you could put a lot of info here.
/// <summary>
/// State information to be passed between dialogs.
/// </summary>
public class TestDialogContext
{
public bool ShouldShowSecondDialog { get; set; }
}

Related

WPF Best way of displaying a busy indicator when dynamically creating a page

I have a WPF application that runs as an XBAP in a browser. On a few pages all the controls are dynamically created depending on what the user selects. Because of this it can look like the application is not doing anything until all the controls are loaded. I'd like to have some sort of busy indicator displayed before hand to show the user that the controls are loading, it doesn't have to be animated although would be nice if it did. I've looked into the telerik busy indicator but this doesn't work as it's really for getting data for a single control and doesn't show until the controls are loaded which defeats the purpose.
I was thinking of displaying an overlay, or something similar, first, containing a loading logo, then load the page behind this and hide the overlay when the controls have loaded. I was wondering if this was the best way of going about this or if there's a better way?
Note: I haven't tried this in a XBAP browser app, but it works in WPF Apps without any problems!
I use a DispatcherTimer to show an hourglass when necessary, and abstract this code to a static class.
public static class UiServices
{
/// <summary>
/// A value indicating whether the UI is currently busy
/// </summary>
private static bool IsBusy;
/// <summary>
/// Sets the busystate as busy.
/// </summary>
public static void SetBusyState()
{
SetBusyState(true);
}
/// <summary>
/// Sets the busystate to busy or not busy.
/// </summary>
/// <param name="busy">if set to <c>true</c> the application is now busy.</param>
private static void SetBusyState(bool busy)
{
if (busy != IsBusy)
{
IsBusy = busy;
Mouse.OverrideCursor = busy ? Cursors.Wait : null;
if (IsBusy)
{
new DispatcherTimer(TimeSpan.FromSeconds(0), DispatcherPriority.ApplicationIdle, dispatcherTimer_Tick, Application.Current.Dispatcher);
}
}
}
/// <summary>
/// Handles the Tick event of the dispatcherTimer control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private static void dispatcherTimer_Tick(object sender, EventArgs e)
{
var dispatcherTimer = sender as DispatcherTimer;
if (dispatcherTimer != null)
{
SetBusyState(false);
dispatcherTimer.Stop();
}
}
}
You would use it like this:
void DoSomething()
{
UiServices.SetBusyState();
// Do your thing
}
Hope this helps!

Unsubscribe EventAggregator events in ViewModels

I start using WPF with PRISM and MVVM. One problem I am facing is that I can’t find a good place / best practice to unsubscribe EventAggregator events formerly subscribed in the ViewModel. The following solution - calling Unsubscribe in the destructor – is much too late. It is just running with the next garbage collection.
public class ViewModel : ViewModelBase
{
public ViewModel()
{
var eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
eventAggregator.GetEvent<SeriesSelectionChangedEvent>().Subscribe(OnSeriesSelectionChanged);
}
~ViewModel()
{
var eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
eventAggregator.GetEvent<SeriesSelectionChangedEvent>().Unsubscribe(OnSeriesSelectionChanged);
}
void OnSeriesSelectionChanged(SeriesSelectionChangedEventArgs e)
{
}
}
It's up to you! If your application can notify ViewModel when it is no longer needed, so you should unsubscribe there.
For example, in our project we have IViewDisposeService. If view (or its model) needs deterministic finalization, it registers itself in IViewDisposeService when showing. Then the Core use the same service to notify registered views when they've been removed from regions.
Another way is to use commands. Your model expose command which must be invoked by a view when it is closing. ViewModel may use command handler to unsubscribe.
By the way, if you worry that EventAggregator will hold your ViewModel, it's not a problem, because Prism's EventAggregator use weak references.
Well sometime back, I also faced the same issue. Here's what we did (WPF App).
Create a new base class - DisposableUserControl : UserControl, IDisposable. This will contain the logic of disposing an user control. Code added in the end.
Replace all user control in your application with DisposableUserControl. like < app: DisposableUserControl .... > < / app.DisposableUserControl>
Add an OnDispose method (Virtual) in ViewModelBase which is called in Dispose () method of VM.Each ViewModel of your app should override this OnDispose method in which you'll unsubscribe your events. Something like-
OnDispose() { base.Dispose(); UnsubscribeEvent (abcEventSubscribername); }
Code
/// <summary>
/// Falg used to avoid calling dispose multiple times on same user control
/// </summary>
private bool isDisposed;
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// If disposing equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed. If disposing equals false, the method has been called by the
/// runtime from inside the finalizer and you should not reference
/// other objects, only unmanaged resources can be disposed.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (!this.isDisposed)
{
this.isDisposed = true;
if (disposing)
{
UtilityFunctions.DisposeChildDisposableUserControls(this);
if (this.DataContext != null && this.DataContext is IDisposable)
{
var parent = LogicalTreeHelper.GetParent(this);
if (parent == null || ((parent as FrameworkElement).DataContext != this.DataContext))
{
(this.DataContext as IDisposable).Dispose();
}
BindingOperations.ClearAllBindings(this);
this.DataContext = null;
}
}
}
}
You could have the View notify the ViewModel when it is unloaded (or in case of a Window when it is closed). Then in the Unloaded/Closed handler in the ViewModel you could unsubscribe. That is the way I do it in my application.

Why does my busy indicator work when I click but not for the default button?

Hard question to describe in one line so here's the detail.
I asked a question before about showing a busy indicator in a WPF window whilst doing some long-running action. The original question is here.
I followed some advice from a comment that suggested a kind of "DoEvents" style workaround. I know this isn't ideal but I only have a few hours to add busy indicators to about 50 windows and they all have lots of code on the UI thread that I do not have the time to modify to use background threads (I expect to be up all night sorting it without that additional headache).
So, after I set the "Busy" property in my viewmodels I run the following DoEvents style code:
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
Now, when I click the button that starts the long-running process, the wait indicator appears as I need it to and all is well. However, the button in question is the default button in the window. If I press Enter, the same command binding is used and I can see in debug mode that the code is visited in exactly the same way. However, the busy indicator doesn't show.
Can anyone suggest a workaround that will allow me to get some sleep tonight?
If you accept to use the "standard" Windows waiting cursor, you can simply set the Cursor property of your Window to Cursors.Wait when starting an operation and then to null at the end of the long-running operation (or use the Mouse.OverrideCursor).
I wrap this in an IDisposable class:
/// <summary>
/// Helper to display a wainting cursor for the whole application
/// </summary>
public class WaitingCursor : IDisposable {
private Cursor m_currentCursor;
public WaitingCursor() {
Enable();
}
/// <summary>
/// Sets the cursort to the waiting cursor
/// </summary>
public void Enable() {
m_currentCursor = Mouse.OverrideCursor;
Mouse.OverrideCursor = Cursors.Wait;
}
/// <summary>
/// Restores the cursor to the default one
/// </summary>
public void Disable() {
Mouse.OverrideCursor = m_currentCursor;
}
#region Implementation of IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose() {
Disable();
}
#endregion
}
Then, one can simply write:
using (new WaitingCursor()) {
... // long-running op
}

WPF update binding in a background thread

I have a control that has its data bound to a standard ObservableCollection, and I have a background task that calls a service to get more data.
I want to, then, update my backing data behind my control, while displaying a "please wait" dialog, but when I add the new items to the collection, the UI thread locks up while it re-binds and updates my controls.
Can I get around this so that my animations and stuff keep running on my "please wait" dialog?
Or at least give the "appearance" to the user that its not locked up?
If i understand correctly, you already use a BackgroundWorker to retrieve the data, and that simply assigning this data to the ObservableCollection is locking up the UI.
One way to avoid locking up the UI is to assign the data to the ObservableCollection in smaller chunks by queuing multiple dispatcher methods. Between each method call, UI events can be handled.
the following would add one item on at a time, that's a bit extreme, but it illustrates the concept.
void UpdateItems()
{
//retrievedItems is the data you received from the service
foreach(object item in retrievedItems)
Dispatcher.BeginInvoke(DispatcherPriority.Background, new ParameterizedThreadStart(AddItem), item);
}
void AddItem(object item)
{
observableCollection.Add(item);
}
ObservableCollection will raise CollectionChanged events that will force UI to rebind data, measure, arrange and redraw. This might take a lot of time if you have many updates coming.
It is possible to make user think that UI is alive by splitting the job in small packages. Use Dispatcher from UI thread (any control has reference to it) to schedule collection update actions with 10-100 items (determine number by experiment, these just to support the idea).
Your background code might looks like this:
void WorkInBackground()
{
var results = new List<object>();
//get results...
// feed UI in packages no more than 100 items
while (results.Count > 0)
{
Application.Current.MainWindow.Dispatcher.BeginInvoke(
new Action<List<object>>(FeedUI),
DispatcherPriority.Background,
results.GetRange(0, Math.Min(results.Count, 100)));
results.RemoveRange(0, Math.Min(results.Count, 100));
}
}
void FeedUI(List<object> items)
{
// items.Count must be small enough to keep UI looks alive
foreach (var item in items)
{
MyCollection.Add(item);
}
}
I have a DLL which runs a worker thread and sends events back to the application - worked perfectly on windows forms, switched to WPF and everything stopped working. I've been smashing my head against a brick wall for 4 hours trying to get this to work. But the solution I ended up with, thanks to Microsoft's UI Thread Safe marshalling EnableCollectionSynchronization, gives a really clean implementation to solve this.
This Collection extends ObservableCollection and implements EnableCollectionSynchronization making these objects usable between WPF and also background workers.
Edit: Microsoft's docs say the following, so I'm going to assume that the object context sharing doesn't matter.
The context parameter is an arbitrary object that you can use to information known when you enable collection synchronization. Context can be null.
ThreadSafeCollection.cs
using System.Collections.ObjectModel;
using System.Windows.Data;
namespace NSYourApplication
{
/// <summary>
/// This ObservableCollection is thread safe
/// You can update it from any thread and the changes will be safely
/// marshalled to the UI Thread WPF bindings
/// Thanks Microsoft!
/// </summary>
/// <typeparam name="T">Whatever type of collection you want!</typeparam>
public class ThreadSafeCollection<T> : ObservableCollection<T>
{
private static object __threadsafelock = new object();
public ThreadSafeCollection()
{
BindingOperations.EnableCollectionSynchronization(this, __threadsafelock);
}
}
}
Example WindowViewModel
WindowViewModel.cs
namespace NSYourApplication
{
/// <summary>
/// Example View
/// BaseModelView implements "PropertyChanged" to update WPF automagically
/// </summary>
class TestViewModel : BaseModelView
{
public ThreadSafeCollection<string> StringCollection { get; set; }
/// <summary>
/// background thread implemented elsewhere...
/// but it calls this method eventually ;)
/// Depending on the complexity you might want to implement
/// [MethodImpl(MethodImplOptions.Synchronized)]
/// to Synchronize multiple threads to prevent chase-conditions,deadlocks etc
/// </summary>
public void NonUIThreadMethod()
{
// No dispatchers or invokes required here!
StringCollection.Add("Some Text from a background worker");
}
/// <summary>
/// Somewhere in the UIThread code it'll call this method
/// </summary>
public void UIThreadMethod()
{
StringCollection.Add("This text come from UI Thread");
}
/// <summary>
/// Constructor, creates a thread-safe collection
/// </summary>
public TestViewModel()
{
StringCollection = new ThreadSafeCollection<string>();
}
}
}
Usage in a listbox in a xaml window/control
MainWindow.xaml
<ListBox x:Name="wpfStringCollection" ItemsSource="{Binding StringCollection,Mode=OneWay}">
</ListBox>
use BackgroundWorker to accomplish this task. update the obsrvablecollection in the DoWork method
Use this:
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(UpdateData), value);
private void UpdateData(int value)
{
BindingSourceProperty = value;
}

Pattern for unsaved changes

I'm developing a winforms app with lots of different forms and user controls. Is there a recommended pattern that I could implement that notifies the user that there are unsaved changes on the current form/control when the form/control is exiting and also when the app is closing?
Memento is a way to encapsulate undoable changes.
You can then keep a log of your uncommitted memento instances.
But that's usually way to complex.
State is usually best.
Your application has two "change" states: Saved All Changes, Unsaved Changes.
Each State has a transition rule based on "change" and "save" methods.
The Saved All Changes implementation of "save" does nothing.
The Unsaved Changes implementation of "save" sets the state to "Saved All Changes".
The Saved All Changes implementation "change" sets the state to Unsaved Changes.
The Unsaved Changes implementation of "change" does nothing.
I'm using LLBL Gen pro for the ORM so that has some good entity tracking built into the objects.
I've kind of rolled my own that seems to work pretty well.
I created a new interface that my base User Controls and base Forms implement:
public interface IClosingNotification
{
/// <summary>
/// True if there is a dirty entity (or a dirty entity in the collection) present
/// </summary>
bool DirtyEntityPresent { get; }
/// <summary>
/// Register an entity to be watched for changes
/// </summary>
/// <param name="entity"></param>
void RegisterForClosingNotification(IEntity entity);
/// <summary>
/// Register a collection to be watched for changes
/// </summary>
/// <param name="collection"></param>
void RegisterForClosingNotification(IEntityCollection collection);
/// <summary>
/// Returns true if the form should close without any notification
/// </summary>
/// <returns></returns>
bool ShouldClose();
}
In my base control/form I have a collection of entities that I watch on each form, and I have a CloseForm() method in these classes that I use when a form is closing.
In my forms, whenever I create an object I can then register it for closing notification using:
RegisterForClosingNotification(MyCustomer);
It works well in our scenario.

Resources