BindInteraction does not follow ViewModel when the view inherits it from another assembly (e.g. ReactiveUserControl<T>)
Maintainer thường phản hồi trong vòng 1 ngày
Một pull request liên quan đã được merge.
- #180 của @ChrisPulman — đã merge
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 32/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- csharp
- Lĩnh vực
- developer-experience, tooling
Hướng nghiên cứu
Start from BindingEmitterHelpers.ResolveViewModelObservation and TypeDetectionExtractor.ExtractProperties / CollectMembersThroughOwnBases, which stop walking bases at the assembly boundary so an inherited ViewModel is missed. Compare with FindViewModelProperty, which already walks bases. Reproduce with the IViewFor plus referenced-assembly base case in the issue; done when BindInteraction (and likely Bind/OneWayBind/BindCommand) follows view.ViewModel across that boundary as in ReactiveUI 24.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Prerequisites
- I have searched this repository's open and closed issues for duplicates.
- I am using the latest stable release.
What happens
On a view that derives from Avalonia's ReactiveUserControl<T>, BindInteraction registers the handler on the view model the view has at the moment of the call, and never moves it:
- If the view has no view model yet (normal inside
WhenActivated, before the host sets theDataContext), no handler is ever registered. EveryHandlecall later throwsUnhandledInteractionException. - If the view model is replaced later (a view whose
DataContextfollows a selection, for example), the handler stays on the first view model. The new one has no handler.
The same call on a view that declares its own ViewModel property follows view.ViewModel correctly. The difference is only where the ViewModel property is declared: if the view inherits it from a base class in another assembly (as every ReactiveUserControl<T> subclass does), the binding stops following the view.
How we ran into it. Our app is a PDF viewer built on Avalonia, with one tab per open document. The main window hosts a single document view, a ReactiveUserControl<PdfFileViewModel>, whose DataContext is bound to the selected tab, so switching tabs swaps the view model under a view that stays loaded. In WhenActivated that view binds 17 interactions with BindInteraction: zoom in/out, page up/down, scroll to page, fit to width, the save dialog and so on. A search panel works the same way for "focus the search box" on Ctrl+F.
The view activates once, when the window loads. At that point usually no document is open yet, so ViewModel is null. On ReactiveUI 24 this worked: each handler moved to whichever document was selected. After upgrading to ReactiveUI 25 none of the documents had handlers, so the zoom buttons, page navigation, bookmarks and Ctrl+F focus all failed with UnhandledInteractionException. When the app restored a document at startup, only that one document worked, and its interactions acted on whichever document was on screen. Nothing warned at build time, and our tests stayed green because each test selects its document before the window finishes loading.
What you expected instead
The handler follows view.ViewModel, as it does in ReactiveUI 24 and as it already does here for a view that declares ViewModel itself:
- bound while
ViewModelis null → handled once a view model is assigned ViewModelreplaced → the new view model's interaction is handled, the old one's is not
Minimal reproduction
using Avalonia;
using Avalonia.Headless;
using ReactiveUI.Avalonia;
using ReactiveUI.Binding;
AppBuilder.Configure<Application>()
.UseHeadless(new AvaloniaHeadlessPlatformOptions())
.UseReactiveUI(static _ => { })
.SetupWithoutStarting();
var view = new View();
using var binding = view.BindInteraction(view.ViewModel, vm => vm.Ask, Answer); // ViewModel is still null here
var first = new ViewModel();
view.ViewModel = first;
Console.WriteLine(await IsHandled(first)); // False - expected True
var second = new ViewModel();
view.ViewModel = second;
Console.WriteLine(await IsHandled(second)); // False - expected True
static Task Answer(IInteractionContext<string, bool> context)
{
context.SetOutput(true);
return Task.CompletedTask;
}
static async Task<bool> IsHandled(ViewModel vm)
{
try {
await vm.Ask.Handle("question");
return true;
} catch (UnhandledInteractionException<string, bool>) {
return false;
}
}
public sealed class ViewModel
{
public Interaction<string, bool> Ask { get; } = new();
}
public sealed class View : ReactiveUserControl<ViewModel>
{
}
Packages: Avalonia 12.1.3, Avalonia.Headless 12.1.3, ReactiveUI.Avalonia 12.1.6.
Without Avalonia: the same failure reproduces with a plain IViewFor<T> + INotifyPropertyChanged view, as long as ViewModel is inherited from a base class in a referenced assembly. Three views, identical except where ViewModel is declared:
| View | ViewModel declared |
Result |
|---|---|---|
| on the view itself | view | follows the view model ✔ |
| inherited from a base in the same assembly | same assembly | follows the view model ✔ |
| inherited from an identical base in a referenced class library | other assembly | does not follow ✘ |
Steps to reproduce
- Create a console app with the packages above and the code above.
- Run it.
- Both lines print
False; expectedTruetwice.
Exception and stack trace
When the interaction is raised from app code:
ReactiveUI.Binding.UnhandledInteractionException`2[System.String,System.Boolean]: Failed to find a registration for an Interaction.
at ReactiveUI.Binding.Interaction`2.Handle(TInput input)
Package and version
ReactiveUI.Binding 8.8.0 and 9.1.0 (via ReactiveUI 25.1.1 / 26.0.1 and ReactiveUI.Avalonia 12.1.5 / 12.1.6)
UI framework
Avalonia
Operating system
Windows
Target framework
net10.0
Device and OS version
Windows 11 Pro 10.0.26200, x64
IDE
Visual Studio 2026 18.0
Did this work in an earlier version?
ReactiveUI 24.2.0
Workaround
Observe the interaction from the view instead of calling BindInteraction, e.g. this.WhenAnyValue(x => x.ViewModel) → select the interaction → register the handler on each new value, disposing the previous registration.
Additional information
Where it seems to come from, in case it saves time (same in v8.8.0 and v9.1.0):
BindingEmitterHelpers.ResolveViewModelObservationroots the binding at the view only whenFindViewModelPropertyTypefinds a typedViewModelin the view's detected properties. Otherwise it binds to theviewModelargument.- Those properties come from
TypeDetectionExtractor.ExtractProperties→CollectMembersThroughOwnBases, which stops walking base classes at the assembly boundary, so an inheritedViewModel(e.g.ReactiveUserControl<T>.ViewModel) is never seen. FindViewModelPropertyin the same file does walk the bases, and is what the #158 field-owner path uses.
Since ResolveViewModelObservation is shared by Bind, OneWayBind and BindCommand, those view-first bindings are presumably affected on such views too. Only BindInteraction was tested.
- Ngôn ngữ chính
- C#
- Star
- 9
- Fork
- 2
- Merge trung bình
- 7 giờ 7 phút
- Pull request đã merge (30 ngày)
- 75
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của reactiveui/ReactiveUI.Binding.SourceGenerators
-
Dependency DashboardĐang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 30/100
reactiveui/ReactiveUI.Binding.SourceGenerators#3 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của reactiveui/ReactiveUI.Binding.SourceGenerators
Issue tương tự
-
:watch: Not Triaged async-task-programming/subsvc dotnet-csharp/svc
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Python: Bug: AnnotationContent and StreamingAnnotationContent to_element writes CitationType.X, so annotations in a rendered chat history fail to parseCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mởpython triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
microsoft/semantic-kernel#14590 ·
Maintainer thường phản hồi trong vòng 3 ngày
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Needs-Triage
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
PowerShell/PowerShell#28194 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
AI Projects Client customer-reported needs-team-attention question Service Attention
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
Azure/azure-sdk-for-net#63804 · 3 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày