MessageBox.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. namespace Terminal.Gui;
  2. /// <summary>
  3. /// MessageBox displays a modal message to the user, with a title, a message and a series of options that the user
  4. /// can choose from.
  5. /// </summary>
  6. /// <para>
  7. /// The difference between the <see cref="Query(string, string, string[])"/> and
  8. /// <see cref="ErrorQuery(string, string, string[])"/> method is the default set of colors used for the message box.
  9. /// </para>
  10. /// <para>
  11. /// The following example pops up a <see cref="MessageBox"/> with the specified title and text, plus two
  12. /// <see cref="Button"/>s. The value -1 is returned when the user cancels the <see cref="MessageBox"/> by pressing the
  13. /// ESC key.
  14. /// </para>
  15. /// <example>
  16. /// <code lang="c#">
  17. /// var n = MessageBox.Query ("Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No");
  18. /// if (n == 0)
  19. /// quit = true;
  20. /// else
  21. /// quit = false;
  22. /// </code>
  23. /// </example>
  24. public static class MessageBox
  25. {
  26. /// <summary>
  27. /// The index of the selected button, or -1 if the user pressed ESC to close the dialog. This is useful for web
  28. /// based console where by default there is no SynchronizationContext or TaskScheduler.
  29. /// </summary>
  30. public static int Clicked { get; private set; } = -1;
  31. /// <summary>
  32. /// Defines the default border styling for <see cref="Dialog"/>. Can be configured via
  33. /// <see cref="ConfigurationManager"/>.
  34. /// </summary>
  35. [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
  36. public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
  37. /// <summary>
  38. /// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  39. /// to the user.
  40. /// </summary>
  41. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  42. /// <param name="width">Width for the window.</param>
  43. /// <param name="height">Height for the window.</param>
  44. /// <param name="title">Title for the query.</param>
  45. /// <param name="message">Message to display, might contain multiple lines.</param>
  46. /// <param name="buttons">Array of buttons to add.</param>
  47. /// <remarks>
  48. /// Use <see cref="ErrorQuery(string, string, string[])"/> instead; it automatically sizes the MessageBox based on
  49. /// the contents.
  50. /// </remarks>
  51. public static int ErrorQuery (int width, int height, string title, string message, params string [] buttons)
  52. {
  53. return QueryFull (true, width, height, title, message, 0, true, buttons);
  54. }
  55. /// <summary>
  56. /// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  57. /// to the user.
  58. /// </summary>
  59. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  60. /// <param name="title">Title for the query.</param>
  61. /// <param name="message">Message to display, might contain multiple lines.</param>
  62. /// <param name="buttons">Array of buttons to add.</param>
  63. /// <remarks>
  64. /// The message box will be vertically and horizontally centered in the container and the size will be
  65. /// automatically determined from the size of the title, message. and buttons.
  66. /// </remarks>
  67. public static int ErrorQuery (string title, string message, params string [] buttons) { return QueryFull (true, 0, 0, title, message, 0, true, buttons); }
  68. /// <summary>
  69. /// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  70. /// to the user.
  71. /// </summary>
  72. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  73. /// <param name="width">Width for the window.</param>
  74. /// <param name="height">Height for the window.</param>
  75. /// <param name="title">Title for the query.</param>
  76. /// <param name="message">Message to display, might contain multiple lines.</param>
  77. /// <param name="defaultButton">Index of the default button.</param>
  78. /// <param name="buttons">Array of buttons to add.</param>
  79. /// <remarks>
  80. /// Use <see cref="ErrorQuery(string, string, string[])"/> instead; it automatically sizes the MessageBox based on
  81. /// the contents.
  82. /// </remarks>
  83. public static int ErrorQuery (
  84. int width,
  85. int height,
  86. string title,
  87. string message,
  88. int defaultButton = 0,
  89. params string [] buttons
  90. )
  91. {
  92. return QueryFull (true, width, height, title, message, defaultButton, true, buttons);
  93. }
  94. /// <summary>
  95. /// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  96. /// to the user.
  97. /// </summary>
  98. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  99. /// <param name="title">Title for the query.</param>
  100. /// <param name="message">Message to display, might contain multiple lines.</param>
  101. /// <param name="defaultButton">Index of the default button.</param>
  102. /// <param name="buttons">Array of buttons to add.</param>
  103. /// <remarks>
  104. /// The message box will be vertically and horizontally centered in the container and the size will be
  105. /// automatically determined from the size of the title, message. and buttons.
  106. /// </remarks>
  107. public static int ErrorQuery (string title, string message, int defaultButton = 0, params string [] buttons)
  108. {
  109. return QueryFull (true, 0, 0, title, message, defaultButton, true, buttons);
  110. }
  111. /// <summary>
  112. /// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  113. /// to the user.
  114. /// </summary>
  115. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  116. /// <param name="width">Width for the window.</param>
  117. /// <param name="height">Height for the window.</param>
  118. /// <param name="title">Title for the query.</param>
  119. /// <param name="message">Message to display, might contain multiple lines.</param>
  120. /// <param name="defaultButton">Index of the default button.</param>
  121. /// <param name="wrapMessagge">If wrap the message or not.</param>
  122. /// <param name="buttons">Array of buttons to add.</param>
  123. /// <remarks>
  124. /// Use <see cref="ErrorQuery(string, string, string[])"/> instead; it automatically sizes the MessageBox based on
  125. /// the contents.
  126. /// </remarks>
  127. public static int ErrorQuery (
  128. int width,
  129. int height,
  130. string title,
  131. string message,
  132. int defaultButton = 0,
  133. bool wrapMessagge = true,
  134. params string [] buttons
  135. )
  136. {
  137. return QueryFull (true, width, height, title, message, defaultButton, wrapMessagge, buttons);
  138. }
  139. /// <summary>
  140. /// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  141. /// to the user.
  142. /// </summary>
  143. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  144. /// <param name="title">Title for the query.</param>
  145. /// <param name="message">Message to display, might contain multiple lines.</param>
  146. /// <param name="defaultButton">Index of the default button.</param>
  147. /// <param name="wrapMessagge">If wrap the message or not.</param>
  148. /// <param name="buttons">Array of buttons to add.</param>
  149. /// <remarks>
  150. /// The message box will be vertically and horizontally centered in the container and the size will be
  151. /// automatically determined from the size of the title, message. and buttons.
  152. /// </remarks>
  153. public static int ErrorQuery (
  154. string title,
  155. string message,
  156. int defaultButton = 0,
  157. bool wrapMessagge = true,
  158. params string [] buttons
  159. )
  160. {
  161. return QueryFull (true, 0, 0, title, message, defaultButton, wrapMessagge, buttons);
  162. }
  163. /// <summary>
  164. /// Presents a normal <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  165. /// to the user.
  166. /// </summary>
  167. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  168. /// <param name="width">Width for the window.</param>
  169. /// <param name="height">Height for the window.</param>
  170. /// <param name="title">Title for the query.</param>
  171. /// <param name="message">Message to display, might contain multiple lines.</param>
  172. /// <param name="buttons">Array of buttons to add.</param>
  173. /// <remarks>
  174. /// Use <see cref="Query(string, string, string[])"/> instead; it automatically sizes the MessageBox based on the
  175. /// contents.
  176. /// </remarks>
  177. public static int Query (int width, int height, string title, string message, params string [] buttons)
  178. {
  179. return QueryFull (false, width, height, title, message, 0, true, buttons);
  180. }
  181. /// <summary>
  182. /// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  183. /// to the user.
  184. /// </summary>
  185. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  186. /// <param name="title">Title for the query.</param>
  187. /// <param name="message">Message to display, might contain multiple lines.</param>
  188. /// <param name="buttons">Array of buttons to add.</param>
  189. /// <remarks>
  190. /// The message box will be vertically and horizontally centered in the container and the size will be
  191. /// automatically determined from the size of the message and buttons.
  192. /// </remarks>
  193. public static int Query (string title, string message, params string [] buttons) { return QueryFull (false, 0, 0, title, message, 0, true, buttons); }
  194. /// <summary>
  195. /// Presents a normal <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  196. /// to the user.
  197. /// </summary>
  198. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  199. /// <param name="width">Width for the window.</param>
  200. /// <param name="height">Height for the window.</param>
  201. /// <param name="title">Title for the query.</param>
  202. /// <param name="message">Message to display, might contain multiple lines.</param>
  203. /// <param name="defaultButton">Index of the default button.</param>
  204. /// <param name="buttons">Array of buttons to add.</param>
  205. /// <remarks>
  206. /// Use <see cref="Query(string, string, string[])"/> instead; it automatically sizes the MessageBox based on the
  207. /// contents.
  208. /// </remarks>
  209. public static int Query (
  210. int width,
  211. int height,
  212. string title,
  213. string message,
  214. int defaultButton = 0,
  215. params string [] buttons
  216. )
  217. {
  218. return QueryFull (false, width, height, title, message, defaultButton, true, buttons);
  219. }
  220. /// <summary>
  221. /// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  222. /// to the user.
  223. /// </summary>
  224. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  225. /// <param name="title">Title for the query.</param>
  226. /// <param name="message">Message to display, might contain multiple lines.</param>
  227. /// <param name="defaultButton">Index of the default button.</param>
  228. /// <param name="buttons">Array of buttons to add.</param>
  229. /// <remarks>
  230. /// The message box will be vertically and horizontally centered in the container and the size will be
  231. /// automatically determined from the size of the message and buttons.
  232. /// </remarks>
  233. public static int Query (string title, string message, int defaultButton = 0, params string [] buttons)
  234. {
  235. return QueryFull (false, 0, 0, title, message, defaultButton, true, buttons);
  236. }
  237. /// <summary>
  238. /// Presents a normal <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  239. /// to the user.
  240. /// </summary>
  241. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  242. /// <param name="width">Width for the window.</param>
  243. /// <param name="height">Height for the window.</param>
  244. /// <param name="title">Title for the query.</param>
  245. /// <param name="message">Message to display, might contain multiple lines.</param>
  246. /// <param name="defaultButton">Index of the default button.</param>
  247. /// <param name="wrapMessagge">If wrap the message or not.</param>
  248. /// <param name="buttons">Array of buttons to add.</param>
  249. /// <remarks>
  250. /// Use <see cref="Query(string, string, string[])"/> instead; it automatically sizes the MessageBox based on the
  251. /// contents.
  252. /// </remarks>
  253. public static int Query (
  254. int width,
  255. int height,
  256. string title,
  257. string message,
  258. int defaultButton = 0,
  259. bool wrapMessagge = true,
  260. params string [] buttons
  261. )
  262. {
  263. return QueryFull (false, width, height, title, message, defaultButton, wrapMessagge, buttons);
  264. }
  265. /// <summary>
  266. /// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
  267. /// to the user.
  268. /// </summary>
  269. /// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</returns>
  270. /// <param name="title">Title for the query.</param>
  271. /// <param name="message">Message to display, might contain multiple lines.</param>
  272. /// <param name="defaultButton">Index of the default button.</param>
  273. /// <param name="wrapMessage">If wrap the message or not.</param>
  274. /// <param name="buttons">Array of buttons to add.</param>
  275. /// <remarks>
  276. /// The message box will be vertically and horizontally centered in the container and the size will be
  277. /// automatically determined from the size of the message and buttons.
  278. /// </remarks>
  279. public static int Query (
  280. string title,
  281. string message,
  282. int defaultButton = 0,
  283. bool wrapMessage = true,
  284. params string [] buttons
  285. )
  286. {
  287. return QueryFull (false, 0, 0, title, message, defaultButton, wrapMessage, buttons);
  288. }
  289. private static int QueryFull (
  290. bool useErrorColors,
  291. int width,
  292. int height,
  293. string title,
  294. string message,
  295. int defaultButton = 0,
  296. bool wrapMessage = true,
  297. params string [] buttons
  298. )
  299. {
  300. // Create button array for Dialog
  301. var count = 0;
  302. List<Button> buttonList = new ();
  303. if (buttons is { })
  304. {
  305. if (defaultButton > buttons.Length - 1)
  306. {
  307. defaultButton = buttons.Length - 1;
  308. }
  309. foreach (string s in buttons)
  310. {
  311. var b = new Button
  312. {
  313. Text = s,
  314. };
  315. if (count == defaultButton)
  316. {
  317. b.IsDefault = true;
  318. }
  319. buttonList.Add (b);
  320. count++;
  321. }
  322. }
  323. var d = new Dialog
  324. {
  325. ButtonAlignment = Alignment.Center,
  326. ButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems,
  327. Buttons = buttonList.ToArray (),
  328. Title = title,
  329. BorderStyle = DefaultBorderStyle,
  330. Width = Dim.Auto (DimAutoStyle.Content, minimumContentDim: Dim.Percent(60)),
  331. Height = Dim.Auto (DimAutoStyle.Content),
  332. };
  333. if (width != 0)
  334. {
  335. d.Width = width;
  336. }
  337. if (height != 0)
  338. {
  339. d.Height = height;
  340. }
  341. if (useErrorColors)
  342. {
  343. d.ColorScheme = Colors.ColorSchemes ["Error"];
  344. }
  345. else
  346. {
  347. d.ColorScheme = Colors.ColorSchemes ["Dialog"];
  348. }
  349. var messageLabel = new Label
  350. {
  351. Text = message,
  352. TextAlignment = Alignment.Center,
  353. X = Pos.Center (),
  354. Y = 0,
  355. // ColorScheme = Colors.ColorSchemes ["Error"]
  356. };
  357. messageLabel.TextFormatter.WordWrap = wrapMessage;
  358. messageLabel.TextFormatter.MultiLine = !wrapMessage;
  359. if (wrapMessage)
  360. {
  361. messageLabel.Width = Dim.Fill ();
  362. messageLabel.Height = Dim.Fill (1);
  363. int GetWrapSize ()
  364. {
  365. // A bit of a hack to get the height of the wrapped text.
  366. messageLabel.TextFormatter.Size = new (d.ContentSize.Width, 1000);
  367. return messageLabel.TextFormatter.FormatAndGetSize ().Height;
  368. }
  369. d.Height = Dim.Auto (DimAutoStyle.Content, minimumContentDim: Dim.Func (GetWrapSize) + 1);
  370. }
  371. d.Add (messageLabel);
  372. // Setup actions
  373. Clicked = -1;
  374. for (var n = 0; n < buttonList.Count; n++)
  375. {
  376. int buttonId = n;
  377. Button b = buttonList [n];
  378. b.Accept += (s, e) =>
  379. {
  380. Clicked = buttonId;
  381. Application.RequestStop ();
  382. };
  383. if (b.IsDefault)
  384. {
  385. b.SetFocus ();
  386. }
  387. }
  388. // Run the modal; do not shutdown the mainloop driver when done
  389. Application.Run (d);
  390. d.Dispose ();
  391. return Clicked;
  392. }
  393. }