Skip to content

Commit c05cf26

Browse files
committed
Add functionality for commands to pass parameter object
1 parent 4a59856 commit c05cf26

File tree

3 files changed

+72
-50
lines changed

3 files changed

+72
-50
lines changed

SimpleViewModel/CommandAttribute.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/// <remarks>
99
/// The generated command class is named "Command_{MethodName}" and is exposed as a property
1010
/// named "{MethodName}Command" on the view model. Commands are lazily initialized when first accessed.
11-
/// The target method must be public and parameterless.
11+
/// The target method must be public and parameterless by default, or can accept a single parameter if AcceptParameter is true.
1212
/// </remarks>
1313
/// <example>
1414
/// <code>
@@ -27,6 +27,12 @@
2727
/// // Delete implementation
2828
/// }
2929
///
30+
/// [Command(AcceptParameter = true)]
31+
/// public void DeleteWithParam(object? param)
32+
/// {
33+
/// // Use param
34+
/// }
35+
///
3036
/// public bool CanDelete()
3137
/// {
3238
/// return _selectedItem != null;
@@ -45,16 +51,12 @@ public sealed class CommandAttribute : Attribute
4551
/// The name of the method that returns <c>true</c> if the command can execute; otherwise, <c>false</c>.
4652
/// If <c>null</c>, the command can always execute.
4753
/// </value>
48-
/// <example>
49-
/// <code>
50-
/// [Command(CanExecuteMethodName = nameof(CanSave))]
51-
/// public void Save() { /* implementation */ }
52-
///
53-
/// public bool CanSave()
54-
/// {
55-
/// return !string.IsNullOrEmpty(_title);
56-
/// }
57-
/// </code>
58-
/// </example>
5954
public string? CanExecuteMethodName { get; init; }
55+
56+
/// <summary>
57+
/// Gets or sets whether the command should pass the XAML parameter to the method.
58+
/// If true, the method must accept a single parameter of type object? (or compatible type).
59+
/// Default is false (parameterless method).
60+
/// </summary>
61+
public bool AcceptParameter { get; init; } = false;
6062
}

SimpleViewModel/SimpleViewModel.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1111
<PackageId>SimpleViewModel</PackageId>
12-
<Version>0.9.7.1</Version>
12+
<Version>0.9.7.2</Version>
1313
<Authors>Derek Gooding</Authors>
1414
<Company>Derek Gooding</Company>
1515
<Description>

ViewModelGenerator/ViewModelGenerator.cs

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,32 +30,32 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
3030

3131
context.RegisterSourceOutput(compilationAndClasses, static (spc, source) =>
3232
{
33-
var (compilation, classNodes) = source;
33+
var (compilation, classNodes) = source;
3434

35-
foreach (var classNode in classNodes)
36-
{
37-
var model = compilation.GetSemanticModel(classNode.SyntaxTree);
38-
if (model.GetDeclaredSymbol(classNode) is not INamedTypeSymbol symbol) continue;
35+
foreach (var classNode in classNodes)
36+
{
37+
var model = compilation.GetSemanticModel(classNode.SyntaxTree);
38+
if (model.GetDeclaredSymbol(classNode) is not INamedTypeSymbol symbol) continue;
3939

40-
if (!symbol.GetAttributes().Any(attr => attr.AttributeClass?.Name == "ViewModelAttribute")) continue;
40+
if (!symbol.GetAttributes().Any(attr => attr.AttributeClass?.Name == "ViewModelAttribute")) continue;
4141

42-
var className = symbol.Name;
43-
var generatedName = $"{className}";
44-
var namespaceName = symbol.ContainingNamespace.ToDisplayString();
42+
var className = symbol.Name;
43+
var generatedName = $"{className}";
44+
var namespaceName = symbol.ContainingNamespace.ToDisplayString();
4545

46-
var bindFields = symbol.GetMembers()
47-
.OfType<IFieldSymbol>()
48-
.Where(f => f.GetAttributes().Any(attr => attr.AttributeClass?.Name == "BindAttribute"))
49-
.ToList();
46+
var bindFields = symbol.GetMembers()
47+
.OfType<IFieldSymbol>()
48+
.Where(f => f.GetAttributes().Any(attr => attr.AttributeClass?.Name == "BindAttribute"))
49+
.ToList();
5050

51-
var commandMethods = symbol.GetMembers()
52-
.OfType<IMethodSymbol>()
53-
.Where(m => m.MethodKind == MethodKind.Ordinary &&
54-
m.GetAttributes().Any(attr => attr.AttributeClass?.Name == "CommandAttribute"))
55-
.ToList();
51+
var commandMethods = symbol.GetMembers()
52+
.OfType<IMethodSymbol>()
53+
.Where(m => m.MethodKind == MethodKind.Ordinary &&
54+
m.GetAttributes().Any(attr => attr.AttributeClass?.Name == "CommandAttribute"))
55+
.ToList();
5656

57-
// Generate ViewModel partial class
58-
var viewModelBuilder = new StringBuilder(
57+
// Generate ViewModel partial class
58+
var viewModelBuilder = new StringBuilder(
5959
$@"// <auto-generated />
6060
#nullable enable
6161
using SimpleViewModel.BaseClasses;
@@ -65,29 +65,39 @@ namespace {namespaceName}
6565
public partial class {className} : BaseViewModel
6666
{{
6767
");
68-
foreach (var field in bindFields)
69-
{
70-
var fieldType = field.Type.ToDisplayString();
71-
var fieldName = ToPascal(field.Name);
68+
foreach (var field in bindFields)
69+
{
70+
var fieldType = field.Type.ToDisplayString();
71+
var fieldName = ToPascal(field.Name);
7272

73-
viewModelBuilder.AppendLine(
73+
viewModelBuilder.AppendLine(
7474
$@" public {fieldType} {fieldName} {{ get => {field.Name};
7575
set
7676
{{
7777
SetProperty(ref {field.Name}, value);");
7878

79-
var onChange = GetOnChangeMethodName(field);
79+
var onChange = GetOnChangeMethodName(field);
8080
if (!string.IsNullOrWhiteSpace(onChange))
8181
viewModelBuilder.AppendLine($" {onChange}();");
82-
viewModelBuilder.AppendLine(" }}");
83-
}
82+
viewModelBuilder.AppendLine(" }}");
83+
}
84+
viewModelBuilder.AppendLine(" }"); // <-- FIX: close property
8485

85-
foreach (var method in commandMethods)
86-
{
87-
var commandClassName = $"Command_{method.Name}";
88-
var commandFieldName = $"{ToPascal(method.Name)}Command";
89-
viewModelBuilder.AppendLine($" private {commandClassName}? _{commandFieldName} {{ get; set; }}");
90-
viewModelBuilder.AppendLine($" public {commandClassName} {commandFieldName} => _{commandFieldName} ??= new(this);");
86+
foreach (var method in commandMethods)
87+
{
88+
var commandClassName = $"Command_{method.Name}";
89+
var commandFieldName = $"{ToPascal(method.Name)}Command";
90+
viewModelBuilder.AppendLine($" private {commandClassName}? _{commandFieldName} {{ get; set; }}");
91+
viewModelBuilder.AppendLine($" public {commandClassName} {commandFieldName} => _{commandFieldName} ??= new(this);");
92+
93+
// Check for AcceptParameter property on CommandAttribute
94+
var acceptParameter = method.GetAttributes()
95+
.FirstOrDefault(attr => attr.AttributeClass?.Name == "CommandAttribute")?
96+
.NamedArguments.FirstOrDefault(arg => arg.Key == "AcceptParameter").Value.Value as bool? ?? false;
97+
98+
// Check if method has a single parameter
99+
var hasParameter = method.Parameters.Length == 1;
100+
var paramType = hasParameter ? method.Parameters[0].Type.ToDisplayString() : null;
91101

92102
// Generate command class per method
93103
var commandBuilder = new StringBuilder(
@@ -108,11 +118,21 @@ public sealed class {commandClassName} : BaseCommand
108118
109119
public override void Execute(object? parameter)
110120
{{
111-
vm.{method.Name}();
112-
}}
113121
");
122+
if (acceptParameter && hasParameter)
123+
{
124+
// Pass parameter to method
125+
commandBuilder.AppendLine($" vm.{method.Name}(({paramType})parameter!);");
126+
}
127+
else
128+
{
129+
// Call parameterless method
130+
commandBuilder.AppendLine($" vm.{method.Name}();");
131+
}
132+
133+
commandBuilder.AppendLine(" }");
114134
var canExecute = GetCanExecuteMethodName(method);
115-
if(!string.IsNullOrWhiteSpace(canExecute))
135+
if (!string.IsNullOrWhiteSpace(canExecute))
116136
commandBuilder.AppendLine($" public override bool CanExecute(object? parameter) => vm.{canExecute}();");
117137
commandBuilder.AppendLine(" }");
118138
commandBuilder.AppendLine("}");

0 commit comments

Comments
 (0)