ApplicationRunnableExtensions.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. namespace Terminal.Gui.App;
  2. /// <summary>
  3. /// Extension methods for <see cref="IApplication"/> that enable running any <see cref="View"/> as a runnable session.
  4. /// </summary>
  5. /// <remarks>
  6. /// These extensions provide convenience methods for wrapping views in <see cref="RunnableWrapper{TView, TResult}"/>
  7. /// and running them in a single call, similar to how <see cref="IApplication.Run(Func{Exception, bool}, string)"/> works.
  8. /// </remarks>
  9. public static class ApplicationRunnableExtensions
  10. {
  11. /// <summary>
  12. /// Runs any View as a runnable session, extracting a typed result via a function.
  13. /// </summary>
  14. /// <typeparam name="TView">The type of view to run.</typeparam>
  15. /// <typeparam name="TResult">The type of result data to extract.</typeparam>
  16. /// <param name="app">The application instance. Cannot be null.</param>
  17. /// <param name="view">The view to run as a blocking session. Cannot be null.</param>
  18. /// <param name="resultExtractor">
  19. /// Function that extracts the result from the view when stopping.
  20. /// Called automatically when the runnable session ends.
  21. /// </param>
  22. /// <param name="errorHandler">Optional handler for unhandled exceptions during the session.</param>
  23. /// <returns>The extracted result, or null if the session was canceled.</returns>
  24. /// <exception cref="ArgumentNullException">
  25. /// Thrown if <paramref name="app"/>, <paramref name="view"/>, or <paramref name="resultExtractor"/> is null.
  26. /// </exception>
  27. /// <remarks>
  28. /// <para>
  29. /// This method wraps the view in a <see cref="RunnableWrapper{TView, TResult}"/>, runs it as a blocking
  30. /// session, and returns the extracted result. The wrapper is NOT disposed automatically;
  31. /// the caller is responsible for disposal.
  32. /// </para>
  33. /// <para>
  34. /// The result is extracted before the view is disposed, ensuring all data is still accessible.
  35. /// </para>
  36. /// </remarks>
  37. /// <example>
  38. /// <code>
  39. /// var app = Application.Create();
  40. /// app.Init();
  41. ///
  42. /// // Run a TextField and get the entered text
  43. /// var text = app.RunView(
  44. /// new TextField { Width = 40 },
  45. /// tf =&gt; tf.Text);
  46. /// Console.WriteLine($"You entered: {text}");
  47. ///
  48. /// // Run a ColorPicker and get the selected color
  49. /// var color = app.RunView(
  50. /// new ColorPicker(),
  51. /// cp =&gt; cp.SelectedColor);
  52. /// Console.WriteLine($"Selected color: {color}");
  53. ///
  54. /// // Run a FlagSelector and get the selected flags
  55. /// var flags = app.RunView(
  56. /// new FlagSelector&lt;SelectorStyles&gt;(),
  57. /// fs =&gt; fs.Value);
  58. /// Console.WriteLine($"Selected styles: {flags}");
  59. ///
  60. /// app.Shutdown();
  61. /// </code>
  62. /// </example>
  63. public static TResult? RunView<TView, TResult> (
  64. this IApplication app,
  65. TView view,
  66. Func<TView, TResult?> resultExtractor,
  67. Func<Exception, bool>? errorHandler = null)
  68. where TView : View
  69. {
  70. if (app is null)
  71. {
  72. throw new ArgumentNullException (nameof (app));
  73. }
  74. if (view is null)
  75. {
  76. throw new ArgumentNullException (nameof (view));
  77. }
  78. if (resultExtractor is null)
  79. {
  80. throw new ArgumentNullException (nameof (resultExtractor));
  81. }
  82. var wrapper = new RunnableWrapper<TView, TResult> { WrappedView = view };
  83. // Subscribe to IsRunningChanging to extract result when stopping
  84. wrapper.IsRunningChanging += (s, e) =>
  85. {
  86. if (!e.NewValue) // Stopping
  87. {
  88. wrapper.Result = resultExtractor (view);
  89. }
  90. };
  91. app.Run (wrapper, errorHandler);
  92. return wrapper.Result;
  93. }
  94. /// <summary>
  95. /// Runs any View as a runnable session without result extraction.
  96. /// </summary>
  97. /// <typeparam name="TView">The type of view to run.</typeparam>
  98. /// <param name="app">The application instance. Cannot be null.</param>
  99. /// <param name="view">The view to run as a blocking session. Cannot be null.</param>
  100. /// <param name="errorHandler">Optional handler for unhandled exceptions during the session.</param>
  101. /// <returns>The view that was run, allowing access to its state after the session ends.</returns>
  102. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> or <paramref name="view"/> is null.</exception>
  103. /// <remarks>
  104. /// <para>
  105. /// This method wraps the view in a <see cref="RunnableWrapper{TView, Object}"/> and runs it as a blocking
  106. /// session. The wrapper is NOT disposed automatically; the caller is responsible for disposal.
  107. /// </para>
  108. /// <para>
  109. /// Use this overload when you don't need automatic result extraction, but still want the view
  110. /// to run as a blocking session. Access the view's properties directly after running.
  111. /// </para>
  112. /// </remarks>
  113. /// <example>
  114. /// <code>
  115. /// var app = Application.Create();
  116. /// app.Init();
  117. ///
  118. /// // Run a ColorPicker without automatic result extraction
  119. /// var colorPicker = new ColorPicker();
  120. /// app.RunView(colorPicker);
  121. ///
  122. /// // Access the view's state directly
  123. /// Console.WriteLine($"Selected: {colorPicker.SelectedColor}");
  124. ///
  125. /// app.Shutdown();
  126. /// </code>
  127. /// </example>
  128. public static TView RunView<TView> (
  129. this IApplication app,
  130. TView view,
  131. Func<Exception, bool>? errorHandler = null)
  132. where TView : View
  133. {
  134. if (app is null)
  135. {
  136. throw new ArgumentNullException (nameof (app));
  137. }
  138. if (view is null)
  139. {
  140. throw new ArgumentNullException (nameof (view));
  141. }
  142. var wrapper = new RunnableWrapper<TView, object> { WrappedView = view };
  143. app.Run (wrapper, errorHandler);
  144. return view;
  145. }
  146. }