@@ -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