Skip to content

Commit 781cea9

Browse files
authored
fix: Combobox not updating when using Bind() generated ViewModelViewHost (#1906)
1 parent a244d76 commit 781cea9

File tree

6 files changed

+68
-66
lines changed

6 files changed

+68
-66
lines changed

src/ReactiveUI.Tests/Platforms/xaml/ActivationForViewFetcherTest.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,20 @@ public void IsHitTestVisibleActivatesFrameworkElement()
5959

6060
uc.RaiseEvent(loaded);
6161

62-
// IsHitTestVisible still false
63-
Array.Empty<bool>().AssertAreEqual(activated);
62+
// Loaded has happened.
63+
new[] { true }.AssertAreEqual(activated);
6464

6565
uc.IsHitTestVisible = true;
6666

67-
// IsHitTestVisible true
67+
// IsHitTestVisible true, we don't want the event to repeat unnecessarily.
6868
new[] { true }.AssertAreEqual(activated);
6969

7070
var unloaded = new RoutedEventArgs();
7171
unloaded.RoutedEvent = FrameworkElement.UnloadedEvent;
7272

7373
uc.RaiseEvent(unloaded);
7474

75+
// We had both a loaded/hit test visible change/unloaded happen.
7576
new[] { true, false }.AssertAreEqual(activated);
7677
}
7778

src/ReactiveUI.Wpf/TransitioningContentControl.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -250,20 +250,12 @@ private void OnTransitionCompleted(object sender, EventArgs e)
250250
{
251251
AbortTransition();
252252

253-
var handler = TransitionCompleted;
254-
if (handler != null)
255-
{
256-
handler(this, new RoutedEventArgs());
257-
}
253+
TransitionCompleted?.Invoke(this, new RoutedEventArgs());
258254
}
259255

260256
private void RaiseTransitionStarted()
261257
{
262-
var handler = TransitionStarted;
263-
if (handler != null)
264-
{
265-
handler(this, new RoutedEventArgs());
266-
}
258+
TransitionStarted?.Invoke(this, new RoutedEventArgs());
267259
}
268260

269261
private void QueueTransition(object oldContent, object newContent)
@@ -282,8 +274,7 @@ private void QueueTransition(object oldContent, object newContent)
282274
{
283275
// Wire up the completion transition.
284276
var transitionInName = Transition + "Transition_" + TransitionPartType.In;
285-
var transitionIn = GetTransitionStoryboardByName(transitionInName);
286-
CompletingTransition = transitionIn;
277+
CompletingTransition = GetTransitionStoryboardByName(transitionInName);
287278

288279
// Wire up the first transition to start the second transition when it's complete.
289280
startingTransitionName = Transition + "Transition_" + TransitionPartType.Out;
@@ -294,8 +285,7 @@ private void QueueTransition(object oldContent, object newContent)
294285
else
295286
{
296287
startingTransitionName = Transition + "Transition_" + TransitionPart;
297-
var transitionIn = GetTransitionStoryboardByName(startingTransitionName);
298-
CompletingTransition = transitionIn;
288+
CompletingTransition = GetTransitionStoryboardByName(startingTransitionName);
299289
}
300290

301291
// Start the transition.

src/ReactiveUI/Platforms/windows-common/ActivationForViewFetcher.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,30 @@ public IObservable<bool> GetActivationForView(IActivatable view)
4040
#if WINDOWS_UWP
4141
var viewLoaded = WindowsObservable.FromEventPattern<FrameworkElement, object>(
4242
x => fe.Loading += x,
43-
x => fe.Loading -= x).Select(_ => true);
43+
x => fe.Loading -= x)
44+
.Select(_ => true);
45+
46+
var hitTestVisible = fe.WhenAnyValue(x => x.IsHitTestVisible);
4447
#else
4548
var viewLoaded = Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(
4649
x => fe.Loaded += x,
47-
x => fe.Loaded -= x).Select(_ => true);
50+
x => fe.Loaded -= x)
51+
.Select(_ => true);
52+
53+
var hitTestVisible = Observable.FromEventPattern<DependencyPropertyChangedEventHandler, DependencyPropertyChangedEventArgs>(
54+
x => fe.IsHitTestVisibleChanged += x,
55+
x => fe.IsHitTestVisibleChanged -= x)
56+
.Select(x => (bool)x.EventArgs.NewValue);
4857
#endif
4958

5059
var viewUnloaded = Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(
5160
x => fe.Unloaded += x,
52-
x => fe.Unloaded -= x).Select(_ => false);
61+
x => fe.Unloaded -= x)
62+
.Select(_ => false);
5363

5464
return viewLoaded
5565
.Merge(viewUnloaded)
56-
.Select(b => b ? fe.WhenAnyValue(x => x.IsHitTestVisible).SkipWhile(x => !x) : Observables.False)
57-
.Switch()
66+
.Merge(hitTestVisible)
5867
.DistinctUntilChanged();
5968
}
6069
}

src/ReactiveUI/Platforms/windows-common/AutoDataTemplateBindingHook.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class AutoDataTemplateBindingHook : IPropertyBindingHook
3838
#else
3939
const string template = "<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' " +
4040
"xmlns:xaml='clr-namespace:ReactiveUI;assembly=__ASSEMBLYNAME__'> " +
41-
"<xaml:ViewModelViewHost ViewModel=\"{Binding}\" VerticalContentAlignment=\"Stretch\" HorizontalContentAlignment=\"Stretch\" IsTabStop=\"False\" />" +
41+
"<xaml:ViewModelViewHost ViewModel=\"{Binding Mode=OneWay}\" VerticalContentAlignment=\"Stretch\" HorizontalContentAlignment=\"Stretch\" IsTabStop=\"False\" />" +
4242
"</DataTemplate>";
4343

4444
var assemblyName = typeof(AutoDataTemplateBindingHook).Assembly.FullName;

src/ReactiveUI/Platforms/windows-common/ViewModelViewHost.cs

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System;
77
using System.Reactive;
8+
using System.Reactive.Disposables;
89
using System.Reactive.Linq;
910
using System.Reactive.Subjects;
1011
using System.Windows;
@@ -27,22 +28,22 @@ namespace ReactiveUI
2728
public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger, IDisposable
2829
{
2930
/// <summary>
30-
/// The view model dependency property.
31+
/// The default content dependency property.
3132
/// </summary>
32-
public static readonly DependencyProperty ViewModelProperty =
33-
DependencyProperty.Register("ViewModel", typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null, SomethingChanged));
33+
public static readonly DependencyProperty DefaultContentProperty =
34+
DependencyProperty.Register(nameof(DefaultContent), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null));
3435

3536
/// <summary>
36-
/// The default content dependency property.
37+
/// The view model dependency property.
3738
/// </summary>
38-
public static readonly DependencyProperty DefaultContentProperty =
39-
DependencyProperty.Register("DefaultContent", typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null, SomethingChanged));
39+
public static readonly DependencyProperty ViewModelProperty =
40+
DependencyProperty.Register(nameof(ViewModel), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null, SomethingChanged));
4041

4142
/// <summary>
4243
/// The view contract observable dependency property.
4344
/// </summary>
4445
public static readonly DependencyProperty ViewContractObservableProperty =
45-
DependencyProperty.Register("ViewContractObservable", typeof(IObservable<string>), typeof(ViewModelViewHost), new PropertyMetadata(Observable<string>.Default));
46+
DependencyProperty.Register(nameof(ViewContractObservable), typeof(IObservable<string>), typeof(ViewModelViewHost), new PropertyMetadata(Observable<string>.Default, SomethingChanged));
4647

4748
private readonly Subject<Unit> _updateViewModel = new Subject<Unit>();
4849
private string _viewContract;
@@ -57,17 +58,14 @@ public ViewModelViewHost()
5758
DefaultStyleKey = typeof(ViewModelViewHost);
5859
#endif
5960

60-
if (ModeDetector.InUnitTestRunner()) // NB: InUnitTestRunner also returns true in Design Mode
61+
if (ModeDetector.InUnitTestRunner())
6162
{
6263
ViewContractObservable = Observable<string>.Never;
64+
65+
// NB: InUnitTestRunner also returns true in Design Mode
6366
return;
6467
}
6568

66-
var vmAndContract = Observable.CombineLatest(
67-
this.WhenAnyValue(x => x.ViewModel),
68-
this.WhenAnyObservable(x => x.ViewContractObservable),
69-
(vm, contract) => new { ViewModel = vm, Contract = contract, });
70-
7169
var platform = Locator.Current.GetService<IPlatformOperations>();
7270
Func<string> platformGetter = () => default(string);
7371

@@ -87,32 +85,15 @@ public ViewModelViewHost()
8785
.StartWith(platformGetter())
8886
.DistinctUntilChanged();
8987

90-
this.WhenActivated(d =>
91-
{
92-
d(vmAndContract.Subscribe(x =>
93-
{
94-
if (x.ViewModel == null)
95-
{
96-
Content = DefaultContent;
97-
return;
98-
}
99-
100-
var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current;
101-
var view = viewLocator.ResolveView(x.ViewModel, x.Contract) ?? viewLocator.ResolveView(x.ViewModel, null);
102-
103-
if (view == null)
104-
{
105-
throw new Exception($"Couldn't find view for '{x.ViewModel}'.");
106-
}
107-
108-
view.ViewModel = x.ViewModel;
109-
Content = view;
110-
}));
111-
112-
d(this.WhenAnyObservable(x => x.ViewContractObservable)
113-
.ObserveOn(RxApp.MainThreadScheduler)
114-
.Subscribe(x => _viewContract = x));
115-
});
88+
var contractChanged = _updateViewModel.Select(_ => ViewContractObservable).Switch();
89+
var viewModelChanged = _updateViewModel.Select(_ => ViewModel);
90+
91+
var vmAndContract = contractChanged.CombineLatest(viewModelChanged, (contract, vm) => new { ViewModel = vm, Contract = contract });
92+
93+
vmAndContract.Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract));
94+
contractChanged
95+
.ObserveOn(RxApp.MainThreadScheduler)
96+
.Subscribe(x => _viewContract = x);
11697
}
11798

11899
/// <summary>
@@ -186,5 +167,26 @@ private static void SomethingChanged(DependencyObject dependencyObject, Dependen
186167
{
187168
((ViewModelViewHost)dependencyObject)._updateViewModel.OnNext(Unit.Default);
188169
}
170+
171+
private void ResolveViewForViewModel(object viewModel, string contract)
172+
{
173+
if (viewModel == null)
174+
{
175+
Content = DefaultContent;
176+
return;
177+
}
178+
179+
var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current;
180+
var viewInstance = viewLocator.ResolveView(viewModel, contract) ?? viewLocator.ResolveView(viewModel, null);
181+
182+
if (viewInstance == null)
183+
{
184+
throw new Exception($"The {nameof(ViewModelViewHost)} could not find a valid view for the view model of type {viewModel.GetType()} and value {viewModel}.");
185+
}
186+
187+
viewInstance.ViewModel = viewModel;
188+
189+
Content = viewInstance;
190+
}
189191
}
190192
}

src/ReactiveUI/View/DefaultViewLocator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,13 @@ private static Type ToggleViewModelType(Type viewModelType)
132132
if (viewModelType.Name.StartsWith("I", StringComparison.InvariantCulture))
133133
{
134134
var toggledTypeName = DeinterfaceifyTypeName(viewModelTypeName);
135-
var toggledType = Reflection.ReallyFindType(toggledTypeName, throwOnFailure: false);
136-
return toggledType;
135+
return Reflection.ReallyFindType(toggledTypeName, throwOnFailure: false);
137136
}
138137
}
139138
else
140139
{
141140
var toggledTypeName = InterfaceifyTypeName(viewModelTypeName);
142-
var toggledType = Reflection.ReallyFindType(toggledTypeName, throwOnFailure: false);
143-
return toggledType;
141+
return Reflection.ReallyFindType(toggledTypeName, throwOnFailure: false);
144142
}
145143

146144
return null;
@@ -215,6 +213,8 @@ private IViewFor AttemptViewResolution(string viewTypeName, string contract)
215213
return null;
216214
}
217215

216+
this.Log().Debug("Resolved service type '{0}'", viewType.FullName);
217+
218218
return view;
219219
}
220220
catch (Exception ex)

0 commit comments

Comments
 (0)