-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
ViewModelViewHost.cs
173 lines (149 loc) · 6.52 KB
/
ViewModelViewHost.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Copyright (c) 2023 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
using Microsoft.UI.Xaml;
namespace ReactiveUI;
/// <summary>
/// This content control will automatically load the View associated with
/// the ViewModel property and display it. This control is very useful
/// inside a DataTemplate to display the View associated with a ViewModel.
/// </summary>
public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger
{
/// <summary>
/// The default content dependency property.
/// </summary>
public static readonly DependencyProperty DefaultContentProperty =
DependencyProperty.Register(nameof(DefaultContent), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null));
/// <summary>
/// The view model dependency property.
/// </summary>
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register(nameof(ViewModel), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null));
/// <summary>
/// The view contract observable dependency property.
/// </summary>
public static readonly DependencyProperty ViewContractObservableProperty =
DependencyProperty.Register(nameof(ViewContractObservable), typeof(IObservable<string>), typeof(ViewModelViewHost), new PropertyMetadata(Observable<string>.Default));
/// <summary>
/// The ContractFallbackByPass dependency property.
/// </summary>
public static readonly DependencyProperty ContractFallbackByPassProperty =
DependencyProperty.Register("ContractFallbackByPass", typeof(bool), typeof(ViewModelViewHost), new PropertyMetadata(false));
private string? _viewContract;
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelViewHost"/> class.
/// </summary>
public ViewModelViewHost()
{
var platform = Locator.Current.GetService<IPlatformOperations>();
Func<string?> platformGetter = () => default;
if (platform is null)
{
// NB: This used to be an error but WPF design mode can't read
// good or do other stuff good.
this.Log().Error("Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance.");
}
else
{
platformGetter = () => platform.GetOrientation();
}
ViewContractObservable = ModeDetector.InUnitTestRunner()
? Observable<string?>.Never
: Observable.FromEvent<SizeChangedEventHandler, string?>(
eventHandler =>
{
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
void Handler(object? _, SizeChangedEventArgs __) => eventHandler(platformGetter());
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
return Handler;
},
x => SizeChanged += x,
x => SizeChanged -= x)
.StartWith(platformGetter())
.DistinctUntilChanged();
var contractChanged = this.WhenAnyObservable(x => x.ViewContractObservable).Do(x => _viewContract = x).StartWith(ViewContract);
var viewModelChanged = this.WhenAnyValue(x => x.ViewModel).StartWith(ViewModel);
var vmAndContract = contractChanged
.CombineLatest(viewModelChanged, (contract, vm) => (ViewModel: vm, Contract: contract));
this.WhenActivated(d =>
{
d(contractChanged
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => _viewContract = x ?? string.Empty));
d(vmAndContract.DistinctUntilChanged().Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract)));
});
}
/// <summary>
/// Gets or sets the view contract observable.
/// </summary>
public IObservable<string?> ViewContractObservable
{
get => (IObservable<string>)GetValue(ViewContractObservableProperty);
set => SetValue(ViewContractObservableProperty, value);
}
/// <summary>
/// Gets or sets the content displayed by default when no content is set.
/// </summary>
public object DefaultContent
{
get => GetValue(DefaultContentProperty);
set => SetValue(DefaultContentProperty, value);
}
/// <summary>
/// Gets or sets the ViewModel to display.
/// </summary>
public object? ViewModel
{
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
/// <summary>
/// Gets or sets the view contract.
/// </summary>
public string? ViewContract
{
get => _viewContract;
set => ViewContractObservable = Observable.Return(value);
}
/// <summary>
/// Gets or sets a value indicating whether should bypass the default contract fallback behavior.
/// </summary>
public bool ContractFallbackByPass
{
get => (bool)GetValue(ContractFallbackByPassProperty);
set => SetValue(ContractFallbackByPassProperty, value);
}
/// <summary>
/// Gets or sets the view locator.
/// </summary>
public IViewLocator? ViewLocator { get; set; }
/// <summary>
/// resolve view for view model with respect to contract.
/// </summary>
/// <param name="viewModel">ViewModel.</param>
/// <param name="contract">contract used by ViewLocator.</param>
protected virtual void ResolveViewForViewModel(object? viewModel, string? contract)
{
if (viewModel is null)
{
Content = DefaultContent;
return;
}
var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current;
var viewInstance = viewLocator.ResolveView(viewModel, contract);
if (viewInstance is null && !ContractFallbackByPass)
{
viewInstance = viewLocator.ResolveView(viewModel);
}
if (viewInstance is null)
{
Content = DefaultContent;
this.Log().Warn($"The {nameof(ViewModelViewHost)} could not find a valid view for the view model of type {viewModel.GetType()} and value {viewModel}.");
return;
}
viewInstance.ViewModel = viewModel;
Content = viewInstance;
}
}