mvvm-toolkit-messenger
CommunityToolkit.Mvvm Messenger pub/sub for decoupled communication between ViewModels (or any objects). Covers WeakReferenceMessenger vs StrongReferenceMessenger, IRecipient<TMessage>, RequestMessage<T> / AsyncRequestMessage<T> / CollectionRequestMessage<T>, ValueChangedMessage<T>, channels (tokens), and the ObservableRecipient activation lifecycle. Use across WPF, WinUI 3, .NET MAUI, Uno, and Avalonia.
DeepseekModel
キュレーション済みスキル
品質 優秀 · 90
v1.0.0
取得
https://deepseekmodel.com/api/download.php?id=github-awesome-copilot-skills-mvvm-toolkit-messenger-skill-md&format=skill
ダウンロード .skill
標準形式。system_prompt と model_config を収録し、任意の Agent で利用可能
.skill ファイルの system_prompt フィールドの実際の内容。
name mvvm-toolkit-messenger description CommunityToolkit.Mvvm Messenger pub/sub for decoupled communication between ViewModels (or any objects). Covers WeakReferenceMessenger vs StrongReferenceMessenger, IRecipient<TMessage>, RequestMessage<T> / AsyncRequestMessage<T> / CollectionRequestMessage<T>, ValueChangedMessage<T>, channels (tokens), and the ObservableRecipient activation lifecycle. Use across WPF, WinUI 3, .NET MAUI, Uno, and Avalonia. CommunityToolkit.Mvvm Messenger Pub/sub messaging for ViewModels (or any objects) without forcing a shared reference graph. Part of CommunityToolkit.Mvvm 8.x. TL;DR. Default to WeakReferenceMessenger.Default . Register handlers with the (recipient, message) lambda and the static modifier so you never capture this . Inherit from ObservableRecipient and toggle IsActive at activation/deactivation to get automatic register/unregister. When to use this skill Two or more ViewModels need to react to an event (login, theme change, save, navigation) without holding references to each other A ViewModel needs to ask another VM for a value (request/reply) You're scoping events to a sub-system or window with channel tokens Diagnosing "my handler never fires" or weak-reference recipient lifetime problems For source generators, base classes, and commands see the mvvm-toolkit skill. For DI wiring (registering an IMessenger instance), see mvvm-toolkit-di . Choose an implementation Type When WeakReferenceMessenger.Default Default. Recipients held weakly — eligible for GC even while registered. Internal trimming runs during full GCs; no manual Cleanup() needed. StrongReferenceMessenger.Default Profiler shows the messenger is hot and allocation matters. Recipients are pinned until you Unregister . Forgetting unregistration leaks them. Custom IMessenger instance Per-window/per-scope (e.g., one messenger per app window). Construct directly, inject via DI. ObservableRecipient 's parameterless constructor uses WeakReferenceMessenger.Default . Pass a different IMessenger to its constructor to override. Define a message The toolkit ships base classes; any class works. using CommunityToolkit.Mvvm.Messaging.Messages; // Single-payload broadcast public sealed class LoggedInUserChangedMessage ( User user ) : ValueChangedMessage < User >( user ) ; // Custom shape (records are great for this) public sealed record ThemeChangedMessage ( AppTheme NewTheme ) ; // Empty signal public sealed record RefreshRequestedMessage ; Register a recipient Lambda style (recommended) WeakReferenceMessenger.Default.Register<MyViewModel, ThemeChangedMessage>( this , static (recipient, message) => recipient.OnThemeChanged(message.NewTheme)); The static modifier prevents accidental closure allocation and keeps this out of the lambda — use the recipient parameter instead. IRecipient<TMessage> interface style public sealed class MyViewModel : ObservableRecipient , IRecipient < ThemeChangedMessage >, IRecipient < RefreshRequestedMessage > { public void Receive ( ThemeChangedMessage message ) { /* ... */ } public void Receive ( RefreshRequestedMessage message ) { /* ... */ } } ObservableRecipient.OnActivated() calls Messenger.RegisterAll(this) , which subscribes every IRecipient<T> interface implemented by the type. If you're not using ObservableRecipient , register manually: WeakReferenceMessenger.Default.RegisterAll( this ); Send a message WeakReferenceMessenger.Default.Send( new ThemeChangedMessage(AppTheme.Dark)); // Empty payloads use the parameterless overload: WeakReferenceMessenger.Default.Send<RefreshRequestedMessage>(); Channels (tokens) Scope messages to a sub-system or window with a token (any equatable value — int , string , Guid ): const int LeftPaneChannel = 1 ; WeakReferenceMessenger.Default.Register<MyViewModel, RefreshRequestedMessage, int >( this , LeftPaneChannel, static (r, _) => r.RefreshLeft()); WeakReferenceMessenger.Default.Send( new RefreshRequestedMessage(), LeftPaneChannel); Messages sent without a token use the default shared channel — they are not delivered to channel-scoped recipients. Request / reply For ask-style scenarios where a recipient provides a value back to the sender, use the RequestMessage<T> family. Sync request public sealed class CurrentUserRequest : RequestMessage < User > { } WeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>( this , static (r, m) => m.Reply(r.CurrentUser)); User user = WeakReferenceMessenger.Default.Send<CurrentUserRequest>(); The implicit conversion from CurrentUserRequest to User throws if no recipient called Reply . Capture the message to check first: var request = WeakReferenceMessenger.Default.Send<CurrentUserRequest>(); if (request.HasReceivedResponse) User user = request.Response; Async request public sealed class CurrentUserRequest : AsyncRequestMessage < User > { } WeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>( this , static (r, m) => m.Reply(r.GetCurrentUserAsync())); User user = await WeakReferenceMessenger.Default.Send<CurrentUserRequest>(); Collection requests (fan-in) CollectionRequestMessage<T> and AsyncCollectionRequestMessage<T> collect a Reply from every responding recipient: public sealed class OpenDocumentsRequest : CollectionRequestMessage < Document > { } var docs = WeakReferenceMessenger.Default.Send<OpenDocumentsRequest>(); foreach (Document doc in docs) { /* ... */ } Lifecycle Even with WeakReferenceMessenger , unregister explicitly when a recipient is being torn down — it trims dead entries and improves performance: WeakReferenceMessenger.Default.Unregister<ThemeChangedMessage>( this ); WeakReferenceMessenger.Default.Unregister<ThemeChangedMessage, int >( this , LeftPaneChannel); WeakReferenceMessenger.Default.UnregisterAll( this ); ObservableRecipient.OnDeactivated() does this automatically when IsActive flips to false . Set it from your activation hook: protected override void OnNavigatedTo ( NavigationEventArgs e ) { base .OnNavigatedTo(e); ViewModel.IsActive = true ; } protected override void OnNavigatedFrom ( NavigationEventArgs e ) { ViewModel.IsActive = false ; base .OnNavigatedFrom(e); } Common pitfalls Capturing this in the lambda. (r, m) => OnX(m) implicitly captures this ; allocates a closure and confuses lifetime. Always use (r, m) => r.OnX(m) with static . Strong-ref recipients without Unregister . With StrongReferenceMessenger , recipients (and their entire object graph) stay pinned forever. Either inherit from ObservableRecipient (auto-unregisters in OnDeactivated ) or call UnregisterAll(this) . Inherited message types. A handler registered for BaseMessage is not invoked for DerivedMessage : BaseMessage . Register each concrete type. Wrong messenger instance. Sending via WeakReferenceMessenger.Default and registering via an injected per-window messenger means the message never arrives. Use the same IMessenger everywhere (typically inject it via ObservableRecipient(messenger) ). OnActivated never runs. ObservableRecipient only registers IRecipient<T> handlers when IsActive flips from false to true . Cross-thread updates. The messenger is thread-agnostic. If a handler updates UI, marshal manually ( DispatcherQueue.TryEnqueue / Dispatcher.BeginInvoke ). Multiple messengers (per-window scoping) services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default); // app-wide services.AddScoped<WindowScopedMessenger>(); // per-window Inject the appropriate IMessenger into the ViewModel constructor: public sealed partial class WindowViewModel ( IMessenger messenger ) : ObservableRecipient ( messenger ) { } This isolates broadcasts to a single window — useful for multi-window desktop apps (WinUI 3, WPF, MAUI desktop, Avalonia). References Topic File Full deep dive (more channel/lifecycle examples, diagnostics) references/messenger-patterns.md External: Messenger docs: https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger WeakReferenceMessenger API: https://learn.microsoft.com/en-us/dotnet/api/communitytoolkit.mvvm.messaging.weakreferencemessenger Source: https://github.com/CommunityToolkit/dotnet
このスキルを起動するキーワード。クリックでコピーできます。
このスキルにはトリガーワードがありません。
ダウンロードした .skill に含まれるフィールド。
| フィールド | 説明 |
|---|---|
| format | フォーマット識別子(skill/v1) |
| skill_id | スキル固有 ID |
| name | スキル名 |
| version | バージョン |
| description | 説明 |
| category | カテゴリ(配列) |
| trigger_words | トリガーワード |
| tags | タグ |
| source | ソース |
| source_url | ソース URL(本ページ) |
| exported_at | エクスポート日時(ダウンロード毎) |
| system_prompt | システムプロンプト本文 |
| model_config | モデル設定:provider / model / temperature / max_tokens / top_p |
| examples | サンプル |
| install_guide | 各プラットフォームの導入説明(Coze / Dify / Claude / カスタム) |