Skip to content

Commit 8d98a6a

Browse files
authored
Fix: command binding now against commandsource on control instead of command property (#417)
* move icommand properties back to simple binding * Generated Command Binding based on class interface * bind to icommand * first part of apply binding * simplify command binding generics
1 parent 9447fbb commit 8d98a6a

File tree

15 files changed

+232
-85
lines changed

15 files changed

+232
-85
lines changed

src/ReactiveUI.WPF.SampleApp/ViewBindingModels/QuestionnaireViewBindingModels.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Linq.Expressions;
88
using System.Windows.Controls;
9+
using System.Windows.Input;
910
using System.Windows.Media;
1011
using ReactiveUI.WPF.SampleApp.ViewModels;
1112
using ReactiveUI.WPF.SampleApp.Views;
@@ -25,7 +26,7 @@ protected override IEnumerable<IControlBindingModel<QuestionnaireView, Questionn
2526
// launch interaction
2627
yield return new ButtonControlBindingModel<QuestionnaireView, QuestionnaireViewModel>(vw => vw.LaunchInteraction)
2728
{
28-
Command = new CommandBinding<QuestionnaireViewModel>(vm => vm.LaunchInteraction)
29+
BindCommand = new CommandBinding<QuestionnaireViewModel>(vm => vm.LaunchInteraction)
2930
};
3031

3132
// Forename

src/Vetuviem.Blazor.SourceGenerator/BlazorPlatformResolver.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ public string GetBaseUiElement()
2929
return "global::Microsoft.AspNetCore.Components.IComponent";
3030
}
3131

32+
/// <inheritdoc />
33+
public string? GetCommandSourceInterface()
34+
{
35+
return null;
36+
}
37+
3238
/// <inheritdoc />
3339
public string? GetCommandInterface()
3440
{

src/Vetuviem.Core/CommandBinding.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
using System;
66
using System.Linq.Expressions;
7-
using System.Reactive;
87
using System.Reactive.Disposables;
98
using System.Windows.Input;
109
using ReactiveUI;
@@ -15,10 +14,10 @@ namespace Vetuviem.Core
1514
/// Represents a command binding between a control and a viewmodel.
1615
/// </summary>
1716
/// <typeparam name="TViewModel">The type for the viewmodel.</typeparam>
18-
public sealed class CommandBinding<TViewModel> : ICommandBinding<TViewModel, ICommand>
17+
public sealed class CommandBinding<TViewModel> : ICommandBinding<TViewModel>
1918
where TViewModel : class
2019
{
21-
private readonly Expression<Func<TViewModel, ReactiveCommand<Unit, Unit>?>> _viewModelBinding;
20+
private readonly Expression<Func<TViewModel, ICommand?>> _viewModelBinding;
2221
private readonly string? _toEvent;
2322

2423
/// <summary>
@@ -27,19 +26,19 @@ public sealed class CommandBinding<TViewModel> : ICommandBinding<TViewModel, ICo
2726
/// <param name="viewModelBinding">Expression for the View Model binding.</param>
2827
/// <param name="toEvent">If specified, bind to the specific event instead of the default.</param>
2928
public CommandBinding(
30-
Expression<Func<TViewModel, ReactiveCommand<Unit, Unit>?>> viewModelBinding,
29+
Expression<Func<TViewModel, ICommand?>> viewModelBinding,
3130
string? toEvent = null)
3231
{
3332
_viewModelBinding = viewModelBinding;
3433
_toEvent = toEvent;
3534
}
3635

3736
/// <inheritdoc/>
38-
public void ApplyBinding<TView>(
37+
public void ApplyBinding<TView, TViewProp>(
3938
Action<IDisposable> disposeAction,
4039
TView view,
4140
TViewModel viewModel,
42-
Expression<Func<TView, ICommand>> viewBinding)
41+
Expression<Func<TView, TViewProp>> viewBinding)
4342
where TView : class, IViewFor<TViewModel>
4443
{
4544
if (disposeAction == null)
@@ -70,11 +69,11 @@ public void ApplyBinding<TView>(
7069
}
7170

7271
/// <inheritdoc/>
73-
public void ApplyBinding<TView>(
72+
public void ApplyBinding<TView, TViewProp>(
7473
CompositeDisposable compositeDisposable,
7574
TView view,
7675
TViewModel viewModel,
77-
Expression<Func<TView, ICommand>> viewBinding)
76+
Expression<Func<TView, TViewProp>> viewBinding)
7877
where TView : class, IViewFor<TViewModel>
7978
{
8079
if (compositeDisposable == null)

src/Vetuviem.Core/ICommandBinding.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ namespace Vetuviem.Core
1313
/// Represents a View to View Model Binding for Commands.
1414
/// </summary>
1515
/// <typeparam name="TViewModel">The type for the ViewModel.</typeparam>
16-
/// <typeparam name="TViewProp">The type for the View.</typeparam>
17-
public interface ICommandBinding<TViewModel, TViewProp>
16+
public interface ICommandBinding<TViewModel>
1817
where TViewModel : class
1918
{
2019
/// <summary>
2120
/// Applies a View to View Model Binding.
2221
/// </summary>
2322
/// <typeparam name="TView">The type for the view.</typeparam>
23+
/// <typeparam name="TViewProp">The type for the View Property.</typeparam>
2424
/// <param name="d">The disposable action registration. Used to clean up when bindings fall out of scope.</param>
2525
/// <param name="view">The instance of the View to bind.</param>
2626
/// <param name="viewModel">The instance of the ViewModel to Bind.</param>
2727
/// <param name="viewBinding">Expression of the View Property to Bind to.</param>
28-
void ApplyBinding<TView>(
28+
void ApplyBinding<TView, TViewProp>(
2929
Action<IDisposable> d,
3030
TView view,
3131
TViewModel viewModel,
@@ -36,11 +36,12 @@ void ApplyBinding<TView>(
3636
/// Applies a View to View Model Binding.
3737
/// </summary>
3838
/// <typeparam name="TView">The type for the view.</typeparam>
39+
/// <typeparam name="TViewProp">The type for the View Property.</typeparam>
3940
/// <param name="compositeDisposable">The disposable action registration. Used to clean up when bindings fall out of scope.</param>
4041
/// <param name="view">The instance of the View to bind.</param>
4142
/// <param name="viewModel">The instance of the ViewModel to Bind.</param>
4243
/// <param name="viewBinding">Expression of the View Property to Bind to.</param>
43-
void ApplyBinding<TView>(
44+
void ApplyBinding<TView, TViewProp>(
4445
CompositeDisposable compositeDisposable,
4546
TView view,
4647
TViewModel viewModel,

src/Vetuviem.SourceGenerator/AbstractBaseSourceGenerator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ public void Execute(GeneratorExecutionContext context)
230230
return namespaceDeclaration;
231231
}
232232

233-
var desiredCommandInterface = platformResolver.GetCommandInterface();
233+
var desiredCommandInterface = platformResolver.GetCommandSourceInterface();
234234

235235
var generatorProcessor = new TGeneratorProcessor();
236236

@@ -247,7 +247,8 @@ public void Execute(GeneratorExecutionContext context)
247247
platformName,
248248
namespaceName,
249249
makeClassesPublic,
250-
includeObsoleteItems);
250+
includeObsoleteItems,
251+
platformResolver.GetCommandInterface());
251252

252253
return result;
253254
}

src/Vetuviem.SourceGenerator/Features/ControlBindingModels/AbstractControlBindingModelClassGenerator.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public ClassDeclarationSyntax GenerateClass(
2525
string platformName,
2626
string rootNamespace,
2727
bool makeClassesPublic,
28-
bool includeObsoleteItems)
28+
bool includeObsoleteItems,
29+
string? platformCommandType)
2930
{
3031
var typeParameterList = GetTypeParameterListSyntax(namedTypeSymbol);
3132

@@ -60,7 +61,8 @@ public ClassDeclarationSyntax GenerateClass(
6061
controlClassFullName,
6162
platformName,
6263
makeClassesPublic,
63-
includeObsoleteItems);
64+
includeObsoleteItems,
65+
platformCommandType);
6466

6567
return classDeclaration
6668
.WithModifiers(modifiers)
@@ -191,7 +193,8 @@ protected abstract SyntaxList<MemberDeclarationSyntax> ApplyMembers(
191193
string controlClassFullName,
192194
string platformName,
193195
bool makeClassesPublic,
194-
bool includeObsoleteItems);
196+
bool includeObsoleteItems,
197+
string? platformCommandType);
195198

196199
/// <summary>
197200
/// Gets the class name identifier from a named type symbol.

src/Vetuviem.SourceGenerator/Features/ControlBindingModels/ControlBindingModelPropertyGenerator.cs

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public static SyntaxList<MemberDeclarationSyntax> GetProperties(
3030
INamedTypeSymbol namedTypeSymbol,
3131
string? desiredCommandInterface,
3232
bool makeClassesPublic,
33-
bool includeObsoleteItems)
33+
bool includeObsoleteItems,
34+
string? platformCommandType)
3435
{
3536
if (namedTypeSymbol == null)
3637
{
@@ -42,8 +43,21 @@ public static SyntaxList<MemberDeclarationSyntax> GetProperties(
4243
.Where(x => x.Kind == SymbolKind.Property)
4344
.ToArray();
4445

46+
var fullName = namedTypeSymbol.GetFullName();
47+
4548
var nodes = new List<MemberDeclarationSyntax>(properties.Length);
4649

50+
if (!string.IsNullOrWhiteSpace(desiredCommandInterface)
51+
&& !string.IsNullOrWhiteSpace(platformCommandType)
52+
&& namedTypeSymbol.Interfaces.Any(interfaceName => interfaceName.GetFullName().Equals(desiredCommandInterface, StringComparison.Ordinal)))
53+
{
54+
var bindCommandPropertyDeclaration = GetBindCommandPropertyDeclaration(
55+
makeClassesPublic,
56+
fullName,
57+
platformCommandType!);
58+
nodes.Add(bindCommandPropertyDeclaration);
59+
}
60+
4761
foreach (var prop in properties)
4862
{
4963
var propertySymbol = prop as IPropertySymbol;
@@ -74,15 +88,7 @@ public static SyntaxList<MemberDeclarationSyntax> GetProperties(
7488
continue;
7589
}
7690

77-
var accessorList = new[]
78-
{
79-
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
80-
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
81-
SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration)
82-
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
83-
};
84-
85-
var fullName = namedTypeSymbol.GetFullName();
91+
var accessorList = GetAccessorDeclarationSyntaxes();
8692

8793
var summary = XmlSyntaxFactory.GenerateSummarySeeAlsoComment(
8894
"Gets or sets the binding logic for {0}",
@@ -101,6 +107,36 @@ public static SyntaxList<MemberDeclarationSyntax> GetProperties(
101107
return new SyntaxList<MemberDeclarationSyntax>(nodes);
102108
}
103109

110+
private static AccessorDeclarationSyntax[] GetAccessorDeclarationSyntaxes()
111+
{
112+
var accessorList = new[]
113+
{
114+
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
115+
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
116+
SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration)
117+
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
118+
};
119+
return accessorList;
120+
}
121+
122+
private static MemberDeclarationSyntax GetBindCommandPropertyDeclaration(
123+
bool makeClassesPublic,
124+
string fullName,
125+
string platformCommandType)
126+
{
127+
var accessorList = GetAccessorDeclarationSyntaxes();
128+
129+
var summary = XmlSyntaxFactory.GenerateSummarySeeAlsoComment(
130+
"Gets or sets the command binding logic for {0}",
131+
fullName);
132+
133+
return GetBindCommandPropertyDeclaration(
134+
accessorList,
135+
summary,
136+
makeClassesPublic,
137+
platformCommandType);
138+
}
139+
104140
private static bool ReplacesBaseProperty(
105141
IPropertySymbol propertySymbol,
106142
INamedTypeSymbol namedTypeSymbol)
@@ -128,6 +164,25 @@ private static bool ReplacesBaseProperty(
128164
return false;
129165
}
130166

167+
private static PropertyDeclarationSyntax GetBindCommandPropertyDeclaration(
168+
AccessorDeclarationSyntax[] accessorList,
169+
IEnumerable<SyntaxTrivia> summary,
170+
bool makeClassesPublic,
171+
string platformCommandType)
172+
{
173+
TypeSyntax type = GetCommandBindingTypeSyntax(platformCommandType);
174+
175+
var result = SyntaxFactory.PropertyDeclaration(
176+
type,
177+
"BindCommand")
178+
.AddModifiers(SyntaxFactory.Token(makeClassesPublic ? SyntaxKind.PublicKeyword : SyntaxKind.InternalKeyword))
179+
.WithAccessorList(
180+
SyntaxFactory.AccessorList(SyntaxFactory.List(accessorList)))
181+
.WithLeadingTrivia(summary);
182+
183+
return result;
184+
}
185+
131186
private static PropertyDeclarationSyntax GetPropertyDeclaration(
132187
IPropertySymbol prop,
133188
AccessorDeclarationSyntax[] accessorList,
@@ -148,6 +203,12 @@ private static PropertyDeclarationSyntax GetPropertyDeclaration(
148203
return result;
149204
}
150205

206+
private static TypeSyntax GetCommandBindingTypeSyntax(string platformCommandType)
207+
{
208+
var type = SyntaxFactory.ParseTypeName($"global::Vetuviem.Core.ICommandBinding<TViewModel>?");
209+
return type;
210+
}
211+
151212
private static TypeSyntax GetBindingTypeSyntax(
152213
IPropertySymbol prop,
153214
string? desiredCommandInterface)
@@ -165,17 +226,6 @@ private static string GetBindingInterfaceName(
165226
IPropertySymbol prop,
166227
string? desiredCommandInterface)
167228
{
168-
if (!string.IsNullOrWhiteSpace(desiredCommandInterface))
169-
{
170-
var propType = prop.Type;
171-
var isCommand = propType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).Equals(desiredCommandInterface, StringComparison.Ordinal)
172-
|| propType.AllInterfaces.Any(interfaceName => interfaceName.GetFullName().Equals(desiredCommandInterface, StringComparison.Ordinal));
173-
if (isCommand)
174-
{
175-
return "ICommandBinding";
176-
}
177-
}
178-
179229
var bindingType = prop.IsReadOnly ? "One" : "OneOrTwo";
180230

181231
return $"I{bindingType}WayBind";

src/Vetuviem.SourceGenerator/Features/ControlBindingModels/ControlBoundControlBindingModelClassGenerator.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ protected override SyntaxList<MemberDeclarationSyntax> ApplyMembers(
3232
string controlClassFullName,
3333
string platformName,
3434
bool makeClassesPublic,
35-
bool includeObsoleteItems)
35+
bool includeObsoleteItems,
36+
string? platformCommandType)
3637
{
3738
return members;
3839
}

0 commit comments

Comments
 (0)