Skip to content

Commit 81a9465

Browse files
committed
Add execution guards to the async version to prevent quick click errors.
1 parent d17fe05 commit 81a9465

File tree

2 files changed

+42
-29
lines changed

2 files changed

+42
-29
lines changed

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.8.2</Version>
12+
<Version>0.9.8.3</Version>
1313
<Authors>Derek Gooding</Authors>
1414
<Company>Derek Gooding</Company>
1515
<Description>

ViewModelGenerator/ViewModelGenerator.cs

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ public partial class {className} : BaseViewModel
8181
viewModelBuilder.AppendLine($" {onChange}();");
8282
viewModelBuilder.AppendLine(" }}");
8383
}
84-
//viewModelBuilder.AppendLine(" }"); // <-- FIX: close property
8584

8685
foreach (var method in commandMethods)
8786
{
@@ -90,17 +89,16 @@ public partial class {className} : BaseViewModel
9089
viewModelBuilder.AppendLine($" private {commandClassName}? _{commandFieldName} {{ get; set; }}");
9190
viewModelBuilder.AppendLine($" public {commandClassName} {commandFieldName} => _{commandFieldName} ??= new(this);");
9291

93-
// Check for AcceptParameter property on CommandAttribute
9492
var acceptParameter = method.GetAttributes()
9593
.FirstOrDefault(attr => attr.AttributeClass?.Name == "CommandAttribute")?
9694
.NamedArguments.FirstOrDefault(arg => arg.Key == "AcceptParameter").Value.Value as bool? ?? false;
9795

98-
// Check if method has a single parameter
9996
var hasParameter = method.Parameters.Length == 1;
10097
var paramType = hasParameter ? method.Parameters[0].Type.ToDisplayString() : null;
10198

102-
// Generate command class per method
10399
var isAsync = IsAsyncTask(method);
100+
var canExecute = GetCanExecuteMethodName(method);
101+
104102
var commandBuilder = new StringBuilder(
105103
$@"// <auto-generated />
106104
#nullable enable
@@ -111,15 +109,35 @@ namespace {namespaceName}
111109
public sealed class {commandClassName} : BaseCommand
112110
{{
113111
private readonly {className} vm;
112+
private bool _isExecuting;
113+
");
114+
if (isAsync)
115+
commandBuilder.AppendLine(" private readonly SemaphoreSlim _executeLock = new(1, 1);");
114116

115-
public {commandClassName}({className} vm)
116-
{{
117-
this.vm = vm;
118-
}}
117+
commandBuilder.AppendLine();
118+
commandBuilder.AppendLine($" public {commandClassName}({className} vm)");
119+
commandBuilder.AppendLine(" {");
120+
commandBuilder.AppendLine(" this.vm = vm;");
121+
commandBuilder.AppendLine(" }");
122+
commandBuilder.AppendLine();
119123

120-
public override {(isAsync ? "async void" : "void")} Execute(object? parameter)
121-
{{
122-
");
124+
if (!string.IsNullOrWhiteSpace(canExecute))
125+
commandBuilder.AppendLine($" public override bool CanExecute(object? parameter) => !_isExecuting && vm.{canExecute}();");
126+
else
127+
commandBuilder.AppendLine(" public override bool CanExecute(object? parameter) => !_isExecuting;");
128+
129+
commandBuilder.AppendLine();
130+
131+
// Execute
132+
commandBuilder.AppendLine($" public override {(isAsync ? "async void" : "void")} Execute(object? parameter)");
133+
commandBuilder.AppendLine(" {");
134+
commandBuilder.AppendLine(" if (_isExecuting) return;");
135+
136+
if (isAsync)
137+
commandBuilder.AppendLine(" if (!_executeLock.Wait(0)) return;");
138+
139+
commandBuilder.AppendLine(" _isExecuting = true;");
140+
commandBuilder.AppendLine(" RaiseCanExecuteChanged();");
123141
commandBuilder.AppendLine(" try");
124142
commandBuilder.AppendLine(" {");
125143

@@ -143,13 +161,19 @@ public sealed class {commandClassName} : BaseCommand
143161
commandBuilder.AppendLine(" {");
144162
commandBuilder.AppendLine(" vm.OnCommandException(ex);");
145163
commandBuilder.AppendLine(" }");
164+
commandBuilder.AppendLine(" finally");
165+
commandBuilder.AppendLine(" {");
166+
commandBuilder.AppendLine(" _isExecuting = false;");
167+
168+
if (isAsync)
169+
commandBuilder.AppendLine(" _executeLock.Release();");
146170

171+
commandBuilder.AppendLine(" RaiseCanExecuteChanged();");
172+
commandBuilder.AppendLine(" }");
147173
commandBuilder.AppendLine(" }");
148-
var canExecute = GetCanExecuteMethodName(method);
149-
if (!string.IsNullOrWhiteSpace(canExecute))
150-
commandBuilder.AppendLine($" public override bool CanExecute(object? parameter) => vm.{canExecute}();");
151174
commandBuilder.AppendLine(" }");
152175
commandBuilder.AppendLine("}");
176+
153177
spc.AddSource($"{commandClassName}.g.cs", SourceText.From(commandBuilder.ToString(), Encoding.UTF8));
154178
}
155179

@@ -177,9 +201,7 @@ private static string ToPascal(string name)
177201
foreach (var namedArg in attr.NamedArguments)
178202
{
179203
if (namedArg.Key == "CanExecuteMethodName")
180-
{
181204
return namedArg.Value.Value?.ToString();
182-
}
183205
}
184206
}
185207
}
@@ -195,9 +217,7 @@ private static string ToPascal(string name)
195217
foreach (var namedArg in attr.NamedArguments)
196218
{
197219
if (namedArg.Key == "OnChangeMethodName")
198-
{
199220
return namedArg.Value.Value?.ToString();
200-
}
201221
}
202222
}
203223
}
@@ -207,14 +227,7 @@ private static string ToPascal(string name)
207227
private static bool IsAsyncTask(IMethodSymbol method)
208228
{
209229
var returnType = method.ReturnType;
210-
211-
if (returnType is INamedTypeSymbol named)
212-
{
213-
if (named.Name == "Task" &&
214-
named.ContainingNamespace.ToDisplayString() == "System.Threading.Tasks")
215-
return true;
216-
}
217-
218-
return false;
230+
return returnType is INamedTypeSymbol named && named.Name == "Task" &&
231+
named.ContainingNamespace.ToDisplayString() == "System.Threading.Tasks";
219232
}
220-
}
233+
}

0 commit comments

Comments
 (0)