FileDialog.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. //
  2. // FileDialog.cs: File system dialogs for open and save
  3. //
  4. // TODO:
  5. // * Add directory selector
  6. // * Implement subclasses
  7. // * Figure out why message text does not show
  8. // * Remove the extra space when message does not show
  9. // * Use a line separator to show the file listing, so we can use same colors as the rest
  10. // * DirListView: Add mouse support
  11. using System;
  12. using System.Collections.Generic;
  13. using NStack;
  14. using System.IO;
  15. using System.Linq;
  16. namespace Terminal.Gui {
  17. internal class DirListView : View {
  18. int top, selected;
  19. DirectoryInfo dirInfo;
  20. List<(string, bool, bool)> infos;
  21. internal bool canChooseFiles = true;
  22. internal bool canChooseDirectories = false;
  23. internal bool allowsMultipleSelection = false;
  24. FileDialog host;
  25. public DirListView (FileDialog host)
  26. {
  27. infos = new List<(string, bool, bool)> ();
  28. CanFocus = true;
  29. this.host = host;
  30. }
  31. bool IsAllowed (FileSystemInfo fsi)
  32. {
  33. if (fsi.Attributes.HasFlag (FileAttributes.Directory))
  34. return true;
  35. if (allowedFileTypes == null)
  36. return true;
  37. foreach (var ft in allowedFileTypes)
  38. if (fsi.Name.EndsWith (ft))
  39. return true;
  40. return false;
  41. }
  42. internal void Reload ()
  43. {
  44. try {
  45. dirInfo = new DirectoryInfo (directory.ToString ());
  46. infos = (from x in dirInfo.GetFileSystemInfos ()
  47. where IsAllowed (x)
  48. orderby (!x.Attributes.HasFlag (FileAttributes.Directory)) + x.Name
  49. select (x.Name, x.Attributes.HasFlag (FileAttributes.Directory), false)).ToList ();
  50. infos.Insert (0, ("..", true, false));
  51. top = 0;
  52. selected = 0;
  53. } catch (Exception) {
  54. dirInfo = null;
  55. infos.Clear ();
  56. } finally {
  57. SetNeedsDisplay ();
  58. }
  59. }
  60. ustring directory;
  61. public ustring Directory {
  62. get => directory;
  63. set {
  64. if (directory == value)
  65. return;
  66. directory = value;
  67. Reload ();
  68. }
  69. }
  70. public override void PositionCursor ()
  71. {
  72. Move (0, selected - top);
  73. }
  74. int lastSelected;
  75. bool shiftOnWheel;
  76. public override bool MouseEvent (MouseEvent me)
  77. {
  78. if ((me.Flags & (MouseFlags.Button1Clicked | MouseFlags.Button1DoubleClicked |
  79. MouseFlags.WheeledUp | MouseFlags.WheeledDown)) == 0)
  80. return false;
  81. if (!HasFocus)
  82. SetFocus ();
  83. if (infos == null)
  84. return false;
  85. if (me.Y + top >= infos.Count)
  86. return true;
  87. int lastSelectedCopy = shiftOnWheel ? lastSelected : selected;
  88. switch (me.Flags) {
  89. case MouseFlags.Button1Clicked:
  90. SetSelected (me);
  91. SelectionChanged ();
  92. SetNeedsDisplay ();
  93. break;
  94. case MouseFlags.Button1DoubleClicked:
  95. SetSelected (me);
  96. if (ExecuteSelection ()) {
  97. host.canceled = false;
  98. Application.RequestStop ();
  99. }
  100. return true;
  101. case MouseFlags.Button1Clicked | MouseFlags.ButtonShift:
  102. SetSelected (me);
  103. if (shiftOnWheel)
  104. lastSelected = lastSelectedCopy;
  105. shiftOnWheel = false;
  106. PerformMultipleSelection (lastSelected);
  107. return true;
  108. case MouseFlags.Button1Clicked | MouseFlags.ButtonCtrl:
  109. SetSelected (me);
  110. PerformMultipleSelection ();
  111. return true;
  112. case MouseFlags.WheeledUp:
  113. SetSelected (me);
  114. selected = lastSelected;
  115. MoveUp ();
  116. return true;
  117. case MouseFlags.WheeledDown:
  118. SetSelected (me);
  119. selected = lastSelected;
  120. MoveDown ();
  121. return true;
  122. case MouseFlags.WheeledUp | MouseFlags.ButtonShift:
  123. SetSelected (me);
  124. selected = lastSelected;
  125. lastSelected = lastSelectedCopy;
  126. shiftOnWheel = true;
  127. MoveUp ();
  128. return true;
  129. case MouseFlags.WheeledDown | MouseFlags.ButtonShift:
  130. SetSelected (me);
  131. selected = lastSelected;
  132. lastSelected = lastSelectedCopy;
  133. shiftOnWheel = true;
  134. MoveDown ();
  135. return true;
  136. }
  137. return true;
  138. }
  139. void SetSelected (MouseEvent me)
  140. {
  141. lastSelected = selected;
  142. selected = top + me.Y;
  143. }
  144. void DrawString (int line, string str)
  145. {
  146. var f = Frame;
  147. var width = f.Width;
  148. var ustr = ustring.Make (str);
  149. Move (allowsMultipleSelection ? 3 : 2, line);
  150. int byteLen = ustr.Length;
  151. int used = allowsMultipleSelection ? 2 : 1;
  152. for (int i = 0; i < byteLen;) {
  153. (var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen);
  154. var count = Rune.ColumnWidth (rune);
  155. if (used + count >= width)
  156. break;
  157. Driver.AddRune (rune);
  158. used += count;
  159. i += size;
  160. }
  161. for (; used < width - 1; used++) {
  162. Driver.AddRune (' ');
  163. }
  164. }
  165. public override void Redraw (Rect bounds)
  166. {
  167. var current = ColorScheme.Focus;
  168. Driver.SetAttribute (current);
  169. Move (0, 0);
  170. var f = Frame;
  171. var item = top;
  172. bool focused = HasFocus;
  173. var width = bounds.Width;
  174. for (int row = 0; row < f.Height; row++, item++) {
  175. bool isSelected = item == selected;
  176. Move (0, row);
  177. var newcolor = focused ? (isSelected ? ColorScheme.HotNormal : ColorScheme.Focus) : ColorScheme.Focus;
  178. if (newcolor != current) {
  179. Driver.SetAttribute (newcolor);
  180. current = newcolor;
  181. }
  182. if (item >= infos.Count) {
  183. for (int c = 0; c < f.Width; c++)
  184. Driver.AddRune (' ');
  185. continue;
  186. }
  187. var fi = infos [item];
  188. Driver.AddRune (isSelected ? '>' : ' ');
  189. if (allowsMultipleSelection)
  190. Driver.AddRune (fi.Item3 ? '*' : ' ');
  191. if (fi.Item2)
  192. Driver.AddRune ('/');
  193. else
  194. Driver.AddRune (' ');
  195. DrawString (row, fi.Item1);
  196. }
  197. }
  198. public Action<(string, bool)> SelectedChanged { get; set; }
  199. public Action<ustring> DirectoryChanged { get; set; }
  200. public Action<ustring> FileChanged { get; set; }
  201. void SelectionChanged ()
  202. {
  203. if (FilePaths.Count > 0)
  204. FileChanged?.Invoke (string.Join (", ", GetFilesName (FilePaths)));
  205. else
  206. FileChanged?.Invoke (infos [selected].Item2 ? "" : Path.GetFileName (infos [selected].Item1));
  207. if (SelectedChanged != null) {
  208. var sel = infos [selected];
  209. SelectedChanged ((sel.Item1, sel.Item2));
  210. }
  211. }
  212. List<string> GetFilesName (IReadOnlyList<string> files)
  213. {
  214. List<string> filesName = new List<string> ();
  215. foreach (var file in files) {
  216. filesName.Add (Path.GetFileName (file));
  217. }
  218. return filesName;
  219. }
  220. public override bool ProcessKey (KeyEvent keyEvent)
  221. {
  222. switch (keyEvent.Key) {
  223. case Key.CursorUp:
  224. case Key.ControlP:
  225. MoveUp ();
  226. return true;
  227. case Key.CursorDown:
  228. case Key.ControlN:
  229. MoveDown ();
  230. return true;
  231. case Key.ControlV:
  232. case Key.PageDown:
  233. var n = (selected + Frame.Height);
  234. if (n > infos.Count)
  235. n = infos.Count - 1;
  236. if (n != selected) {
  237. selected = n;
  238. if (infos.Count >= Frame.Height)
  239. top = selected;
  240. else
  241. top = 0;
  242. SelectionChanged ();
  243. SetNeedsDisplay ();
  244. }
  245. return true;
  246. case Key.Enter:
  247. if (ExecuteSelection ())
  248. return false;
  249. else
  250. return true;
  251. case Key.PageUp:
  252. n = (selected - Frame.Height);
  253. if (n < 0)
  254. n = 0;
  255. if (n != selected) {
  256. selected = n;
  257. top = selected;
  258. SelectionChanged ();
  259. SetNeedsDisplay ();
  260. }
  261. return true;
  262. case Key.Space:
  263. case Key.ControlT:
  264. PerformMultipleSelection ();
  265. return true;
  266. case Key.Home:
  267. MoveFirst ();
  268. return true;
  269. case Key.End:
  270. MoveLast ();
  271. return true;
  272. }
  273. return base.ProcessKey (keyEvent);
  274. }
  275. void MoveLast ()
  276. {
  277. selected = infos.Count - 1;
  278. top = infos.Count () - 1;
  279. SelectionChanged ();
  280. SetNeedsDisplay ();
  281. }
  282. void MoveFirst ()
  283. {
  284. selected = 0;
  285. top = 0;
  286. SelectionChanged ();
  287. SetNeedsDisplay ();
  288. }
  289. void MoveDown ()
  290. {
  291. if (selected + 1 < infos.Count) {
  292. selected++;
  293. if (selected >= top + Frame.Height)
  294. top++;
  295. SelectionChanged ();
  296. SetNeedsDisplay ();
  297. }
  298. }
  299. void MoveUp ()
  300. {
  301. if (selected > 0) {
  302. selected--;
  303. if (selected < top)
  304. top = selected;
  305. SelectionChanged ();
  306. SetNeedsDisplay ();
  307. }
  308. }
  309. internal bool ExecuteSelection (bool isPrompt = false)
  310. {
  311. var isDir = infos [selected].Item2;
  312. if (isDir) {
  313. if (!isPrompt) {
  314. Directory = Path.GetFullPath (Path.Combine (Path.GetFullPath (Directory.ToString ()), infos [selected].Item1));
  315. DirectoryChanged?.Invoke (Directory);
  316. }
  317. } else {
  318. FileChanged?.Invoke (infos [selected].Item1);
  319. if (canChooseFiles) {
  320. // Ensures that at least one file is selected.
  321. if (FilePaths.Count == 0)
  322. PerformMultipleSelection ();
  323. // Let the OK handler take it over
  324. return true;
  325. }
  326. // No files allowed, do not let the default handler take it.
  327. }
  328. return false;
  329. }
  330. void PerformMultipleSelection (int? firstSelected = null)
  331. {
  332. if (allowsMultipleSelection) {
  333. int first = Math.Min (firstSelected ?? selected, selected);
  334. int last = Math.Max (selected, firstSelected ?? selected);
  335. for (int i = first; i <= last; i++) {
  336. if ((canChooseFiles && infos [i].Item2 == false) ||
  337. (canChooseDirectories && infos [i].Item2 &&
  338. infos [i].Item1 != "..")) {
  339. infos [i] = (infos [i].Item1, infos [i].Item2, !infos [i].Item3);
  340. }
  341. }
  342. SelectionChanged ();
  343. SetNeedsDisplay ();
  344. }
  345. }
  346. string [] allowedFileTypes;
  347. public string [] AllowedFileTypes {
  348. get => allowedFileTypes;
  349. set {
  350. allowedFileTypes = value;
  351. Reload ();
  352. }
  353. }
  354. public string MakePath (string relativePath)
  355. {
  356. return Path.GetFullPath (Path.Combine (Directory.ToString (), relativePath));
  357. }
  358. public IReadOnlyList<string> FilePaths {
  359. get {
  360. if (allowsMultipleSelection) {
  361. var res = new List<string> ();
  362. foreach (var item in infos)
  363. if (item.Item3)
  364. res.Add (MakePath (item.Item1));
  365. return res;
  366. } else {
  367. if (infos [selected].Item2) {
  368. if (canChooseDirectories)
  369. return new List<string> () { MakePath (infos [selected].Item1) };
  370. return Array.Empty<string> ();
  371. } else {
  372. if (canChooseFiles)
  373. return new List<string> () { MakePath (infos [selected].Item1) };
  374. return Array.Empty<string> ();
  375. }
  376. }
  377. }
  378. }
  379. }
  380. /// <summary>
  381. /// Base class for the <see cref="OpenDialog"/> and the <see cref="SaveDialog"/>
  382. /// </summary>
  383. public class FileDialog : Dialog {
  384. Button prompt, cancel;
  385. Label nameFieldLabel, message, dirLabel;
  386. TextField dirEntry, nameEntry;
  387. internal DirListView dirListView;
  388. /// <summary>
  389. /// Initializes a new <see cref="FileDialog"/>.
  390. /// </summary>
  391. public FileDialog () : this (title: string.Empty, prompt: string.Empty, nameFieldLabel: string.Empty, message: string.Empty) { }
  392. /// <summary>
  393. /// Initializes a new instance of <see cref="FileDialog"/>
  394. /// </summary>
  395. /// <param name="title">The title.</param>
  396. /// <param name="prompt">The prompt.</param>
  397. /// <param name="nameFieldLabel">The name field label.</param>
  398. /// <param name="message">The message.</param>
  399. public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message) : base (title)//, Driver.Cols - 20, Driver.Rows - 5, null)
  400. {
  401. this.message = new Label (message) {
  402. X = 1,
  403. Y = 0,
  404. };
  405. Add (this.message);
  406. var msgLines = TextFormatter.MaxLines (message, Driver.Cols - 20);
  407. dirLabel = new Label ("Directory: ") {
  408. X = 1,
  409. Y = 1 + msgLines
  410. };
  411. dirEntry = new TextField ("") {
  412. X = Pos.Right (dirLabel),
  413. Y = 1 + msgLines,
  414. Width = Dim.Fill () - 1,
  415. TextChanged = (e) => {
  416. DirectoryPath = dirEntry.Text;
  417. }
  418. };
  419. Add (dirLabel, dirEntry);
  420. this.nameFieldLabel = new Label ("File(s): ") {
  421. X = 3,
  422. Y = 3 + msgLines,
  423. };
  424. nameEntry = new TextField ("") {
  425. X = Pos.Left (dirEntry),
  426. Y = 3 + msgLines,
  427. Width = Dim.Fill () - 1
  428. };
  429. Add (this.nameFieldLabel, nameEntry);
  430. dirListView = new DirListView (this) {
  431. X = 1,
  432. Y = 3 + msgLines + 2,
  433. Width = Dim.Fill () - 1,
  434. Height = Dim.Fill () - 2,
  435. };
  436. DirectoryPath = Path.GetFullPath (Environment.CurrentDirectory);
  437. Add (dirListView);
  438. dirListView.DirectoryChanged = (dir) => dirEntry.Text = dir;
  439. dirListView.FileChanged = (file) => nameEntry.Text = file;
  440. this.cancel = new Button ("Cancel");
  441. this.cancel.Clicked += () => {
  442. canceled = true;
  443. Application.RequestStop ();
  444. };
  445. AddButton (cancel);
  446. this.prompt = new Button (prompt) {
  447. IsDefault = true,
  448. };
  449. this.prompt.Clicked += () => {
  450. dirListView.ExecuteSelection (true);
  451. canceled = false;
  452. Application.RequestStop ();
  453. };
  454. AddButton (this.prompt);
  455. Width = Dim.Percent (80);
  456. Height = Dim.Percent (80);
  457. // On success, we will set this to false.
  458. canceled = true;
  459. }
  460. internal bool canceled;
  461. ///<inheritdoc/>
  462. public override void WillPresent ()
  463. {
  464. base.WillPresent ();
  465. //SetFocus (nameEntry);
  466. }
  467. //protected override void Dispose (bool disposing)
  468. //{
  469. // message?.Dispose ();
  470. // base.Dispose (disposing);
  471. //}
  472. /// <summary>
  473. /// Gets or sets the prompt label for the <see cref="Button"/> displayed to the user
  474. /// </summary>
  475. /// <value>The prompt.</value>
  476. public ustring Prompt {
  477. get => prompt.Text;
  478. set {
  479. prompt.Text = value;
  480. }
  481. }
  482. /// <summary>
  483. /// Gets or sets the name field label.
  484. /// </summary>
  485. /// <value>The name field label.</value>
  486. public ustring NameFieldLabel {
  487. get => nameFieldLabel.Text;
  488. set {
  489. nameFieldLabel.Text = value;
  490. }
  491. }
  492. /// <summary>
  493. /// Gets or sets the message displayed to the user, defaults to nothing
  494. /// </summary>
  495. /// <value>The message.</value>
  496. public ustring Message {
  497. get => message.Text;
  498. set {
  499. message.Text = value;
  500. }
  501. }
  502. /// <summary>
  503. /// Gets or sets a value indicating whether this <see cref="FileDialog"/> can create directories.
  504. /// </summary>
  505. /// <value><c>true</c> if can create directories; otherwise, <c>false</c>.</value>
  506. public bool CanCreateDirectories { get; set; }
  507. /// <summary>
  508. /// Gets or sets a value indicating whether this <see cref="FileDialog"/> is extension hidden.
  509. /// </summary>
  510. /// <value><c>true</c> if is extension hidden; otherwise, <c>false</c>.</value>
  511. public bool IsExtensionHidden { get; set; }
  512. /// <summary>
  513. /// Gets or sets the directory path for this panel
  514. /// </summary>
  515. /// <value>The directory path.</value>
  516. public ustring DirectoryPath {
  517. get => dirEntry.Text;
  518. set {
  519. dirEntry.Text = value;
  520. dirListView.Directory = value;
  521. }
  522. }
  523. /// <summary>
  524. /// The array of filename extensions allowed, or null if all file extensions are allowed.
  525. /// </summary>
  526. /// <value>The allowed file types.</value>
  527. public string [] AllowedFileTypes {
  528. get => dirListView.AllowedFileTypes;
  529. set => dirListView.AllowedFileTypes = value;
  530. }
  531. /// <summary>
  532. /// Gets or sets a value indicating whether this <see cref="FileDialog"/> allows the file to be saved with a different extension
  533. /// </summary>
  534. /// <value><c>true</c> if allows other file types; otherwise, <c>false</c>.</value>
  535. public bool AllowsOtherFileTypes { get; set; }
  536. /// <summary>
  537. /// The File path that is currently shown on the panel
  538. /// </summary>
  539. /// <value>The absolute file path for the file path entered.</value>
  540. public ustring FilePath {
  541. get => dirListView.MakePath (nameEntry.Text.ToString ());
  542. set {
  543. nameEntry.Text = Path.GetFileName (value.ToString ());
  544. }
  545. }
  546. /// <summary>
  547. /// Check if the dialog was or not canceled.
  548. /// </summary>
  549. public bool Canceled { get => canceled; }
  550. }
  551. /// <summary>
  552. /// The <see cref="SaveDialog"/> provides an interactive dialog box for users to pick a file to
  553. /// save.
  554. /// </summary>
  555. /// <remarks>
  556. /// <para>
  557. /// To use, create an instance of <see cref="SaveDialog"/>, and pass it to
  558. /// <see cref="Application.Run()"/>. This will run the dialog modally,
  559. /// and when this returns, the <see cref="FileName"/>property will contain the selected file name or
  560. /// null if the user canceled.
  561. /// </para>
  562. /// </remarks>
  563. public class SaveDialog : FileDialog {
  564. /// <summary>
  565. /// Initializes a new <see cref="SaveDialog"/>.
  566. /// </summary>
  567. public SaveDialog () : this (title: string.Empty, message: string.Empty) { }
  568. /// <summary>
  569. /// Initializes a new <see cref="SaveDialog"/>.
  570. /// </summary>
  571. /// <param name="title">The title.</param>
  572. /// <param name="message">The message.</param>
  573. public SaveDialog (ustring title, ustring message) : base (title, prompt: "Save", nameFieldLabel: "Save as:", message: message) { }
  574. /// <summary>
  575. /// Gets the name of the file the user selected for saving, or null
  576. /// if the user canceled the <see cref="SaveDialog"/>.
  577. /// </summary>
  578. /// <value>The name of the file.</value>
  579. public ustring FileName {
  580. get {
  581. if (canceled)
  582. return null;
  583. return Path.GetFileName (FilePath.ToString ());
  584. }
  585. }
  586. }
  587. /// <summary>
  588. /// The <see cref="OpenDialog"/>provides an interactive dialog box for users to select files or directories.
  589. /// </summary>
  590. /// <remarks>
  591. /// <para>
  592. /// The open dialog can be used to select files for opening, it can be configured to allow
  593. /// multiple items to be selected (based on the AllowsMultipleSelection) variable and
  594. /// you can control whether this should allow files or directories to be selected.
  595. /// </para>
  596. /// <para>
  597. /// To use, create an instance of <see cref="OpenDialog"/>, and pass it to
  598. /// <see cref="Application.Run()"/>. This will run the dialog modally,
  599. /// and when this returns, the list of filds will be available on the <see cref="FilePaths"/> property.
  600. /// </para>
  601. /// <para>
  602. /// To select more than one file, users can use the spacebar, or control-t.
  603. /// </para>
  604. /// </remarks>
  605. public class OpenDialog : FileDialog {
  606. /// <summary>
  607. /// Initializes a new <see cref="OpenDialog"/>.
  608. /// </summary>
  609. public OpenDialog () : this (title: string.Empty, message: string.Empty) { }
  610. /// <summary>
  611. /// Initializes a new <see cref="OpenDialog"/>.
  612. /// </summary>
  613. /// <param name="title"></param>
  614. /// <param name="message"></param>
  615. public OpenDialog (ustring title, ustring message) : base (title, prompt: "Open", nameFieldLabel: "Open", message: message)
  616. {
  617. }
  618. /// <summary>
  619. /// Gets or sets a value indicating whether this <see cref="Terminal.Gui.OpenDialog"/> can choose files.
  620. /// </summary>
  621. /// <value><c>true</c> if can choose files; otherwise, <c>false</c>. Defaults to <c>true</c></value>
  622. public bool CanChooseFiles {
  623. get => dirListView.canChooseFiles;
  624. set {
  625. dirListView.canChooseFiles = value;
  626. dirListView.Reload ();
  627. }
  628. }
  629. /// <summary>
  630. /// Gets or sets a value indicating whether this <see cref="OpenDialog"/> can choose directories.
  631. /// </summary>
  632. /// <value><c>true</c> if can choose directories; otherwise, <c>false</c> defaults to <c>false</c>.</value>
  633. public bool CanChooseDirectories {
  634. get => dirListView.canChooseDirectories;
  635. set {
  636. dirListView.canChooseDirectories = value;
  637. dirListView.Reload ();
  638. }
  639. }
  640. /// <summary>
  641. /// Gets or sets a value indicating whether this <see cref="OpenDialog"/> allows multiple selection.
  642. /// </summary>
  643. /// <value><c>true</c> if allows multiple selection; otherwise, <c>false</c>, defaults to false.</value>
  644. public bool AllowsMultipleSelection {
  645. get => dirListView.allowsMultipleSelection;
  646. set {
  647. dirListView.allowsMultipleSelection = value;
  648. dirListView.Reload ();
  649. }
  650. }
  651. /// <summary>
  652. /// Returns the selected files, or an empty list if nothing has been selected
  653. /// </summary>
  654. /// <value>The file paths.</value>
  655. public IReadOnlyList<string> FilePaths {
  656. get => dirListView.FilePaths;
  657. }
  658. }
  659. }