FileDialog.cs 19 KB

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