2
0

MessageBox.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. namespace Terminal.Gui.Views;
  2. /// <summary>
  3. /// Displays a modal message box with a title, message, and buttons. Returns the index of the selected button,
  4. /// or <see langword="null"/> if the user cancels with <see cref="Application.QuitKey"/>.
  5. /// </summary>
  6. /// <remarks>
  7. /// <para>
  8. /// MessageBox provides static methods for displaying modal dialogs with customizable buttons and messages.
  9. /// All methods return <see langword="int?"/> where the value is the 0-based index of the button pressed,
  10. /// or <see langword="null"/> if the user pressed <see cref="Application.QuitKey"/> (typically Esc).
  11. /// </para>
  12. /// <para>
  13. /// <see cref="Query(IApplication?, string, string, string[])"/> uses the default Dialog color scheme.
  14. /// <see cref="ErrorQuery(IApplication?, string, string, string[])"/> uses the Error color scheme.
  15. /// </para>
  16. /// <para>
  17. /// <b>Important:</b> All MessageBox methods require an <see cref="IApplication"/> instance to be passed.
  18. /// This enables proper modal dialog management and respects the application's lifecycle. Pass your
  19. /// application instance (from <see cref="Application.Create()"/>) or use the legacy
  20. /// <see cref="ApplicationImpl.Instance"/> if using the static Application pattern.
  21. /// </para>
  22. /// <para>
  23. /// Example using instance-based pattern:
  24. /// <code>
  25. /// IApplication app = Application.Create();
  26. /// app.Init();
  27. ///
  28. /// int? result = MessageBox.Query(app, "Quit Demo", "Are you sure you want to quit?", "Yes", "No");
  29. /// if (result == 0) // User clicked "Yes"
  30. /// app.RequestStop();
  31. /// else if (result == null) // User pressed Esc
  32. /// // Handle cancellation
  33. ///
  34. /// app.Shutdown();
  35. /// </code>
  36. /// </para>
  37. /// <para>
  38. /// Example using legacy static pattern:
  39. /// <code>
  40. /// Application.Init();
  41. ///
  42. /// int? result = MessageBox.Query(ApplicationImpl.Instance, "Quit Demo", "Are you sure?", "Yes", "No");
  43. /// if (result == 0) // User clicked "Yes"
  44. /// Application.RequestStop();
  45. ///
  46. /// Application.Shutdown();
  47. /// </code>
  48. /// </para>
  49. /// <para>
  50. /// The <see cref="Clicked"/> property provides a global variable alternative for web-based consoles
  51. /// without SynchronizationContext. However, using the return value is preferred as it's more thread-safe
  52. /// and follows modern async patterns.
  53. /// </para>
  54. /// </remarks>
  55. public static class MessageBox
  56. {
  57. private static LineStyle _defaultBorderStyle = LineStyle.Heavy; // Resources/config.json overrides
  58. private static Alignment _defaultButtonAlignment = Alignment.Center; // Resources/config.json overrides
  59. private static int _defaultMinimumWidth = 0; // Resources/config.json overrides
  60. private static int _defaultMinimumHeight = 0; // Resources/config.json overrides
  61. /// <summary>
  62. /// Defines the default border styling for <see cref="MessageBox"/>. Can be configured via
  63. /// <see cref="ConfigurationManager"/>.
  64. /// </summary>
  65. [ConfigurationProperty (Scope = typeof (ThemeScope))]
  66. public static LineStyle DefaultBorderStyle
  67. {
  68. get => _defaultBorderStyle;
  69. set => _defaultBorderStyle = value;
  70. }
  71. /// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
  72. /// <remarks>This property can be set in a Theme.</remarks>
  73. [ConfigurationProperty (Scope = typeof (ThemeScope))]
  74. public static Alignment DefaultButtonAlignment
  75. {
  76. get => _defaultButtonAlignment;
  77. set => _defaultButtonAlignment = value;
  78. }
  79. /// <summary>
  80. /// Defines the default minimum MessageBox width, as a percentage of the screen width. Can be configured via
  81. /// <see cref="ConfigurationManager"/>.
  82. /// </summary>
  83. [ConfigurationProperty (Scope = typeof (ThemeScope))]
  84. public static int DefaultMinimumWidth
  85. {
  86. get => _defaultMinimumWidth;
  87. set => _defaultMinimumWidth = value;
  88. }
  89. /// <summary>
  90. /// Defines the default minimum Dialog height, as a percentage of the screen width. Can be configured via
  91. /// <see cref="ConfigurationManager"/>.
  92. /// </summary>
  93. [ConfigurationProperty (Scope = typeof (ThemeScope))]
  94. public static int DefaultMinimumHeight
  95. {
  96. get => _defaultMinimumHeight;
  97. set => _defaultMinimumHeight = value;
  98. }
  99. /// <summary>
  100. /// The index of the selected button, or <see langword="null"/> if the user pressed <see cref="Application.QuitKey"/>.
  101. /// </summary>
  102. /// <remarks>
  103. /// <para>
  104. /// Warning: This is a global variable and should be used with caution. It is not thread safe.
  105. /// </para>
  106. /// <para>
  107. /// <b>Deprecated:</b> This property is maintained for backward compatibility. The MessageBox methods
  108. /// now return the button index directly, and <see cref="Dialog.Result"/> provides a cleaner,
  109. /// non-global alternative for custom dialog implementations.
  110. /// </para>
  111. /// </remarks>
  112. public static int? Clicked { get; private set; }
  113. /// <summary>
  114. /// Displays an error <see cref="MessageBox"/> with fixed dimensions.
  115. /// </summary>
  116. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  117. /// <param name="width">Width for the MessageBox.</param>
  118. /// <param name="height">Height for the MessageBox.</param>
  119. /// <param name="title">Title for the MessageBox.</param>
  120. /// <param name="message">Message to display. May contain multiple lines and will be word-wrapped.</param>
  121. /// <param name="buttons">Array of button labels.</param>
  122. /// <returns>
  123. /// The index of the selected button, or <see langword="null"/> if the user pressed
  124. /// <see cref="Application.QuitKey"/>.
  125. /// </returns>
  126. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  127. /// <remarks>
  128. /// Consider using <see cref="ErrorQuery(IApplication?, string, string, string[])"/> which automatically sizes the
  129. /// MessageBox.
  130. /// </remarks>
  131. public static int? ErrorQuery (
  132. IApplication? app,
  133. int width,
  134. int height,
  135. string title,
  136. string message,
  137. params string [] buttons
  138. )
  139. {
  140. return QueryFull (
  141. app,
  142. true,
  143. width,
  144. height,
  145. title,
  146. message,
  147. 0,
  148. true,
  149. buttons);
  150. }
  151. /// <summary>
  152. /// Displays an auto-sized error <see cref="MessageBox"/>.
  153. /// </summary>
  154. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  155. /// <param name="title">Title for the MessageBox.</param>
  156. /// <param name="message">Message to display. May contain multiple lines and will be word-wrapped.</param>
  157. /// <param name="buttons">Array of button labels.</param>
  158. /// <returns>
  159. /// The index of the selected button, or <see langword="null"/> if the user pressed
  160. /// <see cref="Application.QuitKey"/>.
  161. /// </returns>
  162. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  163. /// <remarks>
  164. /// The MessageBox is centered and auto-sized based on title, message, and buttons.
  165. /// </remarks>
  166. public static int? ErrorQuery (IApplication? app, string title, string message, params string [] buttons)
  167. {
  168. return QueryFull (
  169. app,
  170. true,
  171. 0,
  172. 0,
  173. title,
  174. message,
  175. 0,
  176. true,
  177. buttons);
  178. }
  179. /// <summary>
  180. /// Displays an error <see cref="MessageBox"/> with fixed dimensions and a default button.
  181. /// </summary>
  182. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  183. /// <param name="width">Width for the MessageBox.</param>
  184. /// <param name="height">Height for the MessageBox.</param>
  185. /// <param name="title">Title for the MessageBox.</param>
  186. /// <param name="message">Message to display. May contain multiple lines and will be word-wrapped.</param>
  187. /// <param name="defaultButton">Index of the default button (0-based).</param>
  188. /// <param name="buttons">Array of button labels.</param>
  189. /// <returns>
  190. /// The index of the selected button, or <see langword="null"/> if the user pressed
  191. /// <see cref="Application.QuitKey"/>.
  192. /// </returns>
  193. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  194. /// <remarks>
  195. /// Consider using <see cref="ErrorQuery(IApplication?, string, string, int, string[])"/> which automatically sizes the
  196. /// MessageBox.
  197. /// </remarks>
  198. public static int? ErrorQuery (
  199. IApplication? app,
  200. int width,
  201. int height,
  202. string title,
  203. string message,
  204. int defaultButton = 0,
  205. params string [] buttons
  206. )
  207. {
  208. return QueryFull (
  209. app,
  210. true,
  211. width,
  212. height,
  213. title,
  214. message,
  215. defaultButton,
  216. true,
  217. buttons);
  218. }
  219. /// <summary>
  220. /// Displays an auto-sized error <see cref="MessageBox"/> with a default button.
  221. /// </summary>
  222. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  223. /// <param name="title">Title for the MessageBox.</param>
  224. /// <param name="message">Message to display. May contain multiple lines and will be word-wrapped.</param>
  225. /// <param name="defaultButton">Index of the default button (0-based).</param>
  226. /// <param name="buttons">Array of button labels.</param>
  227. /// <returns>
  228. /// The index of the selected button, or <see langword="null"/> if the user pressed
  229. /// <see cref="Application.QuitKey"/>.
  230. /// </returns>
  231. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  232. /// <remarks>
  233. /// The MessageBox is centered and auto-sized based on title, message, and buttons.
  234. /// </remarks>
  235. public static int? ErrorQuery (IApplication? app, string title, string message, int defaultButton = 0, params string [] buttons)
  236. {
  237. return QueryFull (
  238. app,
  239. true,
  240. 0,
  241. 0,
  242. title,
  243. message,
  244. defaultButton,
  245. true,
  246. buttons);
  247. }
  248. /// <summary>
  249. /// Displays an error <see cref="MessageBox"/> with fixed dimensions, a default button, and word-wrap control.
  250. /// </summary>
  251. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  252. /// <param name="width">Width for the MessageBox.</param>
  253. /// <param name="height">Height for the MessageBox.</param>
  254. /// <param name="title">Title for the MessageBox.</param>
  255. /// <param name="message">Message to display. May contain multiple lines.</param>
  256. /// <param name="defaultButton">Index of the default button (0-based).</param>
  257. /// <param name="wrapMessage">
  258. /// If <see langword="true"/>, word-wraps the message; otherwise displays as-is with multi-line
  259. /// support.
  260. /// </param>
  261. /// <param name="buttons">Array of button labels.</param>
  262. /// <returns>
  263. /// The index of the selected button, or <see langword="null"/> if the user pressed
  264. /// <see cref="Application.QuitKey"/>.
  265. /// </returns>
  266. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  267. /// <remarks>
  268. /// Consider using <see cref="ErrorQuery(IApplication?, string, string, int, bool, string[])"/> which automatically
  269. /// sizes the MessageBox.
  270. /// </remarks>
  271. public static int? ErrorQuery (
  272. IApplication? app,
  273. int width,
  274. int height,
  275. string title,
  276. string message,
  277. int defaultButton = 0,
  278. bool wrapMessage = true,
  279. params string [] buttons
  280. )
  281. {
  282. return QueryFull (
  283. app,
  284. true,
  285. width,
  286. height,
  287. title,
  288. message,
  289. defaultButton,
  290. wrapMessage,
  291. buttons);
  292. }
  293. /// <summary>
  294. /// Displays an auto-sized error <see cref="MessageBox"/> with a default button and word-wrap control.
  295. /// </summary>
  296. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  297. /// <param name="title">Title for the MessageBox.</param>
  298. /// <param name="message">Message to display. May contain multiple lines.</param>
  299. /// <param name="defaultButton">Index of the default button (0-based).</param>
  300. /// <param name="wrapMessage">
  301. /// If <see langword="true"/>, word-wraps the message; otherwise displays as-is with multi-line
  302. /// support.
  303. /// </param>
  304. /// <param name="buttons">Array of button labels.</param>
  305. /// <returns>
  306. /// The index of the selected button, or <see langword="null"/> if the user pressed
  307. /// <see cref="Application.QuitKey"/>.
  308. /// </returns>
  309. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  310. /// <remarks>
  311. /// The MessageBox is centered and auto-sized based on title, message, and buttons.
  312. /// </remarks>
  313. public static int? ErrorQuery (
  314. IApplication? app,
  315. string title,
  316. string message,
  317. int defaultButton = 0,
  318. bool wrapMessage = true,
  319. params string [] buttons
  320. )
  321. {
  322. return QueryFull (
  323. app,
  324. true,
  325. 0,
  326. 0,
  327. title,
  328. message,
  329. defaultButton,
  330. wrapMessage,
  331. buttons);
  332. }
  333. /// <summary>
  334. /// Displays a <see cref="MessageBox"/> with fixed dimensions.
  335. /// </summary>
  336. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  337. /// <param name="width">Width for the MessageBox.</param>
  338. /// <param name="height">Height for the MessageBox.</param>
  339. /// <param name="title">Title for the MessageBox.</param>
  340. /// <param name="message">Message to display. May contain multiple lines and will be word-wrapped.</param>
  341. /// <param name="buttons">Array of button labels.</param>
  342. /// <returns>
  343. /// The index of the selected button, or <see langword="null"/> if the user pressed
  344. /// <see cref="Application.QuitKey"/>.
  345. /// </returns>
  346. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  347. /// <remarks>
  348. /// Consider using <see cref="Query(IApplication?, string, string, string[])"/> which automatically sizes the
  349. /// MessageBox.
  350. /// </remarks>
  351. public static int? Query (IApplication? app, int width, int height, string title, string message, params string [] buttons)
  352. {
  353. return QueryFull (
  354. app,
  355. false,
  356. width,
  357. height,
  358. title,
  359. message,
  360. 0,
  361. true,
  362. buttons);
  363. }
  364. /// <summary>
  365. /// Displays an auto-sized <see cref="MessageBox"/>.
  366. /// </summary>
  367. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  368. /// <param name="title">Title for the MessageBox.</param>
  369. /// <param name="message">Message to display. May contain multiple lines and will be word-wrapped.</param>
  370. /// <param name="buttons">Array of button labels.</param>
  371. /// <returns>
  372. /// The index of the selected button, or <see langword="null"/> if the user pressed
  373. /// <see cref="Application.QuitKey"/>.
  374. /// </returns>
  375. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  376. /// <remarks>
  377. /// The MessageBox is centered and auto-sized based on title, message, and buttons.
  378. /// </remarks>
  379. public static int? Query (IApplication? app, string title, string message, params string [] buttons)
  380. {
  381. return QueryFull (
  382. app,
  383. false,
  384. 0,
  385. 0,
  386. title,
  387. message,
  388. 0,
  389. true,
  390. buttons);
  391. }
  392. /// <summary>
  393. /// Displays a <see cref="MessageBox"/> with fixed dimensions and a default button.
  394. /// </summary>
  395. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  396. /// <param name="width">Width for the MessageBox.</param>
  397. /// <param name="height">Height for the MessageBox.</param>
  398. /// <param name="title">Title for the MessageBox.</param>
  399. /// <param name="message">Message to display. May contain multiple lines and will be word-wrapped.</param>
  400. /// <param name="defaultButton">Index of the default button (0-based).</param>
  401. /// <param name="buttons">Array of button labels.</param>
  402. /// <returns>
  403. /// The index of the selected button, or <see langword="null"/> if the user pressed
  404. /// <see cref="Application.QuitKey"/>.
  405. /// </returns>
  406. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  407. /// <remarks>
  408. /// Consider using <see cref="Query(IApplication?, string, string, int, string[])"/> which automatically sizes the
  409. /// MessageBox.
  410. /// </remarks>
  411. public static int? Query (
  412. IApplication? app,
  413. int width,
  414. int height,
  415. string title,
  416. string message,
  417. int defaultButton = 0,
  418. params string [] buttons
  419. )
  420. {
  421. return QueryFull (
  422. app,
  423. false,
  424. width,
  425. height,
  426. title,
  427. message,
  428. defaultButton,
  429. true,
  430. buttons);
  431. }
  432. /// <summary>
  433. /// Displays an auto-sized <see cref="MessageBox"/> with a default button.
  434. /// </summary>
  435. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  436. /// <param name="title">Title for the MessageBox.</param>
  437. /// <param name="message">Message to display. May contain multiple lines and will be word-wrapped.</param>
  438. /// <param name="defaultButton">Index of the default button (0-based).</param>
  439. /// <param name="buttons">Array of button labels.</param>
  440. /// <returns>
  441. /// The index of the selected button, or <see langword="null"/> if the user pressed
  442. /// <see cref="Application.QuitKey"/>.
  443. /// </returns>
  444. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  445. /// <remarks>
  446. /// The MessageBox is centered and auto-sized based on title, message, and buttons.
  447. /// </remarks>
  448. public static int? Query (IApplication? app, string title, string message, int defaultButton = 0, params string [] buttons)
  449. {
  450. return QueryFull (
  451. app,
  452. false,
  453. 0,
  454. 0,
  455. title,
  456. message,
  457. defaultButton,
  458. true,
  459. buttons);
  460. }
  461. /// <summary>
  462. /// Displays a <see cref="MessageBox"/> with fixed dimensions, a default button, and word-wrap control.
  463. /// </summary>
  464. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  465. /// <param name="width">Width for the MessageBox.</param>
  466. /// <param name="height">Height for the MessageBox.</param>
  467. /// <param name="title">Title for the MessageBox.</param>
  468. /// <param name="message">Message to display. May contain multiple lines.</param>
  469. /// <param name="defaultButton">Index of the default button (0-based).</param>
  470. /// <param name="wrapMessage">
  471. /// If <see langword="true"/>, word-wraps the message; otherwise displays as-is with multi-line
  472. /// support.
  473. /// </param>
  474. /// <param name="buttons">Array of button labels.</param>
  475. /// <returns>
  476. /// The index of the selected button, or <see langword="null"/> if the user pressed
  477. /// <see cref="Application.QuitKey"/>.
  478. /// </returns>
  479. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  480. /// <remarks>
  481. /// Consider using <see cref="Query(IApplication?, string, string, int, bool, string[])"/> which automatically sizes
  482. /// the MessageBox.
  483. /// </remarks>
  484. public static int? Query (
  485. IApplication? app,
  486. int width,
  487. int height,
  488. string title,
  489. string message,
  490. int defaultButton = 0,
  491. bool wrapMessage = true,
  492. params string [] buttons
  493. )
  494. {
  495. return QueryFull (
  496. app,
  497. false,
  498. width,
  499. height,
  500. title,
  501. message,
  502. defaultButton,
  503. wrapMessage,
  504. buttons);
  505. }
  506. /// <summary>
  507. /// Displays an auto-sized <see cref="MessageBox"/> with a default button and word-wrap control.
  508. /// </summary>
  509. /// <param name="app">The application instance. If <see langword="null"/>, uses <see cref="IApplication.TopRunnable"/>.</param>
  510. /// <param name="title">Title for the MessageBox.</param>
  511. /// <param name="message">Message to display. May contain multiple lines.</param>
  512. /// <param name="defaultButton">Index of the default button (0-based).</param>
  513. /// <param name="wrapMessage">
  514. /// If <see langword="true"/>, word-wraps the message; otherwise displays as-is with multi-line
  515. /// support.
  516. /// </param>
  517. /// <param name="buttons">Array of button labels.</param>
  518. /// <returns>
  519. /// The index of the selected button, or <see langword="null"/> if the user pressed
  520. /// <see cref="Application.QuitKey"/>.
  521. /// </returns>
  522. /// <exception cref="ArgumentNullException">Thrown if <paramref name="app"/> is <see langword="null"/>.</exception>
  523. /// <remarks>
  524. /// The MessageBox is centered and auto-sized based on title, message, and buttons.
  525. /// </remarks>
  526. public static int? Query (
  527. IApplication? app,
  528. string title,
  529. string message,
  530. int defaultButton = 0,
  531. bool wrapMessage = true,
  532. params string [] buttons
  533. )
  534. {
  535. return QueryFull (
  536. app,
  537. false,
  538. 0,
  539. 0,
  540. title,
  541. message,
  542. defaultButton,
  543. wrapMessage,
  544. buttons);
  545. }
  546. private static int? QueryFull (
  547. IApplication? app,
  548. bool useErrorColors,
  549. int width,
  550. int height,
  551. string title,
  552. string message,
  553. int defaultButton = 0,
  554. bool wrapMessage = true,
  555. params string [] buttons
  556. )
  557. {
  558. ArgumentNullException.ThrowIfNull (app);
  559. // Create button array for Dialog
  560. var count = 0;
  561. List<Button> buttonList = new ();
  562. if (buttons is { })
  563. {
  564. if (defaultButton > buttons.Length - 1)
  565. {
  566. defaultButton = buttons.Length - 1;
  567. }
  568. foreach (string s in buttons)
  569. {
  570. int buttonIndex = count; // Capture index for closure
  571. var b = new Button
  572. {
  573. Text = s,
  574. IsDefault = count == defaultButton,
  575. Data = buttonIndex
  576. };
  577. // Set up Accepting handler to store result in Dialog before RequestStop
  578. b.Accepting += (s, e) =>
  579. {
  580. // Store the button index in the dialog before stopping
  581. // This ensures Dialog.Result is set correctly
  582. if (e?.Context?.Source is Button { Data: int index } button)
  583. {
  584. if (button.SuperView is Dialog dialog)
  585. {
  586. dialog.Result = index;
  587. dialog.Canceled = false;
  588. }
  589. }
  590. if (e is { })
  591. {
  592. (s as View)?.App?.RequestStop ();
  593. e.Handled = true;
  594. }
  595. };
  596. buttonList.Add (b);
  597. count++;
  598. }
  599. }
  600. var d = new Dialog
  601. {
  602. Title = title,
  603. ButtonAlignment = DefaultButtonAlignment,
  604. ButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems,
  605. BorderStyle = DefaultBorderStyle,
  606. Buttons = buttonList.ToArray ()
  607. };
  608. d.Width = Dim.Auto (
  609. DimAutoStyle.Auto,
  610. Dim.Func (_ => (int)((app.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * (DefaultMinimumWidth / 100f))),
  611. Dim.Func (_ => (int)((app.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * 0.9f)));
  612. d.Height = Dim.Auto (
  613. DimAutoStyle.Auto,
  614. Dim.Func (_ => (int)((app.Screen.Height - d.GetAdornmentsThickness ().Vertical) * (DefaultMinimumHeight / 100f))),
  615. Dim.Func (_ => (int)((app.Screen.Height - d.GetAdornmentsThickness ().Vertical) * 0.9f)));
  616. if (width != 0)
  617. {
  618. d.Width = width;
  619. }
  620. if (height != 0)
  621. {
  622. d.Height = height;
  623. }
  624. d.SchemeName = useErrorColors ? SchemeManager.SchemesToSchemeName (Schemes.Error) : SchemeManager.SchemesToSchemeName (Schemes.Dialog);
  625. d.HotKeySpecifier = new ('\xFFFF');
  626. d.Text = message;
  627. d.TextAlignment = Alignment.Center;
  628. d.VerticalTextAlignment = Alignment.Start;
  629. d.TextFormatter.WordWrap = wrapMessage;
  630. d.TextFormatter.MultiLine = !wrapMessage;
  631. // Run the modal; do not shut down the mainloop driver when done
  632. app.Run (d);
  633. // Use Dialog.Result instead of manually tracking with Clicked
  634. // Dialog automatically extracts which button was clicked in OnIsRunningChanging
  635. int result = d.Result ?? -1;
  636. // Update legacy Clicked property for backward compatibility
  637. Clicked = result;
  638. d.Dispose ();
  639. return result;
  640. }
  641. }