Skip to content

Commit 44f2693

Browse files
worldbeaterglennawatson
authored andcommitted
feature: Add ReactiveWindow (#1827)
1 parent ad8920e commit 44f2693

File tree

2 files changed

+194
-110
lines changed

2 files changed

+194
-110
lines changed

src/ReactiveUI.Tests/DependencyResolverTests.cs

Lines changed: 124 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -5,167 +5,181 @@
55
using ReactiveUI;
66
using ReactiveUI.Tests;
77
using Splat;
8-
98
using Xunit;
109

1110
namespace ReactiveUI.Tests
1211
{
13-
public class ExampleViewModel : ReactiveObject
14-
{
15-
}
12+
public class ExampleViewModel : ReactiveObject
13+
{
14+
}
1615

17-
public class AnotherViewModel : ReactiveObject
18-
{
19-
}
16+
public class AnotherViewModel : ReactiveObject
17+
{
18+
}
2019

21-
public class NeverUsedViewModel : ReactiveObject
22-
{
23-
}
20+
public class NeverUsedViewModel : ReactiveObject
21+
{
22+
}
2423

25-
public class SingleInstanceExampleViewModel : ReactiveObject
26-
{
27-
}
24+
public class SingleInstanceExampleViewModel : ReactiveObject
25+
{
26+
}
2827

29-
public class ViewModelWithWeirdName : ReactiveObject
30-
{
31-
}
28+
public class ViewModelWithWeirdName : ReactiveObject
29+
{
30+
}
3231

33-
public class ExampleView : ReactiveUserControl<ExampleViewModel>
34-
{
35-
}
32+
public class ExampleWindowViewModel : ReactiveObject
33+
{
34+
}
3635

37-
public class AnotherView : ReactiveUserControl<AnotherViewModel>
38-
{
39-
}
36+
public class ExampleWindowView : ReactiveWindow<ExampleWindowViewModel>
37+
{
38+
}
39+
40+
public class ExampleView : ReactiveUserControl<ExampleViewModel>
41+
{
42+
}
43+
44+
public class AnotherView : ReactiveUserControl<AnotherViewModel>
45+
{
46+
}
47+
48+
[ViewContract("contract")]
49+
public class ContractExampleView : ReactiveUserControl<ExampleViewModel>
50+
{
51+
}
52+
53+
[SingleInstanceView]
54+
public class NeverUsedView : ReactiveUserControl<NeverUsedViewModel>
55+
{
56+
public static int Instances;
4057

41-
[ViewContract("contract")]
42-
public class ContractExampleView : ReactiveUserControl<ExampleViewModel>
58+
public NeverUsedView()
4359
{
60+
Instances++;
4461
}
62+
}
4563

46-
[SingleInstanceView]
47-
public class NeverUsedView : ReactiveUserControl<NeverUsedViewModel>
48-
{
49-
public static int Instances;
64+
[SingleInstanceView]
65+
public class SingleInstanceExampleView : ReactiveUserControl<SingleInstanceExampleViewModel>
66+
{
67+
public static int Instances;
5068

51-
public NeverUsedView()
52-
{
53-
Instances++;
54-
}
69+
public SingleInstanceExampleView()
70+
{
71+
Instances++;
5572
}
73+
}
5674

57-
[SingleInstanceView]
58-
public class SingleInstanceExampleView : ReactiveUserControl<SingleInstanceExampleViewModel>
59-
{
60-
public static int Instances;
75+
[ViewContract("contract")]
76+
[SingleInstanceView]
77+
public class SingleInstanceWithContractExampleView : ReactiveUserControl<SingleInstanceExampleViewModel>
78+
{
79+
public static int Instances;
6180

62-
public SingleInstanceExampleView() { Instances++; }
81+
public SingleInstanceWithContractExampleView()
82+
{
83+
Instances++;
6384
}
85+
}
6486

65-
[ViewContract("contract")]
66-
[SingleInstanceView]
67-
public class SingleInstanceWithContractExampleView : ReactiveUserControl<SingleInstanceExampleViewModel>
68-
{
69-
public static int Instances;
87+
public class ViewWithoutMatchingName : ReactiveUserControl<ViewModelWithWeirdName>
88+
{
89+
}
7090

71-
public SingleInstanceWithContractExampleView() { Instances++; }
72-
}
91+
public class DependencyResolverTests
92+
{
93+
private readonly IMutableDependencyResolver _resolver;
7394

74-
public class ViewWithoutMatchingName : ReactiveUserControl<ViewModelWithWeirdName>
95+
public DependencyResolverTests()
7596
{
97+
_resolver = new ModernDependencyResolver();
98+
_resolver.InitializeSplat();
99+
_resolver.InitializeReactiveUI();
100+
_resolver.RegisterViewsForViewModels(GetType().Assembly);
76101
}
77102

78-
public class DependencyResolverTests
103+
[WpfFact]
104+
public void RegisterViewsForViewModelShouldRegisterAllViews()
79105
{
80-
private readonly IMutableDependencyResolver _resolver;
81-
82-
public DependencyResolverTests()
106+
using (_resolver.WithResolver())
83107
{
84-
_resolver = new ModernDependencyResolver();
85-
_resolver.InitializeSplat();
86-
_resolver.InitializeReactiveUI();
87-
_resolver.RegisterViewsForViewModels(GetType().Assembly);
88-
}
89-
90-
[WpfFact]
91-
public void RegisterViewsForViewModelShouldRegisterAllViews()
92-
{
93-
using (_resolver.WithResolver())
94-
{
95-
Assert.Single(_resolver.GetServices<IViewFor<ExampleViewModel>>());
96-
Assert.Single(_resolver.GetServices<IViewFor<AnotherViewModel>>());
97-
Assert.Single(_resolver.GetServices<IViewFor<ViewModelWithWeirdName>>());
98-
}
108+
Assert.Single(_resolver.GetServices<IViewFor<ExampleViewModel>>());
109+
Assert.Single(_resolver.GetServices<IViewFor<AnotherViewModel>>());
110+
Assert.Single(_resolver.GetServices<IViewFor<ExampleWindowViewModel>>());
111+
Assert.Single(_resolver.GetServices<IViewFor<ViewModelWithWeirdName>>());
99112
}
113+
}
100114

101-
[WpfFact]
102-
public void RegisterViewsForViewModelShouldIncludeContracts()
115+
[WpfFact]
116+
public void RegisterViewsForViewModelShouldIncludeContracts()
117+
{
118+
using (_resolver.WithResolver())
103119
{
104-
using (_resolver.WithResolver())
105-
{
106-
Assert.Single(_resolver.GetServices(typeof(IViewFor<ExampleViewModel>), "contract"));
107-
}
120+
Assert.Single(_resolver.GetServices(typeof(IViewFor<ExampleViewModel>), "contract"));
108121
}
122+
}
109123

110-
[WpfFact]
111-
public void NonContractRegistrationsShouldResolveCorrectly()
124+
[WpfFact]
125+
public void NonContractRegistrationsShouldResolveCorrectly()
126+
{
127+
using (_resolver.WithResolver())
112128
{
113-
using (_resolver.WithResolver())
114-
{
115-
Assert.IsType<AnotherView>(_resolver.GetService<IViewFor<AnotherViewModel>>());
116-
}
129+
Assert.IsType<AnotherView>(_resolver.GetService<IViewFor<AnotherViewModel>>());
117130
}
131+
}
118132

119-
[WpfFact]
120-
public void ContractRegistrationsShouldResolveCorrectly()
133+
[WpfFact]
134+
public void ContractRegistrationsShouldResolveCorrectly()
135+
{
136+
using (_resolver.WithResolver())
121137
{
122-
using (_resolver.WithResolver())
123-
{
124-
Assert.IsType<ContractExampleView>(_resolver.GetService(typeof(IViewFor<ExampleViewModel>), "contract"));
125-
}
138+
Assert.IsType<ContractExampleView>(_resolver.GetService(typeof(IViewFor<ExampleViewModel>), "contract"));
126139
}
140+
}
127141

128-
[Fact]
129-
public void SingleInstanceViewsShouldOnlyBeInstantiatedWhenRequested()
142+
[Fact]
143+
public void SingleInstanceViewsShouldOnlyBeInstantiatedWhenRequested()
144+
{
145+
using (_resolver.WithResolver())
130146
{
131-
using (_resolver.WithResolver())
132-
{
133-
Assert.Equal(0, NeverUsedView.Instances);
134-
}
147+
Assert.Equal(0, NeverUsedView.Instances);
135148
}
149+
}
136150

137-
[WpfFact]
138-
public void SingleInstanceViewsShouldOnlyBeInstantiatedOnce()
151+
[WpfFact]
152+
public void SingleInstanceViewsShouldOnlyBeInstantiatedOnce()
153+
{
154+
using (_resolver.WithResolver())
139155
{
140-
using (_resolver.WithResolver())
141-
{
142-
Assert.Equal(0, SingleInstanceExampleView.Instances);
156+
Assert.Equal(0, SingleInstanceExampleView.Instances);
143157

144-
var instance = _resolver.GetService(typeof(IViewFor<SingleInstanceExampleViewModel>));
145-
Assert.Equal(1, SingleInstanceExampleView.Instances);
158+
var instance = _resolver.GetService(typeof(IViewFor<SingleInstanceExampleViewModel>));
159+
Assert.Equal(1, SingleInstanceExampleView.Instances);
146160

147-
var instance2 = _resolver.GetService(typeof(IViewFor<SingleInstanceExampleViewModel>));
148-
Assert.Equal(1, SingleInstanceExampleView.Instances);
161+
var instance2 = _resolver.GetService(typeof(IViewFor<SingleInstanceExampleViewModel>));
162+
Assert.Equal(1, SingleInstanceExampleView.Instances);
149163

150-
Assert.Same(instance, instance2);
151-
}
164+
Assert.Same(instance, instance2);
152165
}
166+
}
153167

154-
[WpfFact]
155-
public void SingleInstanceViewsWithContractShouldResolveCorrectly()
168+
[WpfFact]
169+
public void SingleInstanceViewsWithContractShouldResolveCorrectly()
170+
{
171+
using (_resolver.WithResolver())
156172
{
157-
using (_resolver.WithResolver())
158-
{
159-
Assert.Equal(0, SingleInstanceWithContractExampleView.Instances);
173+
Assert.Equal(0, SingleInstanceWithContractExampleView.Instances);
160174

161-
var instance = _resolver.GetService(typeof(IViewFor<SingleInstanceExampleViewModel>), "contract");
162-
Assert.Equal(1, SingleInstanceWithContractExampleView.Instances);
175+
var instance = _resolver.GetService(typeof(IViewFor<SingleInstanceExampleViewModel>), "contract");
176+
Assert.Equal(1, SingleInstanceWithContractExampleView.Instances);
163177

164-
var instance2 = _resolver.GetService(typeof(IViewFor<SingleInstanceExampleViewModel>), "contract");
165-
Assert.Equal(1, SingleInstanceWithContractExampleView.Instances);
178+
var instance2 = _resolver.GetService(typeof(IViewFor<SingleInstanceExampleViewModel>), "contract");
179+
Assert.Equal(1, SingleInstanceWithContractExampleView.Instances);
166180

167-
Assert.Same(instance, instance2);
168-
}
181+
Assert.Same(instance, instance2);
169182
}
183+
}
170184
}
171185
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Windows;
6+
7+
namespace ReactiveUI
8+
{
9+
/// <summary>
10+
/// A <see cref="Window"/> that is reactive.
11+
/// </summary>
12+
/// <remarks>
13+
/// <para>
14+
/// This class is a <see cref="Window"/> that is also reactive. That is, it implements <see cref="IViewFor{TViewModel}"/>.
15+
/// You can extend this class to get an implementation of <see cref="IViewFor{TViewModel}"/> rather than writing one yourself.
16+
/// </para>
17+
/// <para>
18+
/// Note that the XAML for your control must specify the same base class, including the generic argument you provide for your view
19+
/// model. To do this, use the <c>TypeArguments</c> attribute as follows:
20+
/// <code>
21+
/// <![CDATA[
22+
/// <rxui:ReactiveWindow
23+
/// x:Class="views:YourView"
24+
/// x:TypeArguments="vms:YourViewModel"
25+
/// xmlns:rxui="http://reactiveui.net"
26+
/// xmlns:views="clr-namespace:Foo.Bar.Views"
27+
/// xmlns:vms="clr-namespace:Foo.Bar.ViewModels">
28+
/// <!-- view XAML here -->
29+
/// </rxui:ReactiveWindow>
30+
/// ]]>
31+
/// </code>
32+
/// </para>
33+
/// </remarks>
34+
/// <typeparam name="TViewModel">
35+
/// The type of the view model backing the view.
36+
/// </typeparam>
37+
public abstract class ReactiveWindow<TViewModel> :
38+
Window, IViewFor<TViewModel>
39+
where TViewModel : class
40+
{
41+
/// <summary>
42+
/// Gets the binding root view model.
43+
/// </summary>
44+
public TViewModel BindingRoot => ViewModel;
45+
46+
/// <summary>
47+
/// The view model dependency property.
48+
/// </summary>
49+
public static readonly DependencyProperty ViewModelProperty =
50+
DependencyProperty.Register(
51+
"ViewModel",
52+
typeof(TViewModel),
53+
typeof(ReactiveWindow<TViewModel>),
54+
new PropertyMetadata(null));
55+
56+
/// <inheritdoc/>
57+
public TViewModel ViewModel
58+
{
59+
get => (TViewModel)GetValue(ViewModelProperty);
60+
set => SetValue(ViewModelProperty, value);
61+
}
62+
63+
/// <inheritdoc/>
64+
object IViewFor.ViewModel
65+
{
66+
get => ViewModel;
67+
set => ViewModel = (TViewModel)value;
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)