DialogsPage.xaml.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. using System;
  2. using System.Buffers;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Threading.Tasks;
  7. using Avalonia;
  8. using Avalonia.Controls;
  9. using Avalonia.Controls.Presenters;
  10. using Avalonia.Dialogs;
  11. using Avalonia.Layout;
  12. using Avalonia.Markup.Xaml;
  13. using Avalonia.Platform.Storage;
  14. using Avalonia.Platform.Storage.FileIO;
  15. #pragma warning disable CS0618 // Type or member is obsolete
  16. #nullable enable
  17. namespace ControlCatalog.Pages
  18. {
  19. public class DialogsPage : UserControl
  20. {
  21. public DialogsPage()
  22. {
  23. this.InitializeComponent();
  24. var results = this.Get<ItemsPresenter>("PickerLastResults");
  25. var resultsVisible = this.Get<TextBlock>("PickerLastResultsVisible");
  26. var bookmarkContainer = this.Get<TextBox>("BookmarkContainer");
  27. var openedFileContent = this.Get<TextBox>("OpenedFileContent");
  28. var openMultiple = this.Get<CheckBox>("OpenMultiple");
  29. IStorageFolder? lastSelectedDirectory = null;
  30. List<FileDialogFilter> GetFilters()
  31. {
  32. if (this.Get<CheckBox>("UseFilters").IsChecked != true)
  33. return new List<FileDialogFilter>();
  34. return new List<FileDialogFilter>
  35. {
  36. new FileDialogFilter
  37. {
  38. Name = "Text files (.txt)", Extensions = new List<string> {"txt"}
  39. },
  40. new FileDialogFilter
  41. {
  42. Name = "All files",
  43. Extensions = new List<string> {"*"}
  44. }
  45. };
  46. }
  47. List<FilePickerFileType>? GetFileTypes()
  48. {
  49. if (this.Get<CheckBox>("UseFilters").IsChecked != true)
  50. return null;
  51. return new List<FilePickerFileType>
  52. {
  53. FilePickerFileTypes.All,
  54. FilePickerFileTypes.TextPlain
  55. };
  56. }
  57. this.Get<Button>("OpenFile").Click += async delegate
  58. {
  59. // Almost guaranteed to exist
  60. var uri = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName;
  61. var initialFileName = uri == null ? null : System.IO.Path.GetFileName(uri);
  62. var initialDirectory = uri == null ? null : System.IO.Path.GetDirectoryName(uri);
  63. var result = await new OpenFileDialog()
  64. {
  65. Title = "Open file",
  66. Filters = GetFilters(),
  67. Directory = initialDirectory,
  68. InitialFileName = initialFileName
  69. }.ShowAsync(GetWindow());
  70. results.Items = result;
  71. resultsVisible.IsVisible = result?.Any() == true;
  72. };
  73. this.Get<Button>("OpenMultipleFiles").Click += async delegate
  74. {
  75. var result = await new OpenFileDialog()
  76. {
  77. Title = "Open multiple files",
  78. Filters = GetFilters(),
  79. Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null,
  80. AllowMultiple = true
  81. }.ShowAsync(GetWindow());
  82. results.Items = result;
  83. resultsVisible.IsVisible = result?.Any() == true;
  84. };
  85. this.Get<Button>("SaveFile").Click += async delegate
  86. {
  87. var filters = GetFilters();
  88. var result = await new SaveFileDialog()
  89. {
  90. Title = "Save file",
  91. Filters = filters,
  92. Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null,
  93. DefaultExtension = filters?.Any() == true ? "txt" : null,
  94. InitialFileName = "test.txt"
  95. }.ShowAsync(GetWindow());
  96. results.Items = new[] { result };
  97. resultsVisible.IsVisible = result != null;
  98. };
  99. this.Get<Button>("SelectFolder").Click += async delegate
  100. {
  101. var result = await new OpenFolderDialog()
  102. {
  103. Title = "Select folder",
  104. Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null
  105. }.ShowAsync(GetWindow());
  106. lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result));
  107. results.Items = new [] { result };
  108. resultsVisible.IsVisible = result != null;
  109. };
  110. this.Get<Button>("OpenBoth").Click += async delegate
  111. {
  112. var result = await new OpenFileDialog()
  113. {
  114. Title = "Select both",
  115. Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null,
  116. AllowMultiple = true
  117. }.ShowManagedAsync(GetWindow(), new ManagedFileDialogOptions
  118. {
  119. AllowDirectorySelection = true
  120. });
  121. results.Items = result;
  122. resultsVisible.IsVisible = result?.Any() == true;
  123. };
  124. this.Get<Button>("DecoratedWindow").Click += delegate
  125. {
  126. new DecoratedWindow().Show();
  127. };
  128. this.Get<Button>("DecoratedWindowDialog").Click += delegate
  129. {
  130. _ = new DecoratedWindow().ShowDialog(GetWindow());
  131. };
  132. this.Get<Button>("Dialog").Click += delegate
  133. {
  134. var window = CreateSampleWindow();
  135. window.Height = 200;
  136. _ = window.ShowDialog(GetWindow());
  137. };
  138. this.Get<Button>("DialogNoTaskbar").Click += delegate
  139. {
  140. var window = CreateSampleWindow();
  141. window.Height = 200;
  142. window.ShowInTaskbar = false;
  143. _ = window.ShowDialog(GetWindow());
  144. };
  145. this.Get<Button>("OwnedWindow").Click += delegate
  146. {
  147. var window = CreateSampleWindow();
  148. window.Show(GetWindow());
  149. };
  150. this.Get<Button>("OwnedWindowNoTaskbar").Click += delegate
  151. {
  152. var window = CreateSampleWindow();
  153. window.ShowInTaskbar = false;
  154. window.Show(GetWindow());
  155. };
  156. this.Get<Button>("OpenFilePicker").Click += async delegate
  157. {
  158. var result = await GetStorageProvider().OpenFilePickerAsync(new FilePickerOpenOptions()
  159. {
  160. Title = "Open file",
  161. FileTypeFilter = GetFileTypes(),
  162. SuggestedStartLocation = lastSelectedDirectory,
  163. AllowMultiple = openMultiple.IsChecked == true
  164. });
  165. await SetPickerResult(result);
  166. };
  167. this.Get<Button>("SaveFilePicker").Click += async delegate
  168. {
  169. var fileTypes = GetFileTypes();
  170. var file = await GetStorageProvider().SaveFilePickerAsync(new FilePickerSaveOptions()
  171. {
  172. Title = "Save file",
  173. FileTypeChoices = fileTypes,
  174. SuggestedStartLocation = lastSelectedDirectory,
  175. SuggestedFileName = "FileName",
  176. DefaultExtension = fileTypes?.Any() == true ? "txt" : null,
  177. ShowOverwritePrompt = false
  178. });
  179. if (file is not null && file.CanOpenWrite)
  180. {
  181. // Sync disposal of StreamWriter is not supported on WASM
  182. #if NET6_0_OR_GREATER
  183. await using var stream = await file.OpenWrite();
  184. await using var reader = new System.IO.StreamWriter(stream);
  185. #else
  186. using var stream = await file.OpenWrite();
  187. using var reader = new System.IO.StreamWriter(stream);
  188. #endif
  189. await reader.WriteLineAsync(openedFileContent.Text);
  190. lastSelectedDirectory = await file.GetParentAsync();
  191. }
  192. await SetPickerResult(file is null ? null : new [] {file});
  193. };
  194. this.Get<Button>("OpenFolderPicker").Click += async delegate
  195. {
  196. var folders = await GetStorageProvider().OpenFolderPickerAsync(new FolderPickerOpenOptions()
  197. {
  198. Title = "Folder file",
  199. SuggestedStartLocation = lastSelectedDirectory,
  200. AllowMultiple = openMultiple.IsChecked == true
  201. });
  202. await SetPickerResult(folders);
  203. lastSelectedDirectory = folders.FirstOrDefault();
  204. };
  205. this.Get<Button>("OpenFileFromBookmark").Click += async delegate
  206. {
  207. var file = bookmarkContainer.Text is not null
  208. ? await GetStorageProvider().OpenFileBookmarkAsync(bookmarkContainer.Text)
  209. : null;
  210. await SetPickerResult(file is null ? null : new[] { file });
  211. };
  212. this.Get<Button>("OpenFolderFromBookmark").Click += async delegate
  213. {
  214. var folder = bookmarkContainer.Text is not null
  215. ? await GetStorageProvider().OpenFolderBookmarkAsync(bookmarkContainer.Text)
  216. : null;
  217. await SetPickerResult(folder is null ? null : new[] { folder });
  218. lastSelectedDirectory = folder;
  219. };
  220. async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
  221. {
  222. items ??= Array.Empty<IStorageItem>();
  223. bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmark() : "Can't bookmark";
  224. var mappedResults = new List<string>();
  225. if (items.FirstOrDefault() is IStorageItem item)
  226. {
  227. var resultText = item is IStorageFile ? "File:" : "Folder:";
  228. resultText += Environment.NewLine;
  229. var props = await item.GetBasicPropertiesAsync();
  230. resultText += @$"Size: {props.Size}
  231. DateCreated: {props.DateCreated}
  232. DateModified: {props.DateModified}
  233. CanBookmark: {item.CanBookmark}
  234. ";
  235. if (item is IStorageFile file)
  236. {
  237. resultText += @$"
  238. CanOpenRead: {file.CanOpenRead}
  239. CanOpenWrite: {file.CanOpenWrite}
  240. Content:
  241. ";
  242. if (file.CanOpenRead)
  243. {
  244. #if NET6_0_OR_GREATER
  245. await using var stream = await file.OpenRead();
  246. #else
  247. using var stream = await file.OpenRead();
  248. #endif
  249. using var reader = new System.IO.StreamReader(stream);
  250. // 4GB file test, shouldn't load more than 10000 chars into a memory.
  251. const int length = 10000;
  252. var buffer = ArrayPool<char>.Shared.Rent(length);
  253. try
  254. {
  255. var charsRead = await reader.ReadAsync(buffer, 0, length);
  256. resultText += new string(buffer, 0, charsRead);
  257. }
  258. finally
  259. {
  260. ArrayPool<char>.Shared.Return(buffer);
  261. }
  262. }
  263. }
  264. openedFileContent.Text = resultText;
  265. lastSelectedDirectory = await item.GetParentAsync();
  266. if (lastSelectedDirectory is not null)
  267. {
  268. mappedResults.Add(FullPathOrName(lastSelectedDirectory));
  269. }
  270. foreach (var selectedItem in items)
  271. {
  272. mappedResults.Add("+> " + FullPathOrName(selectedItem));
  273. if (selectedItem is IStorageFolder folder)
  274. {
  275. foreach (var innerItems in await folder.GetItemsAsync())
  276. {
  277. mappedResults.Add("++> " + FullPathOrName(innerItems));
  278. }
  279. }
  280. }
  281. }
  282. results.Items = mappedResults;
  283. resultsVisible.IsVisible = mappedResults.Any();
  284. }
  285. }
  286. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  287. {
  288. base.OnAttachedToVisualTree(e);
  289. var openedFileContent = this.Get<TextBox>("OpenedFileContent");
  290. try
  291. {
  292. var storageProvider = GetStorageProvider();
  293. openedFileContent.Text = $@"CanOpen: {storageProvider.CanOpen}
  294. CanSave: {storageProvider.CanSave}
  295. CanPickFolder: {storageProvider.CanPickFolder}";
  296. }
  297. catch (Exception ex)
  298. {
  299. openedFileContent.Text = "Storage provider is not available: " + ex.Message;
  300. }
  301. }
  302. private Window CreateSampleWindow()
  303. {
  304. Button button;
  305. Button dialogButton;
  306. var window = new Window
  307. {
  308. Height = 200,
  309. Width = 200,
  310. Content = new StackPanel
  311. {
  312. Spacing = 4,
  313. Children =
  314. {
  315. new TextBlock { Text = "Hello world!" },
  316. (button = new Button
  317. {
  318. HorizontalAlignment = HorizontalAlignment.Center,
  319. Content = "Click to close",
  320. IsDefault = true
  321. }),
  322. (dialogButton = new Button
  323. {
  324. HorizontalAlignment = HorizontalAlignment.Center,
  325. Content = "Dialog",
  326. IsDefault = false
  327. })
  328. }
  329. },
  330. WindowStartupLocation = WindowStartupLocation.CenterOwner
  331. };
  332. button.Click += (_, __) => window.Close();
  333. dialogButton.Click += (_, __) =>
  334. {
  335. var dialog = CreateSampleWindow();
  336. dialog.Height = 200;
  337. dialog.ShowDialog(window);
  338. };
  339. return window;
  340. }
  341. private IStorageProvider GetStorageProvider()
  342. {
  343. var forceManaged = this.Get<CheckBox>("ForceManaged").IsChecked ?? false;
  344. return forceManaged
  345. ? new ManagedStorageProvider<Window>(GetWindow(), null)
  346. : GetTopLevel().StorageProvider;
  347. }
  348. private static string FullPathOrName(IStorageItem? item)
  349. {
  350. if (item is null) return "(null)";
  351. return item.TryGetUri(out var uri) ? uri.ToString() : item.Name;
  352. }
  353. Window GetWindow() => this.VisualRoot as Window ?? throw new NullReferenceException("Invalid Owner");
  354. TopLevel GetTopLevel() => this.VisualRoot as TopLevel ?? throw new NullReferenceException("Invalid Owner");
  355. private void InitializeComponent()
  356. {
  357. AvaloniaXamlLoader.Load(this);
  358. }
  359. }
  360. }
  361. #pragma warning restore CS0618 // Type or member is obsolete