Winforms recursive file scan blocks UI - winforms

I have a program that searches the given directory and adds all the files to a list view. My problem is that the ui thread gets stuck while the search is busy. I have tried using tasks but can’t get it to work in async. The list view must be updated after each file has been found.
I have done a lot of reading about the TPL and how to use it but can’t get it to work in this case. I got it to work where the processing of data is in one method that create a task to process it. Can any one tel me what is wrong in the code below and how to fix it?
Here is my code:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
WalkDirectory(new DirectoryInfo(drive));
});
}
public void testTaskUpdateLabel(string labelTeks)
{
Task taskUpdateLabel = new Task(() =>
{
label4.Text = labelTeks;
});
taskUpdateLabel.Start(uiScheduler);
}
public void testTaskUpdateLabel(string labelTeks)
{
Task taskUpdateLabel = new Task(() =>
{
label4.Text = labelTeks;
});
taskUpdateLabel.Start(uiScheduler);
}
public bool WalkDirectory(DirectoryInfo directory)
{
if (directory == null)
{
throw new ArgumentNullException("directory");
}
return this.WalkDirectories(directory);
}
private bool WalkDirectories(DirectoryInfo directory)
{
bool continueScan = true;
continueScan = WalkFilesInDirectory(directory);
if (continueScan)
{
DirectoryInfo[] subDirectories = directory.GetDirectories();
foreach (DirectoryInfo subDirectory in subDirectories)
{
try
{
if ((subDirectory.Attributes & FileAttributes.ReparsePoint) != 0)
{
continue;
}
if (!(continueScan = WalkDirectory(subDirectory)))
{
break;
}
}
catch (UnauthorizedAccessException)
{
continue;
}
}
}
if (continueScan)
{
testTaskUpdateLabel(directory.FullName);
}
return continueScan;
}
private bool WalkFilesInDirectory(DirectoryInfo directory)
{
bool continueScan = true;
// Break up the search pattern in separate patterns
string[] searchPatterns = _searchPattern.Split(';');
// Try to find files for each search pattern
foreach (string searchPattern in searchPatterns)
{
if (!continueScan)
{
break;
}
// Scan all files in the current path
foreach (FileInfo file in directory.GetFiles(searchPattern))
{
try
{
testTaskUpdate(file.FullName);
}
catch (UnauthorizedAccessException)
{
continue;
}
}
}
return continueScan;
}

If you use a BackgroundWorker class, the UI will work and progress can be updated in the ProgressChanged event handler.
MSDN Reference

Can any one tel me what is wrong in the code below and how to fix it?
The problem is here
public void testTaskUpdateLabel(string labelTeks)
{
Task taskUpdateLabel = new Task(() =>
{
label4.Text = labelTeks;
});
taskUpdateLabel.Start(uiScheduler);
}
You should not use TPL to update the UI. TPL tasks are for doing non UI work and UI should only be updated on the UI thread. You already moved the work on a thread pool thread (via Task.Run), so the only problem you need to solve is how to update the UI from inside the worker. There are many ways to do that - using Control.Invoke/BeginInvoke, SynchronizationContext etc, but the preferred approach for TPL is to pass and use IProgress<T> interface. Don't be fooled by the name - the interface is an abstraction of a callback with some data. There is a standard BCL provided implementation - Progress<T> class with the following behavior, according to the documentation
Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed.
i.e. perfectly fits in UI update scenarios.
With all that being said, here is how you can apply that to your code. We'll use IProgress<string> and will call Report method and pass the full name for each file/directory we find - a direct replacement of your testTaskUpdateLabel calls.
private void button1_Click(object sender, EventArgs e)
{
var progress = new Progress<string>(text => label4.Text = text);
Task.Run(() =>
{
WalkDirectory(new DirectoryInfo(drive), progress);
});
}
public bool WalkDirectory(DirectoryInfo directory, IProgress<string> progress)
{
if (directory == null) throw new ArgumentNullException("directory");
if (progress == null) throw new ArgumentNullException("progress");
return WalkDirectories(directory, progress);
}
bool WalkDirectories(DirectoryInfo directory, IProgress<string> progress)
{
// ...
if (!(continueScan = WalkDirectories(subDirectory, progress)))
// ...
if (continueScan)
progress.Report(directory.FullName);
// ...
}
bool WalkFilesInDirectory(DirectoryInfo directory, IProgress<string> progress)
{
// ...
try
{
progress.Report(file.FullName);
}
// ...
}

I got it to work by making the walkDirectory, walkDirectories and WalkFiles methods async. Thus using the await keyword before I call the testUpdate and testUpdateLabel methods. This way the listview is updated with the search results while the search is running without blocking the UI thread. I.E. the user can cancel the search when the file he was searching for has been found.

Related

async Task Main() in WinForms application having async initialization

We're having a winforms application that uses an async initialization process. Simplified you can say that the application will run the following steps:
Init - this runs async
Show MainForm
Application.Run()
The currently existing and working code looks like this:
[STAThread]
private static void Main()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
var task = StartUp();
HandleException(task);
Application.Run();
}
private static async Task StartUp()
{
await InitAsync();
var frm = new Form();
frm.Closed += (_, __) => Application.ExitThread();
frm.Show();
}
private static async Task InitAsync()
{
// the real content doesn't matter
await Task.Delay(1000);
}
private static async void HandleException(Task task)
{
try
{
await Task.Yield();
await task;
}
catch (Exception e)
{
Console.WriteLine(e);
Application.ExitThread();
}
}
The background how this is working is described very detailed by Mark Sowul here.
Since C# 7.1 we're able to use async Task in main method. We tried it in a straight forward way:
[STAThread]
private static async Task Main()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
try
{
await StartUp();
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
Application.ExitThread();
}
}
private static async Task StartUp()
{
await InitAsync();
var frm = new Form();
frm.Closed += (_, __) => Application.ExitThread();
frm.Show();
}
private static async Task InitAsync()
{
// the real content doesn't matter
await Task.Delay(1000);
}
But that doesn't work. The reason is clear. All the code after the first await will be forwarded to the message loop. But the message loop hasn't startet yet because the code that starts it (Application.Run()) is located after the first await.
Removing the synchronization context will fix the problem but causes to run the code after await in a different thread.
Reordering the code to call Application.Run() before the first await will not work because it is a blocking call.
We try to use the new feature of having an async Task Main() that allows us to remove the HandleException-solution that is hard to understand. But we don't know how.
Do you have any suggestions?
You don't need async Main. Here is how it can possibly be done:
[STAThread]
static void Main()
{
void threadExceptionHandler(object s, System.Threading.ThreadExceptionEventArgs e)
{
Console.WriteLine(e);
Application.ExitThread();
}
async void startupHandler(object s, EventArgs e)
{
// WindowsFormsSynchronizationContext is already set here
Application.Idle -= startupHandler;
try
{
await StartUp();
}
catch (Exception)
{
// handle if desired, otherwise threadExceptionHandler will handle it
throw;
}
};
Application.ThreadException += threadExceptionHandler;
Application.Idle += startupHandler;
try
{
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
Application.Idle -= startupHandler;
Application.ThreadException -= threadExceptionHandler;
}
}
Note, if you don't register threadExceptionHandler event handler and StartUp throws (or anything else on the message loop throws, for the matter), it will still work. The exception will be caught inside the try/catch which wraps Application.Run. It will just be a TargetInvocationException exception with the original exception available via its InnerException property.
Updated to address the comments:
But for me it looks very strange to register an EventHandler to the
idle event so startup the whole application. It's totally clear how
that works but still strange. In that case I prefer the
HandleException solution that I already have.
I guess it's a matter of taste. I don't know why WinForms API designers didn't provide something like WPF's Application.Startup. However, in the lack of a dedicated event for this on WinForm's Application class, deferring specific initialization code upon the first Idle event is IMO an elegant solution, and it's widely used here on SO.
I particularly don't like the explicit manual provisioning of WindowsFormsSynchronizationContext before Application.Run has started, but if you want an alternative solution, here you go:
[STAThread]
static void Main()
{
async void startupHandler(object s)
{
try
{
await StartUp();
}
catch (Exception ex)
{
// handle here if desired,
// otherwise it be asynchronously propogated to
// the try/catch wrapping Application.Run
throw;
}
};
// don't dispatch exceptions to Application.ThreadException
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
using (var ctx = new WindowsFormsSynchronizationContext())
{
System.Threading.SynchronizationContext.SetSynchronizationContext(ctx);
try
{
ctx.Post(startupHandler, null);
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
System.Threading.SynchronizationContext.SetSynchronizationContext(null);
}
}
}
IMO, either approach is more clean than the one used in your question. On a side note, you should be using ApplicationContext to handle the form closure. You can pass an instance of ApplicationContext to Application.Run.
The only point that I'm
missing is your hint that the synchronization context is already set.
Yes it is - but why?
It is indeed set as a part of Application.Run, if not already present on the current thread. If you like to learn more details, you could investigate it in .NET Reference Source.

Wpf child form, OnClosing event and await

I have a child form launched form a parent form with:
ConfigForm cfg = new ConfigForm();
cfg.ShowDialog();
This child form is used to configure some application parameters.
I want to check if there are some changes not saved, and if so, warn the user.
So my On OnClosing event is declared this way:
private async void ChildFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
// Here i call a function that compare the current config with the saved config
bool isUptated = CheckUnsavedChanges();
// If updated is false, it means that there are unsaved changes...
if (!isUpdated)
{
e.Cancel = true;
// At this point i create a MessageDialog (Mahapps) to warn the user about unsaved changes...
MessageDialogStyle style = MessageDialogStyle.AffirmativeAndNegative;
var metroDialogSettings = new MetroDialogSettings()
{
AffirmativeButtonText = "Close",
NegativeButtonText = "Cancel"
};
var result = await this.ShowMessageAsync("Config", "There are unsaved changes, do you want to exit?", style, metroDialogSettings);
// If we press Close, we want to close child form and go back to parent...
if (result == MessageDialogResult.Affirmative)
{
e.Cancel = false;
}
}
}
My logic says that if i declare e.cancel to false it will continue closing the form, but it doesn't happen, the child form remains open.
My guess is that the async call is doing something i don't understand, because if i declare ChildFormClosing in this way:
private async void ChildFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
bool isUptated = CheckUnsavedChanges();
e.Cancel = true;
if (!isUpdated)
{
MessageDialogStyle style = MessageDialogStyle.AffirmativeAndNegative;
var metroDialogSettings = new MetroDialogSettings()
{
AffirmativeButtonText = "Close",
NegativeButtonText = "Cancel"
};
var result = await this.ShowMessageAsync("Config", "There are unsaved changes, do you want to exit?", style, metroDialogSettings);
if (result == MessageDialogResult.Affirmative)
{
e.Cancel = false;
}
}
else
{
e.Cancel = false;
}
}
The final else e.Cancel = false works and the child form is closed...
Any clue?
Thanks!
Since this method is an event handler for a window, it will be called on the UI thread already, so there is no need to show the message box asynchronously.
As for the strange behavior that you are seeing, this is related to the await in the event handler. When you await a method call, what is actually happening is that everything up until the await is executed as normal, but once the await statement is reach control returns to the caller. Once the method that is awaited upon returns, then the rest of the original method executes.
The code that fires the OnClosing event is probably not designed with asynchronous event handlers in mind, so it assumes that if an event handler returns, it has finished whatever work it needs to do. Since your event handler sets CancelEventArgs.Cancel to true before it awaits on a method call, the caller to your event handler sees that it is set to true, so it doesn't close the form.
This is why showing the message box synchronously works: the entire method is executed before control returns to the caller, so CancelEventArgs.Cancel is always set to its expected value.
Raymond Chen recently posted two articles about async that might be interesting reading: Crash course in async and await and The perils of async void. The second article describes why async event handlers tend to not work how you expect them to.
The main problem with using async/await in OnClosing is, as Andy explained, that as soon as the await statement is executed, control is returned to the caller and the closing process continues.
We can work around this by making another round trip back to OnClosing after awaiting, this time with a flag to indicate whether to actually close or not, but the problem is that calling Close while the Window is already closing, is not allowed and throws an exception.
The way to solve this issue is to simply defer the execution of Close to after the current closing process, at which point it becomes valid again to close the window.
I wanted to do something like this to allow the user to handle async closing logic in the ViewModel.
I don't know if there are other edge cases that I haven't covered, but this code so far works for me:
CoreWindow.cs
public class CoreWindow : Window
{
private bool _isClosing;
private bool _canClose;
private BaseDialogViewModel ViewModel => (BaseDialogViewModel) DataContext;
public CoreWindow()
{
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is BaseDialogViewModel oldDataContext)
{
oldDataContext.Closed -= OnViewModelClosed;
}
if (e.NewValue is BaseDialogViewModel newDataContext)
{
newDataContext.Closed += OnViewModelClosed;
}
}
private void OnViewModelClosed(object sender, EventArgs e)
{
if (!_isClosing)
{
_isClosing = true;
Close();
}
}
protected override async void OnClosing(CancelEventArgs e)
{
if (ViewModel == null)
{
base.OnClosing(e);
return;
}
if (!_canClose)
{
// Immediately cancel closing, because the decision
// to cancel is made in the ViewModel and not here
e.Cancel = true;
base.OnClosing(e);
try
{
// Ask ViewModel if allowed to close
bool closed = await ViewModel.OnClosing();
if (closed)
{
// Set _canClose to true, so that when we call Close again
// and return to this method, we proceed to close as usual
_canClose = true;
// Close cannot be called while Window is in closing state, so use
// InvokeAsync to defer execution of Close after OnClosing returns
_ = Dispatcher.InvokeAsync(Close, DispatcherPriority.Normal);
}
}
catch (Exception ex)
{
// TODO: Log exception
}
finally
{
_isClosing = false;
}
}
base.OnClosing(e);
}
}
BaseDialogViewModel.cs
public class BaseDialogViewModel : BaseViewModel
{
public event EventHandler Closed;
public bool? DialogResult { get; set; }
public void Close()
{
Closed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Override to add custom logic while dialog is closing
/// </summary>
/// <returns>True if should close dialog, otherwise false</returns>
public virtual Task<bool> OnClosing()
{
return Task.FromResult(true);
}
}
BaseViewModel just contains some validation and property notification stuff, not really relevant to show here.
Big thanks to Rick Strahl for the Dispatcher solution!
UPDATE:
It's possible to use await Task.Yield(); instead of Dispatcher.InvokeAsync.

asynchronous UI update from ViewModel in WPF

I am having a problem with getting data from db and showing in UI asynchronously.
I am using MVVM light, when I click the button, action is triggered in ViewModel:
private void SearchQuery(string query)
{
_redisModel.GetFriendsListAsync(query);
}
At some point GetFriendsListCompleted is called by background thread notifing viewmodel that job is done.
At this point I need to update ListBox ItemSource. But when I try to update is I get
“The calling thread cannot access this object because a different thread owns it”
I have tried Dispatcher.CurrentDispatcher.Invoke(),App.Current.Dispatcher.Invoke() and different magic, but it still doesn’t work.
I tried to give UI dispatcher to ViewModel and then call it from there - didn't work.
private string filterText = string.Empty;
public string FilterText
{
get { return filterText; }
set
{
filterText = value;
this.RaisePropertyChanged(() => this.FilterText);
this.FriendsList.View.Refresh(); // Here where exception is happening.
}
}
I tried to change this line to
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() =>this.FriendsList.View.Refresh())); - still the same.
I am using Telerik ListBox to display items. FriendList is CollectionViewSource(http://www.telerik.com/help/wpf/radlistbox-overview.html). It works when I use Telerik example from WPF Control Examples. Problems start to occur when I use my async methods.
Type of view is System.ComponentModel.ICollectionView it is used for Filtering and Grouping.
I have also tried to just assign ObservableCollection to Items property of the ListBox and it doesn't work either.
A bit more details on how _redisModel.GetFriendsListAsync works:
In the end(after all chain of calls) it ends up here:
public GetAsyncResult(Func<T> workToBeDone, Action<IAsyncResult> cbMethod, Object state)
{
_cbMethod = cbMethod;
_state = state;
QueueWorkOnThreadPool(workToBeDone);
}
ThreadPool.QueueUserWorkItem(state =>
{
try
{
_result = workToBeDone();
}
catch (Exception ex)
{
_exception = ex;
}
finally
{
UpdateStatusToComplete(); //1 and 2
NotifyCallbackWhenAvailable(); //3 callback invocation
}
});
In viewmodel I have method:
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => this.FriendsList.View.Refresh()));
}
}
Can anybody please help me with this ?
Thank you
You are creating CollectionViewSource in one thread and refreshing that in another thread (dispatcher thread). Update your GetFriendsListCompleted to
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => {
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
this.FriendsList.View.Refresh();
}));
}
}
}
You haven't shown any of the code that's actually running on the background thread on completion but I'm guessing that in it you're creating a collection object that you're then trying to assign to your CollectionView. When the CV tries to update (on the UI thread) from your Refresh call it would then try to use the collection that's owned by the other thread.
If you include the relevant code it would be easier to say for sure.

Binding ComboBox and ObservableCollection<KeyValue> in wpf

in My OpenViewModel i collect data:
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get { return availableData; }
set
{
if (value != availableData)
{
availableData= value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
method for collecting data:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
try
{
client = webservice.GetClient();
AvailableDatas = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return AvailableDatas;
}
How to call the method CollectData in wpf and fill my COmboBox?
thx
You might simply call the method the first time the AvailableDatas property is accessed (e.g. from a binding in XAML):
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get
{
if (availableData == null)
{
availableData = CollectData();
}
return availableData;
}
set
{
if (value != availableData)
{
availableData = value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
Then you should change the CollectData method in a way that is does not also set the property:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
ObservableCollection<KeyValue> data = null;
try
{
client = webservice.GetClient();
data = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return data;
}
You could override the OnActivated() event assuming you are using an IScreen implementation and load data in there, or just do it in the constructor or a custom Initialise method if you want to roll your own (or in the property accessor as someone has already said).
You can also use coroutines if you want some visual context for the user and a better tie in with CM actions
There is a nice simple implementation of a Loader class here which helps provide visual context to the user:
https://caliburnmicro.codeplex.com/wikipage?title=IResult%20and%20Coroutines&referringTitle=Documentation
This searches the visual tree for a BusyIndicator control and activates it whilst the content is loading e.g. ...
public class SomeViewModel : Screen
{
protected override void OnActivate()
{
RefreshData();
}
public void RefreshData()
{
Coroutine.BeginExecute(LoadData(), new ActionExecutionContext() { Target = this });
}
public IEnumerable<IResult> LoadData()
{
yield return Loader.Show("Loading Data...");
yield return new LoadSomeDataRoutine(client.GetDatas);
yield return Loader.Hide();
}
}
The reason to have a RefreshData method is that this also allows you to bind CM actions and allows the coroutine can grab more contextual information.
Obviously you have less need to worry about the async->sync benefits this gives in Silverlight because you are using WPF (but it still applies to async web service calls), however it still has many benefits and it also helps you to write reusable routines which become part of your application framework (e.g. some level of error handling/logging encapsulated in the IResult implementation etc)
You also mentioned filling the combobox... all you would need to do in CM is place a combobox on your control, and set it's Name property to the name of the property on your VM:
public class SomeViewModel : Screen
{
public ObservableCollection<MyObject> MyProperty { //blah blah... }
}
<UserControl .... blah>
<ComboBox x:Name="MyProperty" />
</UserControl>
This will fill the combobox with the items. You will still need to set the binding for SelectedItem/SelectedValue
I assume you know this already though - if not CM has some decent documentation:
https://caliburnmicro.codeplex.com/wikipage?title=Basic%20Configuration%2c%20Actions%20and%20Conventions&referringTitle=Documentation

How to receive DialogResult using mvvm-light Messenger

I'm trying to use the mvvm-light messenger capability to open a custom confirm password dialog in my view, triggered by a command in my viewmodel.
I think I understand the usage of Messenger.Default.Register and Messenger.Default.Send.
But how do I get the dialog results back in my viewmodel?
To me the sending seems to be a one way street...
Could someone help a beginner with a small C#/WPF code sample?
Thanks for any help
IMHO it is better to use the NotificationMessageAction<T> as it is cut out for this task.
On the sender side:
var msg = new NotificationMessageAction<MessageBoxResult>(this, "GetPassword", (r) =>
{
if (r == MessageBoxResult.OK)
{
// do stuff
}
});
Messenger.Default.Send(msg);
And on the receiver side:
Messenger.Default.Register<NotificationMessageAction<MessageBoxResult>>(this, (m) =>
{
if (m.Notification == "GetPassword") {
var dlg = new PasswordDialog();
var result = dlg.ShowDialog();
m.Execute(result);
}
});
I believe that this approach is cleaner as it does not create an unnecessary dependency from the View to the ViewModel (although this way round is not so bad). For better readability consider sub-classing the NodificationMessageAction<MessageResult>. I.e.
public class ShowPasswordMessage : NotificationMessageAction<MessageBoxResult>
{
public ShowPasswordMessage(object Sender, Action<MessageBoxResult> callback)
: base(sender, "GetPassword", callback)
{
}
}
Then the sender
var msg = new ShowPasswordMessage(this, (r) =>
{
if (r == MessageBoxResult.OK)
{
// do stuff
}
});
Messenger.Default.Send(msg);
and receiver side
Messenger.Default.Register<ShowPasswordMessage>(this, (m) =>
{
var dlg = new PasswordDialog();
var result = dlg.ShowDialog();
m.Execute(result);
});
becomes a lot clearer.
And verry important unregister the recipient as else you might create a memory leak.
In Register method you can show a dialog and pass the YourViewModel reference.
Messenger.Default.Register<YourViewModel>(this, "showDialog", viewModel=>
{
var dlg = new Dialog();
viewModel.Result = dlg.ShowDialog();
});
somewhere in your code you can throw Send() message with a reference to YourViewModel like this:
Messenger.Default.Send(viewModel, "showDialog");
In order to achieve the above using DialogMessage as the title suggests,
one may use the following:
sender side:
void SendMessage(String msgText)
{
DialogMessage messege = new DialogMessage(msgText, res =>
{
callback(res);
})
//set more dialog properties using the Initializer
{ Button = MessageBoxButton.OKCancel, Caption = "" };
Messenger.Default.Send(messege, "mb1");
}
public void callback(MessageBoxResult res)
{
if (res == MessageBoxResult.OK)
{ /*do something*/ }
}
receiver side:
void Rec()
{
Messenger.Default.Register<DialogMessage>(
this, "mb1", msg => ShowDialog(msg));
Unloaded += Unreg;
}
void ShowDialog(DialogMessage msg)
{
var result = MessageBox.Show(
msg.Content,
msg.Caption,
msg.Button);
msg.Callback(result);
}
note the explicit call to the Callback method in the last line of the receiver.
msg.Callback(result);

Resources