-
Notifications
You must be signed in to change notification settings - Fork 252
Initial version of the docs #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
68837a4
8e4176f
23f514f
1fb5af0
767b938
138e489
cb39fb5
276729b
db4945c
d7d91b9
8546839
06c1c69
3ed5e87
ec8f88c
adc9afc
0a72a80
69b4c5b
f1d3cc3
dcd922b
b8b1cb4
fcb0ace
2aba1cb
3dc9451
780b4cc
687cb07
1048027
4027d23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| --- | ||
| title: AsyncRelayCommand | ||
| author: Sergio0694 | ||
| description: An asynchronous command whose sole purpose is to relay its functionality to other objects by invoking delegates | ||
| keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, mvvm, componentmodel, property changed, notification, binding, command, delegate, net core, net standard | ||
| dev_langs: | ||
| - csharp | ||
| --- | ||
|
|
||
| # AsyncRelayCommand and AsyncRelayCommand<T> | ||
|
|
||
| The [`AsyncRelayCommand`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.input.AsyncRelayCommand) and [`AsyncRelayCommand<T>`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.input.AsyncRelayCommand-1) are `ICommand` implementations that extend the functionalities offered by `RelayCommand`, with support for asynchronous operations. | ||
|
|
||
| ## How they work | ||
|
|
||
| `AsyncRelayCommand` and `AsyncRelayCommand<T>` have the following main features: | ||
|
|
||
| - They extend the functionalities of the non-asynchronous commands included in the library, with support for `Task`-returning delegates. | ||
| - They expose an `ExecutionTask` property that can be used to monitor the progress of a pending operation, and an `IsRunning` that can be used to check when an operation completes. This is particularly useful to bind a command to UI elements such as loading indicators. | ||
| - They implement the `IAsyncRelayCommand` and `IAsyncRelayCommand<T>` interfaces, which means that viewmodel can easily expose commands using these to reduce the tight coupling between types. For instance, this makes it easier to replace a command with a custom implementation exposing the same public API surface, if needed. | ||
|
|
||
| ## Working with asynchronous commands | ||
|
|
||
| Let's imagine a scenario similar to the one described in the `RelayCommand` sample, but a command executing an asynchronous operation: | ||
|
|
||
| ```csharp | ||
| public class MyViewModel : ObservableObject | ||
| { | ||
| public MyViewModel() | ||
| { | ||
| DownloadTextCommand = new AsyncRelayCommand(DownloadText); | ||
| } | ||
|
|
||
| public IAsyncRelayCommand DownloadTextCommand { get; } | ||
|
|
||
| private Task<string> DownloadText() | ||
| { | ||
| return WebService.LoadMyTextAsync(); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| With the related UI code: | ||
|
|
||
| ```xml | ||
| <Page | ||
| x:Class="MyApp.Views.MyPage" | ||
| xmlns:viewModels="using:MyApp.ViewModels"> | ||
| <Page.DataContext> | ||
| <viewModels:MyViewModel x:Name="ViewModel"/> | ||
| </Page.DataContext> | ||
|
|
||
| <StackPanel Spacing="8"> | ||
| <TextBlock> | ||
| <Run Text="Task status:"/> | ||
| <Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask.Status, Mode=OneWay}"/> | ||
| <LineBreak/> | ||
| <Run Text="Result:"/> | ||
| <Run | ||
| xmlns:ex="using:Microsoft.Toolkit.Extensions" | ||
| Text="{x:Bind ex:TaskExtensions.GetResultOrDefault(ViewModel.DownloadTextCommand.ExecutionTask), Mode=OneWay}"/> | ||
| </TextBlock> | ||
| <Button | ||
| Content="Click me!" | ||
| Command="{x:Bind ViewModel.DownloadTextCommand}"/> | ||
| <ProgressRing IsActive="{x:Bind ViewModel.DownloadTextCommand.IsRunning, Mode=OneWay}"/> | ||
| </StackPanel> | ||
| </Page> | ||
| ``` | ||
|
|
||
| Upon clicking the `Button`, the command is invoked, and the `ExecutionTask` updated. When the operation completes, the property raises a notification which is reflected in the UI. In this case, both the task status and the current result of the task are displayed. Note that to show the result of the task, it is necessary to use the `TaskExtensions.GetResultOrDefault` method - this provides access to the result of a task that has not yet completed without blocking the thread (and possibly causing a deadlock). | ||
|
|
||
| ## Sample Code | ||
|
|
||
| There are more examples in the [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.Shared/Mvvm). | ||
|
|
||
| ## Requirements | ||
|
|
||
| | Device family | Universal, 10.0.16299.0 or higher | | ||
| | --- | --- | | ||
| | Namespace | Microsoft.Toolkit.Mvvm | | ||
| | NuGet package | [Microsoft.Toolkit.Mvvm](https://www.nuget.org/packages/Microsoft.Toolkit.Mvvm/) | | ||
|
|
||
| ## API | ||
|
|
||
| * [AsyncRelayCommand source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs) | ||
| * [AsyncRelayCommand<T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| --- | ||
| title: Introduction to the MVVM package | ||
| author: Sergio0694 | ||
| description: An overview of how to get started with MVVM package and to the APIs it contains | ||
| keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, get started, visual studio, MVVM, net core, net standard | ||
| dev_langs: | ||
| - csharp | ||
| - vb | ||
| --- | ||
Sergio0694 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Introduction to the MVVM package | ||
|
|
||
| The `Microsoft.Toolkit.Mvvm` package is a modern, fast, and modular MVVM library. It is part of the Windows Community Toolkit and is built around the following principles: | ||
|
|
||
| - **Platform and Runtime Independent** - **.NET Standard 2.0** 🚀 (UI Framework Agnostic) | ||
| - **Simple to pick-up and use** - No strict requirements on Application structure or coding-paradigms (outside of 'MVVM'ness), i.e., flexible usage. | ||
| - **À la carte** - Freedom to choose which components to use. | ||
| - **Reference Implementation** - Lean and performant, providing implementations for interfaces that are included in the Base Class Library, but lack concrete types to use them directly. | ||
|
|
||
| This package targets .NET Standard so it can be used on any app platform: UWP, WinForms, WPF, Xamarin, Uno, and more; and on any runtime: .NET Native, .NET Core, .NET Framework, or Mono. It runs on all of them. The API surface is identical in all cases, making it perfect for building shared libraries. | ||
|
|
||
| To install the package from within Visual Studio: | ||
|
|
||
| 1. In Solution Explorer, right-click on the project and select **Manage NuGet Packages**. Search for **Microsoft.Toolkit.Mvvm** and install it. | ||
|
|
||
|  | ||
|
|
||
| 2. Add a using or Imports directive to use the new APIs: | ||
|
|
||
| ```c# | ||
| using Microsoft.Toolkit.Mvvm; | ||
| ``` | ||
Sergio0694 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ```vb | ||
| Imports Microsoft.Toolkit.Mvvm | ||
| ``` | ||
|
|
||
| 3. Code samples are available in the other docs pages for the MVVM package, and in the [unit tests](https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/UnitTests/UnitTests.Shared/Mvvm) for the project. | ||
|
|
||
| ## When should I use this package? | ||
|
|
||
| Use this package for access to a collection of standard, self-contained, lightweight types that provide a starting implementation for building modern apps using the MVVM pattern. These types alone are usually enough for many users to build apps without needing additional external references. | ||
|
|
||
| The included types are: | ||
|
|
||
| - **Microsoft.Toolkit.Mvvm.ComponentModel** | ||
| - `ObservableObject` | ||
| - `ObservableRecipient` | ||
| - **Microsoft.Toolkit.Mvvm.DependencyInjection** | ||
| - `Ioc` | ||
| - **Microsoft.Toolkit.Mvvm.Input** | ||
| - `RelayCommand` | ||
| - `RelayCommand<T>` | ||
| - `AsyncRelayCommand` | ||
| - `AsyncRelayCommand<T>` | ||
| - `IRelayCommand` | ||
| - `IRelayCommand<in T>` | ||
| - `IAsyncRelayCommand` | ||
| - `IAsyncRelayCommand<in T>` | ||
| - **Microsoft.Toolkit.Mvvm.Messaging** | ||
| - `Messenger` | ||
| - `IMessenger` | ||
| - **Microsoft.Toolkit.Mvvm.Messaging.Messages** | ||
| - `PropertyChangedMessage<T>` | ||
| - `RequestMessage<T>` | ||
| - `AsyncRequestMessage<T>` | ||
| - `CollectionRequestMessage<T>` | ||
| - `AsyncCollectionRequestMessage<T>` | ||
| - `ValueChangedMessage<T>` | ||
|
|
||
| This package aims to offer as much flexibility as possible, so developers are free to choose which components to use. All types are loosely-coupled, so that it's only necessary to include what you use. There is no requirement to go "all-in" with a specific series of all-encompassing APIs, nor is there a set of mandatory patterns that need to be followed when building apps using these helpers. Combine these building blocks in a way that best fits your needs. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| --- | ||
| title: Ioc | ||
| author: Sergio0694 | ||
| description: A type that facilitates the use of the IServiceProvider type | ||
| keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, mvvm, service, dependency injection, net core, net standard | ||
| dev_langs: | ||
| - csharp | ||
| --- | ||
|
|
||
| # Ioc ([Inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control)) | ||
|
|
||
| The [`Ioc`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.DependencyInjection.Ioc) class is a type that facilitates the use of the `IServiceProvider` type. It's powered by the `Microsoft.Extensions.DependencyInjection` package, which provides a fully featured and powerful DI set of APIs, and acts as an easy to setup and use `IServiceProvider`. | ||
|
|
||
| ## Configure and resolve services | ||
|
|
||
| The main entry point is the `ConfigureServices` method, which can be used like so: | ||
|
|
||
| ```csharp | ||
| // Register the services at startup | ||
| Ioc.Default.ConfigureServices(services => | ||
| { | ||
| services.AddSingleton<IFilesService, FilesService>(); | ||
| services.AddSingleton<ISettingsService, SettingsService>(); | ||
| // Other services here... | ||
| }); | ||
|
|
||
| // Retrieve a service instance when needed | ||
| IFilesService fileService = Ioc.Default.GetService<IFilesService>(); | ||
| ``` | ||
|
|
||
| The `Ioc.Default` property offers a thread-safe `IServiceProvider` instance that can be used anywhere in the application to resolve services. The `ConfigureService` method handles the initialization of that service. It is also possible to create different `Ioc` instances and to initialize each with different services. | ||
|
|
||
| ## Constructor injection | ||
|
|
||
| One powerful feature that is available is "constructor injection", which means that the DI service provider is able to automatically resolve indirect dependencies between registered services when creating instances of the type being requested. Consider the following service: | ||
|
|
||
| ```csharp | ||
| public class ConsoleLogger : ILogger | ||
| { | ||
| private readonly IFileService FileService; | ||
| private readonly IConsoleService ConsoleService; | ||
|
|
||
| public FileLogger( | ||
| IFileService fileService, | ||
| IConsoleService consoleService) | ||
| { | ||
| FileService = fileService; | ||
| ConsoleService = consoleService; | ||
| } | ||
|
|
||
| // Methods for the IFileLogger interface here... | ||
| } | ||
| ``` | ||
|
|
||
| Here we have a `ConsoleLogger` implementing the `ILogger` interface, and requiring `IFileService` and `IConsoleService` instances. Constructor injection means the DI service provider will "automagically" gather all the necessary services, like so: | ||
|
|
||
| ```csharp | ||
| // Register the services at startup | ||
| Ioc.Default.ConfigureServices(services => | ||
| { | ||
| services.AddSingleton<IFileService, FileService>(); | ||
| services.AddSingleton<IConsoleService, ConsoleService>(); | ||
| services.AddSingleton<ILogger, ConsoleLogger>(); | ||
| }); | ||
|
|
||
| // Retrieve a console logger with constructor injection | ||
| ConsoleLogger consoleLogger = Ioc.Default.GetService<ConsoleLogger>(); | ||
| ``` | ||
|
|
||
| The DI service provider will automatically check whether all the necessary services are registered, then it will retrieve them and invoke the constructor for the registered `ILogger` concrete type, to get the instance to return - all done automatically! | ||
|
|
||
| ## More docs | ||
|
|
||
| For more info about `Microsoft.Extensions.DependencyInjection`, see [here](https://docs.microsoft.com/aspnet/core/fundamentals/dependency-injection). | ||
|
|
||
| ## Sample Code | ||
|
|
||
| There are more examples in the [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.Shared/Mvvm). | ||
|
|
||
| ## Requirements | ||
|
|
||
| | Device family | Universal, 10.0.16299.0 or higher | | ||
| | --- | --- | | ||
| | Namespace | Microsoft.Toolkit.Mvvm | | ||
| | NuGet package | [Microsoft.Toolkit.Mvvm](https://www.nuget.org/packages/Microsoft.Toolkit.Mvvm/) | | ||
|
|
||
| ## API | ||
|
|
||
| * [ObservableObject source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| --- | ||
| title: Messenger | ||
| author: Sergio0694 | ||
| description: A type that can be used to exchange messages between different objects | ||
| keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, mvvm, service, messenger, messaging, net core, net standard | ||
| dev_langs: | ||
| - csharp | ||
| --- | ||
|
|
||
| # Messenger | ||
|
|
||
| The [`Messenger`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.Messaging.Messenger) class (with the accompanying [`IMessenger`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.Messaging.IMessenger) interface) can be used to exchange messages between different objects. This can be useful to decouple different modules of an application without having to keep strong references to types being referenced. It is also possible to send messages to specific channels, uniquely identified by a token, and to have different messengers in different sections of an application. | ||
|
|
||
| ## How it works | ||
|
|
||
| The `Messenger` type is responsible for maintaining links between recipients (receivers of messages) and their registered message types, with relative message handlers. Any object can be registered as a recipient for a given message type using a message handler, which will be invoked whenever the `Messenger` instance is used to send a message of that type. It is also possible to send messages through specific communication channels (each identified by a unique token), so that multiple modules can exchange messages of the same type without causing conflicts. Messages sent without a token use the default shared channel. | ||
|
|
||
| There are two ways to perform message registration: either through the `IRecipient<TMessage>` interface, or using an `Action<TMessage>` delegate acting as message handler. The first lets you register all the handlers with a single call to the `RegisterAll` extension, which automatically registers the recipients of all the declared message handlers, while the latter is useful when you need more flexibility or when you want to use a simple lambda expression as a message handler. | ||
|
|
||
| Similar to the `Ioc` class, `Messenger` exposes a `Default` property that offers a thread-safe implementation built-in into the package. It is also possible to create multiple `Messenger` instances if needed, for instance if a different one is injected with a DI service provider into a different module of the app (for instance, multiple windows running in the same process). | ||
|
|
||
| ## Sending and receiving messages | ||
|
|
||
| Consider the following: | ||
|
|
||
| ```csharp | ||
| // Create a message | ||
| public class LoggedInUserChangedMessage : ValueChangedMessage<User> | ||
| { | ||
| public LoggedInUserChangedMessage(User user) : base(user) | ||
| { | ||
| } | ||
| } | ||
|
|
||
| // Register a message in some module | ||
| Messenger.Default.Register<LoggedInUserChangedMessage>(this, m => | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good to indicate examples of where in the code the calls to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added an example scenario in adc9afc, let me know if it needs tweaking! 👍 |
||
| { | ||
| // Handle the message here | ||
| }); | ||
|
|
||
| // Send a message from some other module | ||
| Messenger.Default.Send(new LoggedInUserChangedMessage(user)); | ||
| ``` | ||
|
|
||
| Let's imagine this message type being used in a simple messaging application, which displays a header with the user name and profile image of the currently logged user, a panel with a list of conversations, and another panel with messages from the current conversation, if one is selected. Let's say these three sections are supported by the `HeaderViewModel`, `ConversationsListViewModel` and `ConversationViewModel` types respectively. In this scenario, the `LoggedInUserChangedMessage` message might be sent by the `HeaderViewModel` after a login operation has completed, and both those other viewmodels might register handlers for it. For instance, `ConversationsListViewModel` will load the list of conversations for the new user, and `ConversationViewModel` will just close the current conversation, if one is present. | ||
|
|
||
| The `Messenger` class takes care of delivering messages to all the registered recipients. Note that a recipient can subscribe to messages of a specific type. Note that inherited message types are not registered in the default `Messenger` implementation. | ||
|
|
||
| When a recipient is not needed anymore, you should unregister it so that it will stop receiving messages. You can unregister either by message type, by registration token, or by recipient: | ||
|
|
||
| ```csharp | ||
| // Unregisters the recipient from a message type | ||
| Messenger.Default.Unregister<LoggedInUserChangedMessage>(this); | ||
|
|
||
| // Unregisters the recipient from a message type in a specified channel | ||
| Messenger.Default.Unregister<LoggedInUserChangedMessage, int>(this, 42); | ||
|
|
||
| // Unregister the recipient from all messages, across all channels | ||
| Messenger.Default.UnregisterAll(this); | ||
| ``` | ||
|
|
||
| > [!WARNING] | ||
| > The `Messenger` implementation uses strong references to track the registered recipients. This is done for performance reasons, and it means that each registered recipient should manually be unregistered to avoid memory leaks. That is, as long as a recipient is registered, the `Messenger` instance in use will keep an active reference to it, which will prevent the garbage collector from being able to collect that instance. You can either handle this manually, or you can inherit from [`ObservableRecipient`](ObservableRecipient.md), which by default automatically takes care of removing all the message registrations for recipient when it is deactivated (see docs on `ObservableRecipient` for more info about this). | ||
|
|
||
| ## Using request messages | ||
|
|
||
| Another useful feature of messenger instances is that they can also be used to request values from a module to another. In order to do so, the package includes a base `RequestMessage<T>` class, which can be used like so: | ||
|
|
||
| ```csharp | ||
| // Create a message | ||
| public class LoggedInUserRequestMessage : RequestMessage<User> | ||
| { | ||
| } | ||
|
|
||
| // Register the receiver in a module | ||
| Messenger.Default.Register<LoggedInUserRequestMessage>(this, m => | ||
| { | ||
| m.Reply(CurrentUser); // Assume this is a private member | ||
| }); | ||
|
|
||
| // Request the value from another module | ||
| User user = Messenger.Default.Send<LoggedInUserRequestMessage>(); | ||
| ``` | ||
|
|
||
| The `RequestMessage<T>` class includes an implicit converter that makes the conversion from a `LoggedInUserRequestMessage` to its contained `User` object possible. This will also check that a response has been received for the message, and throw an exception if that's not the case. It is also possible to send request messages without this mandatory response guarantee: just store the returned message in a local variable, and then manually check whether a response value is available or not. Doing so will not trigger the automatic exception if a response is not received when the `Send` method returns. | ||
|
|
||
| The same namespace also includes base requests message for other scenarios: `AsyncRequestMessage<T>`, `CollectionRequestMessage<T>` and `AsyncCollectionRequestMessage<T>`. | ||
| Here's how you can use an async request message: | ||
|
|
||
| ```csharp | ||
| // Create a message | ||
| public class LoggedInUserRequestMessage : AsyncRequestMessage<User> | ||
| { | ||
| } | ||
|
|
||
| // Register the receiver in a module | ||
| Messenger.Default.Register<LoggedInUserRequestMessage>(this, m => | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like an async method that isn't using the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method is actually synchronous (you can't have an asynchronous method at all if you don't use the Let me know if that helps, and/or if you feel like we should add a note with more info to clarify this 😊
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My concern is that someone will see Does There's currently lots of potential for uncertainty here.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The The point for users here should just be that you can reply to async request messages with a I totally get your point though and we might need to add some notes on this, I feel like this is a point over which many developers get confused pretty often. As in, they see any
Let's imagine you had some Messenger.Default.Register<ValueAsyncRequestMessage>(this, async m =>
{
m.Reply(await IRestService.GetValueAsync());
});You'd instead skip the extra async state machine (which would also add unnecessary overhead in this case) and do: Messenger.Default.Register<ValueAsyncRequestMessage>(this, m =>
{
m.Reply(RestService.GetValueAsync());
});It's exactly the same - the only differenceis that in this case the only await operation would be in the message sender on the returned request message, skipping the "proxy" one in the actual message handler. I should note that this is purely an implementation detail though - there's no practical functionality change because of this. The same exact functionality is still possible, it's just a matter of how it's used, or specifically, of how you'd write asynchronous message listeners for async request messages. Let me know if this helps, and how you think we should clarify this in the docs! 😊 |
||
| { | ||
| m.Reply(GetCurrentUserAsync()); // We're replying with a Task<User> | ||
| }); | ||
|
|
||
| // Request the value from another module (we can directly await on the request) | ||
| User user = await Messenger.Default.Send<LoggedInUserRequestMessage>(); | ||
| ``` | ||
|
|
||
| ## Sample Code | ||
|
|
||
| There are more examples in the [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.Shared/Mvvm). | ||
|
|
||
| ## Requirements | ||
|
|
||
| | Device family | Universal, 10.0.16299.0 or higher | | ||
| | --- | --- | | ||
| | Namespace | Microsoft.Toolkit.Mvvm | | ||
| | NuGet package | [Microsoft.Toolkit.Mvvm](https://www.nuget.org/packages/Microsoft.Toolkit.Mvvm/) | | ||
|
|
||
| ## API | ||
|
|
||
| * [Messenger source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs) | ||
| * [IMessenger source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs) | ||
Uh oh!
There was an error while loading. Please reload this page.