Skip to content

Commit

Permalink
Fix: command binding now against commandsource on control instead of …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
dpvreony authored Jun 9, 2024
1 parent 9447fbb commit 8d98a6a
Show file tree
Hide file tree
Showing 15 changed files with 232 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using ReactiveUI.WPF.SampleApp.ViewModels;
using ReactiveUI.WPF.SampleApp.Views;
Expand All @@ -25,7 +26,7 @@ protected override IEnumerable<IControlBindingModel<QuestionnaireView, Questionn
// launch interaction
yield return new ButtonControlBindingModel<QuestionnaireView, QuestionnaireViewModel>(vw => vw.LaunchInteraction)
{
Command = new CommandBinding<QuestionnaireViewModel>(vm => vm.LaunchInteraction)
BindCommand = new CommandBinding<QuestionnaireViewModel>(vm => vm.LaunchInteraction)
};

// Forename
Expand Down
6 changes: 6 additions & 0 deletions src/Vetuviem.Blazor.SourceGenerator/BlazorPlatformResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public string GetBaseUiElement()
return "global::Microsoft.AspNetCore.Components.IComponent";
}

/// <inheritdoc />
public string? GetCommandSourceInterface()
{
return null;
}

/// <inheritdoc />
public string? GetCommandInterface()
{
Expand Down
15 changes: 7 additions & 8 deletions src/Vetuviem.Core/CommandBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Disposables;
using System.Windows.Input;
using ReactiveUI;
Expand All @@ -15,10 +14,10 @@ namespace Vetuviem.Core
/// Represents a command binding between a control and a viewmodel.
/// </summary>
/// <typeparam name="TViewModel">The type for the viewmodel.</typeparam>
public sealed class CommandBinding<TViewModel> : ICommandBinding<TViewModel, ICommand>
public sealed class CommandBinding<TViewModel> : ICommandBinding<TViewModel>
where TViewModel : class
{
private readonly Expression<Func<TViewModel, ReactiveCommand<Unit, Unit>?>> _viewModelBinding;
private readonly Expression<Func<TViewModel, ICommand?>> _viewModelBinding;
private readonly string? _toEvent;

/// <summary>
Expand All @@ -27,19 +26,19 @@ public sealed class CommandBinding<TViewModel> : ICommandBinding<TViewModel, ICo
/// <param name="viewModelBinding">Expression for the View Model binding.</param>
/// <param name="toEvent">If specified, bind to the specific event instead of the default.</param>
public CommandBinding(
Expression<Func<TViewModel, ReactiveCommand<Unit, Unit>?>> viewModelBinding,
Expression<Func<TViewModel, ICommand?>> viewModelBinding,
string? toEvent = null)
{
_viewModelBinding = viewModelBinding;
_toEvent = toEvent;
}

/// <inheritdoc/>
public void ApplyBinding<TView>(
public void ApplyBinding<TView, TViewProp>(
Action<IDisposable> disposeAction,
TView view,
TViewModel viewModel,
Expression<Func<TView, ICommand>> viewBinding)
Expression<Func<TView, TViewProp>> viewBinding)
where TView : class, IViewFor<TViewModel>
{
if (disposeAction == null)
Expand Down Expand Up @@ -70,11 +69,11 @@ public void ApplyBinding<TView>(
}

/// <inheritdoc/>
public void ApplyBinding<TView>(
public void ApplyBinding<TView, TViewProp>(
CompositeDisposable compositeDisposable,
TView view,
TViewModel viewModel,
Expression<Func<TView, ICommand>> viewBinding)
Expression<Func<TView, TViewProp>> viewBinding)
where TView : class, IViewFor<TViewModel>
{
if (compositeDisposable == null)
Expand Down
9 changes: 5 additions & 4 deletions src/Vetuviem.Core/ICommandBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ namespace Vetuviem.Core
/// Represents a View to View Model Binding for Commands.
/// </summary>
/// <typeparam name="TViewModel">The type for the ViewModel.</typeparam>
/// <typeparam name="TViewProp">The type for the View.</typeparam>
public interface ICommandBinding<TViewModel, TViewProp>
public interface ICommandBinding<TViewModel>
where TViewModel : class
{
/// <summary>
/// Applies a View to View Model Binding.
/// </summary>
/// <typeparam name="TView">The type for the view.</typeparam>
/// <typeparam name="TViewProp">The type for the View Property.</typeparam>
/// <param name="d">The disposable action registration. Used to clean up when bindings fall out of scope.</param>
/// <param name="view">The instance of the View to bind.</param>
/// <param name="viewModel">The instance of the ViewModel to Bind.</param>
/// <param name="viewBinding">Expression of the View Property to Bind to.</param>
void ApplyBinding<TView>(
void ApplyBinding<TView, TViewProp>(
Action<IDisposable> d,
TView view,
TViewModel viewModel,
Expand All @@ -36,11 +36,12 @@ void ApplyBinding<TView>(
/// Applies a View to View Model Binding.
/// </summary>
/// <typeparam name="TView">The type for the view.</typeparam>
/// <typeparam name="TViewProp">The type for the View Property.</typeparam>
/// <param name="compositeDisposable">The disposable action registration. Used to clean up when bindings fall out of scope.</param>
/// <param name="view">The instance of the View to bind.</param>
/// <param name="viewModel">The instance of the ViewModel to Bind.</param>
/// <param name="viewBinding">Expression of the View Property to Bind to.</param>
void ApplyBinding<TView>(
void ApplyBinding<TView, TViewProp>(
CompositeDisposable compositeDisposable,
TView view,
TViewModel viewModel,
Expand Down
5 changes: 3 additions & 2 deletions src/Vetuviem.SourceGenerator/AbstractBaseSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ public void Execute(GeneratorExecutionContext context)
return namespaceDeclaration;
}

var desiredCommandInterface = platformResolver.GetCommandInterface();
var desiredCommandInterface = platformResolver.GetCommandSourceInterface();

var generatorProcessor = new TGeneratorProcessor();

Expand All @@ -247,7 +247,8 @@ public void Execute(GeneratorExecutionContext context)
platformName,
namespaceName,
makeClassesPublic,
includeObsoleteItems);
includeObsoleteItems,
platformResolver.GetCommandInterface());

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public ClassDeclarationSyntax GenerateClass(
string platformName,
string rootNamespace,
bool makeClassesPublic,
bool includeObsoleteItems)
bool includeObsoleteItems,
string? platformCommandType)
{
var typeParameterList = GetTypeParameterListSyntax(namedTypeSymbol);

Expand Down Expand Up @@ -60,7 +61,8 @@ public ClassDeclarationSyntax GenerateClass(
controlClassFullName,
platformName,
makeClassesPublic,
includeObsoleteItems);
includeObsoleteItems,
platformCommandType);

return classDeclaration
.WithModifiers(modifiers)
Expand Down Expand Up @@ -191,7 +193,8 @@ protected abstract SyntaxList<MemberDeclarationSyntax> ApplyMembers(
string controlClassFullName,
string platformName,
bool makeClassesPublic,
bool includeObsoleteItems);
bool includeObsoleteItems,
string? platformCommandType);

Check warning on line 197 in src/Vetuviem.SourceGenerator/Features/ControlBindingModels/AbstractControlBindingModelClassGenerator.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Parameter 'platformCommandType' has no matching param tag in the XML comment for 'AbstractControlBindingModelClassGenerator.ApplyMembers(SyntaxList<MemberDeclarationSyntax>, INamedTypeSymbol, string?, bool, string, string, bool, bool, string?)' (but other parameters do)

Check warning on line 197 in src/Vetuviem.SourceGenerator/Features/ControlBindingModels/AbstractControlBindingModelClassGenerator.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'platformCommandType' has no matching param tag in the XML comment for 'AbstractControlBindingModelClassGenerator.ApplyMembers(SyntaxList<MemberDeclarationSyntax>, INamedTypeSymbol, string?, bool, string, string, bool, bool, string?)' (but other parameters do)

/// <summary>
/// Gets the class name identifier from a named type symbol.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public static SyntaxList<MemberDeclarationSyntax> GetProperties(
INamedTypeSymbol namedTypeSymbol,
string? desiredCommandInterface,
bool makeClassesPublic,
bool includeObsoleteItems)
bool includeObsoleteItems,
string? platformCommandType)

Check warning on line 34 in src/Vetuviem.SourceGenerator/Features/ControlBindingModels/ControlBindingModelPropertyGenerator.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Parameter 'platformCommandType' has no matching param tag in the XML comment for 'ControlBindingModelPropertyGenerator.GetProperties(INamedTypeSymbol, string?, bool, bool, string?)' (but other parameters do)

Check warning on line 34 in src/Vetuviem.SourceGenerator/Features/ControlBindingModels/ControlBindingModelPropertyGenerator.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'platformCommandType' has no matching param tag in the XML comment for 'ControlBindingModelPropertyGenerator.GetProperties(INamedTypeSymbol, string?, bool, bool, string?)' (but other parameters do)
{
if (namedTypeSymbol == null)
{
Expand All @@ -42,8 +43,21 @@ public static SyntaxList<MemberDeclarationSyntax> GetProperties(
.Where(x => x.Kind == SymbolKind.Property)
.ToArray();

var fullName = namedTypeSymbol.GetFullName();

var nodes = new List<MemberDeclarationSyntax>(properties.Length);

if (!string.IsNullOrWhiteSpace(desiredCommandInterface)
&& !string.IsNullOrWhiteSpace(platformCommandType)
&& namedTypeSymbol.Interfaces.Any(interfaceName => interfaceName.GetFullName().Equals(desiredCommandInterface, StringComparison.Ordinal)))
{
var bindCommandPropertyDeclaration = GetBindCommandPropertyDeclaration(
makeClassesPublic,
fullName,
platformCommandType!);
nodes.Add(bindCommandPropertyDeclaration);
}

foreach (var prop in properties)
{
var propertySymbol = prop as IPropertySymbol;
Expand Down Expand Up @@ -74,15 +88,7 @@ public static SyntaxList<MemberDeclarationSyntax> GetProperties(
continue;
}

var accessorList = new[]
{
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
};

var fullName = namedTypeSymbol.GetFullName();
var accessorList = GetAccessorDeclarationSyntaxes();

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

private static AccessorDeclarationSyntax[] GetAccessorDeclarationSyntaxes()
{
var accessorList = new[]
{
SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
};
return accessorList;
}

private static MemberDeclarationSyntax GetBindCommandPropertyDeclaration(
bool makeClassesPublic,
string fullName,
string platformCommandType)
{
var accessorList = GetAccessorDeclarationSyntaxes();

var summary = XmlSyntaxFactory.GenerateSummarySeeAlsoComment(
"Gets or sets the command binding logic for {0}",
fullName);

return GetBindCommandPropertyDeclaration(
accessorList,
summary,
makeClassesPublic,
platformCommandType);
}

private static bool ReplacesBaseProperty(
IPropertySymbol propertySymbol,
INamedTypeSymbol namedTypeSymbol)
Expand Down Expand Up @@ -128,6 +164,25 @@ private static bool ReplacesBaseProperty(
return false;
}

private static PropertyDeclarationSyntax GetBindCommandPropertyDeclaration(
AccessorDeclarationSyntax[] accessorList,
IEnumerable<SyntaxTrivia> summary,
bool makeClassesPublic,
string platformCommandType)
{
TypeSyntax type = GetCommandBindingTypeSyntax(platformCommandType);

var result = SyntaxFactory.PropertyDeclaration(
type,
"BindCommand")
.AddModifiers(SyntaxFactory.Token(makeClassesPublic ? SyntaxKind.PublicKeyword : SyntaxKind.InternalKeyword))
.WithAccessorList(
SyntaxFactory.AccessorList(SyntaxFactory.List(accessorList)))
.WithLeadingTrivia(summary);

return result;
}

private static PropertyDeclarationSyntax GetPropertyDeclaration(
IPropertySymbol prop,
AccessorDeclarationSyntax[] accessorList,
Expand All @@ -148,6 +203,12 @@ private static PropertyDeclarationSyntax GetPropertyDeclaration(
return result;
}

private static TypeSyntax GetCommandBindingTypeSyntax(string platformCommandType)
{
var type = SyntaxFactory.ParseTypeName($"global::Vetuviem.Core.ICommandBinding<TViewModel>?");
return type;
}

private static TypeSyntax GetBindingTypeSyntax(
IPropertySymbol prop,
string? desiredCommandInterface)
Expand All @@ -165,17 +226,6 @@ private static string GetBindingInterfaceName(
IPropertySymbol prop,
string? desiredCommandInterface)
{
if (!string.IsNullOrWhiteSpace(desiredCommandInterface))
{
var propType = prop.Type;
var isCommand = propType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).Equals(desiredCommandInterface, StringComparison.Ordinal)
|| propType.AllInterfaces.Any(interfaceName => interfaceName.GetFullName().Equals(desiredCommandInterface, StringComparison.Ordinal));
if (isCommand)
{
return "ICommandBinding";
}
}

var bindingType = prop.IsReadOnly ? "One" : "OneOrTwo";

return $"I{bindingType}WayBind";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ protected override SyntaxList<MemberDeclarationSyntax> ApplyMembers(
string controlClassFullName,
string platformName,
bool makeClassesPublic,
bool includeObsoleteItems)
bool includeObsoleteItems,
string? platformCommandType)
{
return members;
}
Expand Down
Loading

0 comments on commit 8d98a6a

Please sign in to comment.