RelayCommandAttribute.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Windows.Input;
  6. namespace CommunityToolkit.Mvvm.Input;
  7. /// <summary>
  8. /// An attribute that can be used to automatically generate <see cref="ICommand"/> properties from declared methods. When this attribute
  9. /// is used to decorate a method, a generator will create a command property with the corresponding <see cref="IRelayCommand"/> interface
  10. /// depending on the signature of the method. If an invalid method signature is used, the generator will report an error.
  11. /// <para>
  12. /// In order to use this attribute, the containing type doesn't need to implement any interfaces. The generated properties will be lazily
  13. /// assigned but their value will never change, so there is no need to support property change notifications or other additional functionality.
  14. /// </para>
  15. /// <para>
  16. /// This attribute can be used as follows:
  17. /// <code>
  18. /// partial class MyViewModel
  19. /// {
  20. /// [RelayCommand]
  21. /// private void GreetUser(User? user)
  22. /// {
  23. /// Console.WriteLine($"Hello {user.Name}!");
  24. /// }
  25. /// }
  26. /// </code>
  27. /// And with this, code analogous to this will be generated:
  28. /// <code>
  29. /// partial class MyViewModel
  30. /// {
  31. /// private RelayCommand? greetUserCommand;
  32. ///
  33. /// public IRelayCommand GreetUserCommand => greetUserCommand ??= new RelayCommand(GreetUser);
  34. /// }
  35. /// </code>
  36. /// </para>
  37. /// <para>
  38. /// The following signatures are supported for annotated methods:
  39. /// <code>
  40. /// void Method();
  41. /// </code>
  42. /// Will generate an <see cref="IRelayCommand"/> property (using a <see cref="RelayCommand"/> instance).
  43. /// <code>
  44. /// void Method(T?);
  45. /// </code>
  46. /// Will generate an <see cref="IRelayCommand{T}"/> property (using a <see cref="RelayCommand{T}"/> instance).
  47. /// <code>
  48. /// Task Method();
  49. /// Task Method(CancellationToken);
  50. /// Task&lt;T&gt; Method();
  51. /// Task&lt;T&gt; Method(CancellationToken);
  52. /// </code>
  53. /// Will both generate an <see cref="IAsyncRelayCommand"/> property (using an <see cref="AsyncRelayCommand{T}"/> instance).
  54. /// <code>
  55. /// Task Method(T?);
  56. /// Task Method(T?, CancellationToken);
  57. /// Task&lt;T&gt; Method(T?);
  58. /// Task&lt;T&gt; Method(T?, CancellationToken);
  59. /// </code>
  60. /// Will both generate an <see cref="IAsyncRelayCommand{T}"/> property (using an <see cref="AsyncRelayCommand{T}"/> instance).
  61. /// </para>
  62. /// </summary>
  63. [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
  64. public sealed class RelayCommandAttribute : Attribute
  65. {
  66. /// <summary>
  67. /// Gets or sets the name of the property or method that will be invoked to check whether the
  68. /// generated command can be executed at any given time. The referenced member needs to return
  69. /// a <see cref="bool"/> value, and has to have a signature compatible with the target command.
  70. /// </summary>
  71. public string? CanExecute { get; init; }
  72. /// <summary>
  73. /// Gets or sets a value indicating whether or not to allow concurrent executions for an asynchronous command.
  74. /// <para>
  75. /// When set for an attribute used on a method that would result in an <see cref="AsyncRelayCommand"/> or an
  76. /// <see cref="AsyncRelayCommand{T}"/> property to be generated, this will modify the behavior of these commands
  77. /// when an execution is invoked while a previous one is still running. It is the same as creating an instance of
  78. /// these command types with a constructor such as <see cref="AsyncRelayCommand(Func{System.Threading.Tasks.Task}, AsyncRelayCommandOptions)"/>
  79. /// and using the <see cref="AsyncRelayCommandOptions.AllowConcurrentExecutions"/> value.
  80. /// </para>
  81. /// </summary>
  82. /// <remarks>Using this property is not valid if the target command doesn't map to an asynchronous command.</remarks>
  83. public bool AllowConcurrentExecutions { get; init; }
  84. /// <summary>
  85. /// Gets or sets a value indicating whether or not to exceptions should be propagated to <see cref="System.Threading.Tasks.TaskScheduler.UnobservedTaskException"/>.
  86. /// <para>
  87. /// When set for an attribute used on a method that would result in an <see cref="AsyncRelayCommand"/> or an
  88. /// <see cref="AsyncRelayCommand{T}"/> property to be generated, this will modify the behavior of these commands
  89. /// in case an exception is thrown by the underlying operation. It is the same as creating an instance of
  90. /// these command types with a constructor such as <see cref="AsyncRelayCommand(Func{System.Threading.Tasks.Task}, AsyncRelayCommandOptions)"/>
  91. /// and using the <see cref="AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler"/> value.
  92. /// </para>
  93. /// </summary>
  94. /// <remarks>Using this property is not valid if the target command doesn't map to an asynchronous command.</remarks>
  95. public bool FlowExceptionsToTaskScheduler { get; init; }
  96. /// <summary>
  97. /// Gets or sets a value indicating whether a cancel command should also be generated for an asynchronous command.
  98. /// <para>
  99. /// When set to <see langword="true"/>, this additional code will be generated:
  100. /// <code>
  101. /// partial class MyViewModel
  102. /// {
  103. /// private ICommand? loginUserCancelCommand;
  104. ///
  105. /// public ICommand LoginUserCancelCommand => loginUserCancelCommand ??= LoginUserCommand.CreateCancelCommand();
  106. /// }
  107. /// </code>
  108. /// Where <c>LoginUserCommand</c> is an <see cref="IAsyncRelayCommand"/> defined in the class (or generated by this attribute as well).
  109. /// </para>
  110. /// </summary>
  111. /// <remarks>Using this property is not valid if the target command doesn't map to a cancellable asynchronous command.</remarks>
  112. public bool IncludeCancelCommand { get; init; }
  113. }