DialogsPage.xaml.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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. IStorageFolder? lastSelectedDirectory = null;
  25. bool ignoreTextChanged = false;
  26. var results = this.Get<ItemsControl>("PickerLastResults");
  27. var resultsVisible = this.Get<TextBlock>("PickerLastResultsVisible");
  28. var bookmarkContainer = this.Get<TextBox>("BookmarkContainer");
  29. var openedFileContent = this.Get<TextBox>("OpenedFileContent");
  30. var openMultiple = this.Get<CheckBox>("OpenMultiple");
  31. var currentFolderBox = this.Get<AutoCompleteBox>("CurrentFolderBox");
  32. currentFolderBox.TextChanged += async (sender, args) =>
  33. {
  34. if (ignoreTextChanged) return;
  35. if (Enum.TryParse<WellKnownFolder>(currentFolderBox.Text, true, out var folderEnum))
  36. {
  37. lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolderAsync(folderEnum);
  38. }
  39. else
  40. {
  41. if (!Uri.TryCreate(currentFolderBox.Text, UriKind.Absolute, out var folderLink))
  42. {
  43. Uri.TryCreate("file://" + currentFolderBox.Text, UriKind.Absolute, out folderLink);
  44. }
  45. if (folderLink is not null)
  46. {
  47. lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink);
  48. }
  49. }
  50. };
  51. List<FileDialogFilter> GetFilters()
  52. {
  53. return GetFileTypes()?.Select(f => new FileDialogFilter
  54. {
  55. Name = f.Name, Extensions = f.Patterns!.ToList()
  56. }).ToList() ?? new List<FileDialogFilter>();
  57. }
  58. List<FilePickerFileType>? GetFileTypes()
  59. {
  60. var selectedItem = (this.Get<ComboBox>("FilterSelector").SelectedItem as ComboBoxItem)?.Content
  61. ?? "None";
  62. var binLogType = new FilePickerFileType("Binary Log")
  63. {
  64. Patterns = new[] { "*.binlog", "*.buildlog" },
  65. MimeTypes = new[] { "application/binlog", "application/buildlog" },
  66. AppleUniformTypeIdentifiers = new[] { "public.data" }
  67. };
  68. return selectedItem switch
  69. {
  70. "All + TXT + BinLog" => new List<FilePickerFileType>
  71. {
  72. FilePickerFileTypes.All, FilePickerFileTypes.TextPlain, binLogType
  73. },
  74. "Binlog" => new List<FilePickerFileType> { binLogType },
  75. "TXT extension only" => new List<FilePickerFileType>
  76. {
  77. new("TXT") { Patterns = FilePickerFileTypes.TextPlain.Patterns }
  78. },
  79. "TXT mime only" => new List<FilePickerFileType>
  80. {
  81. new("TXT") { MimeTypes = FilePickerFileTypes.TextPlain.MimeTypes }
  82. },
  83. "TXT apple type id only" => new List<FilePickerFileType>
  84. {
  85. new("TXT")
  86. {
  87. AppleUniformTypeIdentifiers =
  88. FilePickerFileTypes.TextPlain.AppleUniformTypeIdentifiers
  89. }
  90. },
  91. _ => null
  92. };
  93. }
  94. this.Get<Button>("OpenFile").Click += async delegate
  95. {
  96. // Almost guaranteed to exist
  97. var uri = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName;
  98. var initialFileName = uri == null ? null : System.IO.Path.GetFileName(uri);
  99. var initialDirectory = uri == null ? null : System.IO.Path.GetDirectoryName(uri);
  100. var result = await new OpenFileDialog()
  101. {
  102. Title = "Open file",
  103. Filters = GetFilters(),
  104. Directory = initialDirectory,
  105. InitialFileName = initialFileName
  106. }.ShowAsync(GetWindow());
  107. results.ItemsSource = result;
  108. resultsVisible.IsVisible = result?.Any() == true;
  109. };
  110. this.Get<Button>("OpenMultipleFiles").Click += async delegate
  111. {
  112. var result = await new OpenFileDialog()
  113. {
  114. Title = "Open multiple files",
  115. Filters = GetFilters(),
  116. Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
  117. AllowMultiple = true
  118. }.ShowAsync(GetWindow());
  119. results.ItemsSource = result;
  120. resultsVisible.IsVisible = result?.Any() == true;
  121. };
  122. this.Get<Button>("SaveFile").Click += async delegate
  123. {
  124. var filters = GetFilters();
  125. var result = await new SaveFileDialog()
  126. {
  127. Title = "Save file",
  128. Filters = filters,
  129. Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
  130. DefaultExtension = filters?.Any() == true ? "txt" : null,
  131. InitialFileName = "test.txt"
  132. }.ShowAsync(GetWindow());
  133. results.ItemsSource = new[] { result };
  134. resultsVisible.IsVisible = result != null;
  135. };
  136. this.Get<Button>("SelectFolder").Click += async delegate
  137. {
  138. var result = await new OpenFolderDialog()
  139. {
  140. Title = "Select folder",
  141. Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
  142. }.ShowAsync(GetWindow());
  143. if (string.IsNullOrEmpty(result))
  144. {
  145. resultsVisible.IsVisible = false;
  146. }
  147. else
  148. {
  149. SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result));
  150. results.ItemsSource = new[] { result };
  151. resultsVisible.IsVisible = true;
  152. }
  153. };
  154. this.Get<Button>("OpenBoth").Click += async delegate
  155. {
  156. var result = await new OpenFileDialog()
  157. {
  158. Title = "Select both",
  159. Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
  160. AllowMultiple = true
  161. }.ShowManagedAsync(GetWindow(), new ManagedFileDialogOptions
  162. {
  163. AllowDirectorySelection = true
  164. });
  165. results.ItemsSource = result;
  166. resultsVisible.IsVisible = result?.Any() == true;
  167. };
  168. this.Get<Button>("DecoratedWindow").Click += delegate
  169. {
  170. new DecoratedWindow().Show();
  171. };
  172. this.Get<Button>("DecoratedWindowDialog").Click += delegate
  173. {
  174. _ = new DecoratedWindow().ShowDialog(GetWindow());
  175. };
  176. this.Get<Button>("Dialog").Click += delegate
  177. {
  178. var window = CreateSampleWindow();
  179. window.Height = 200;
  180. _ = window.ShowDialog(GetWindow());
  181. };
  182. this.Get<Button>("DialogNoTaskbar").Click += delegate
  183. {
  184. var window = CreateSampleWindow();
  185. window.Height = 200;
  186. window.ShowInTaskbar = false;
  187. _ = window.ShowDialog(GetWindow());
  188. };
  189. this.Get<Button>("OwnedWindow").Click += delegate
  190. {
  191. var window = CreateSampleWindow();
  192. window.Show(GetWindow());
  193. };
  194. this.Get<Button>("OwnedWindowNoTaskbar").Click += delegate
  195. {
  196. var window = CreateSampleWindow();
  197. window.ShowInTaskbar = false;
  198. window.Show(GetWindow());
  199. };
  200. this.Get<Button>("OpenFilePicker").Click += async delegate
  201. {
  202. var result = await GetStorageProvider().OpenFilePickerAsync(new FilePickerOpenOptions()
  203. {
  204. Title = "Open file",
  205. FileTypeFilter = GetFileTypes(),
  206. SuggestedStartLocation = lastSelectedDirectory,
  207. AllowMultiple = openMultiple.IsChecked == true
  208. });
  209. await SetPickerResult(result);
  210. };
  211. this.Get<Button>("SaveFilePicker").Click += async delegate
  212. {
  213. var fileTypes = GetFileTypes();
  214. var file = await GetStorageProvider().SaveFilePickerAsync(new FilePickerSaveOptions()
  215. {
  216. Title = "Save file",
  217. FileTypeChoices = fileTypes,
  218. SuggestedStartLocation = lastSelectedDirectory,
  219. SuggestedFileName = "FileName",
  220. DefaultExtension = fileTypes?.Any() == true ? "txt" : null,
  221. ShowOverwritePrompt = false
  222. });
  223. if (file is not null)
  224. {
  225. // Sync disposal of StreamWriter is not supported on WASM
  226. #if NET6_0_OR_GREATER
  227. await using var stream = await file.OpenWriteAsync();
  228. await using var reader = new System.IO.StreamWriter(stream);
  229. #else
  230. using var stream = await file.OpenWriteAsync();
  231. using var reader = new System.IO.StreamWriter(stream);
  232. #endif
  233. await reader.WriteLineAsync(openedFileContent.Text);
  234. SetFolder(await file.GetParentAsync());
  235. }
  236. await SetPickerResult(file is null ? null : new[] { file });
  237. };
  238. this.Get<Button>("OpenFolderPicker").Click += async delegate
  239. {
  240. var folders = await GetStorageProvider().OpenFolderPickerAsync(new FolderPickerOpenOptions()
  241. {
  242. Title = "Folder file",
  243. SuggestedStartLocation = lastSelectedDirectory,
  244. AllowMultiple = openMultiple.IsChecked == true
  245. });
  246. await SetPickerResult(folders);
  247. SetFolder(folders.FirstOrDefault());
  248. };
  249. this.Get<Button>("OpenFileFromBookmark").Click += async delegate
  250. {
  251. var file = bookmarkContainer.Text is not null
  252. ? await GetStorageProvider().OpenFileBookmarkAsync(bookmarkContainer.Text)
  253. : null;
  254. await SetPickerResult(file is null ? null : new[] { file });
  255. };
  256. this.Get<Button>("OpenFolderFromBookmark").Click += async delegate
  257. {
  258. var folder = bookmarkContainer.Text is not null
  259. ? await GetStorageProvider().OpenFolderBookmarkAsync(bookmarkContainer.Text)
  260. : null;
  261. await SetPickerResult(folder is null ? null : new[] { folder });
  262. SetFolder(folder);
  263. };
  264. void SetFolder(IStorageFolder? folder)
  265. {
  266. ignoreTextChanged = true;
  267. lastSelectedDirectory = folder;
  268. currentFolderBox.Text = folder?.Path is { IsAbsoluteUri: true } abs ? abs.LocalPath : folder?.Path?.ToString();
  269. ignoreTextChanged = false;
  270. }
  271. async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
  272. {
  273. items ??= Array.Empty<IStorageItem>();
  274. bookmarkContainer.Text = items.FirstOrDefault(f => f.CanBookmark) is { } f ? await f.SaveBookmarkAsync() : "Can't bookmark";
  275. var mappedResults = new List<string>();
  276. if (items.FirstOrDefault() is IStorageItem item)
  277. {
  278. var resultText = item is IStorageFile ? "File:" : "Folder:";
  279. resultText += Environment.NewLine;
  280. var props = await item.GetBasicPropertiesAsync();
  281. resultText += @$"Size: {props.Size}
  282. DateCreated: {props.DateCreated}
  283. DateModified: {props.DateModified}
  284. CanBookmark: {item.CanBookmark}
  285. ";
  286. if (item is IStorageFile file)
  287. {
  288. resultText += @$"
  289. Content:
  290. ";
  291. resultText += await ReadTextFromFile(file, 500);
  292. }
  293. openedFileContent.Text = resultText;
  294. var parent = await item.GetParentAsync();
  295. SetFolder(parent);
  296. if (parent is not null)
  297. {
  298. mappedResults.Add(FullPathOrName(parent));
  299. }
  300. foreach (var selectedItem in items)
  301. {
  302. mappedResults.Add("+> " + FullPathOrName(selectedItem));
  303. if (selectedItem is IStorageFolder folder)
  304. {
  305. await foreach (var innerItem in folder.GetItemsAsync())
  306. {
  307. mappedResults.Add("++> " + FullPathOrName(innerItem));
  308. }
  309. }
  310. }
  311. }
  312. results.ItemsSource = mappedResults;
  313. resultsVisible.IsVisible = mappedResults.Any();
  314. }
  315. }
  316. public static async Task<string> ReadTextFromFile(IStorageFile file, int length)
  317. {
  318. #if NET6_0_OR_GREATER
  319. await using var stream = await file.OpenReadAsync();
  320. #else
  321. using var stream = await file.OpenReadAsync();
  322. #endif
  323. using var reader = new System.IO.StreamReader(stream);
  324. // 4GB file test, shouldn't load more than 10000 chars into a memory.
  325. var buffer = ArrayPool<char>.Shared.Rent(length);
  326. try
  327. {
  328. var charsRead = await reader.ReadAsync(buffer, 0, length);
  329. return new string(buffer, 0, charsRead);
  330. }
  331. finally
  332. {
  333. ArrayPool<char>.Shared.Return(buffer);
  334. }
  335. }
  336. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  337. {
  338. base.OnAttachedToVisualTree(e);
  339. var openedFileContent = this.Get<TextBox>("OpenedFileContent");
  340. try
  341. {
  342. var storageProvider = GetStorageProvider();
  343. openedFileContent.Text = $@"CanOpen: {storageProvider.CanOpen}
  344. CanSave: {storageProvider.CanSave}
  345. CanPickFolder: {storageProvider.CanPickFolder}";
  346. }
  347. catch (Exception ex)
  348. {
  349. openedFileContent.Text = "Storage provider is not available: " + ex.Message;
  350. }
  351. }
  352. private Window CreateSampleWindow()
  353. {
  354. Button button;
  355. Button dialogButton;
  356. var window = new Window
  357. {
  358. Height = 200,
  359. Width = 200,
  360. Content = new StackPanel
  361. {
  362. Spacing = 4,
  363. Children =
  364. {
  365. new TextBlock { Text = "Hello world!" },
  366. (button = new Button
  367. {
  368. HorizontalAlignment = HorizontalAlignment.Center,
  369. Content = "Click to close",
  370. IsDefault = true
  371. }),
  372. (dialogButton = new Button
  373. {
  374. HorizontalAlignment = HorizontalAlignment.Center,
  375. Content = "Dialog",
  376. IsDefault = false
  377. })
  378. }
  379. },
  380. WindowStartupLocation = WindowStartupLocation.CenterOwner
  381. };
  382. button.Click += (_, __) => window.Close();
  383. dialogButton.Click += (_, __) =>
  384. {
  385. var dialog = CreateSampleWindow();
  386. dialog.Height = 200;
  387. dialog.ShowDialog(window);
  388. };
  389. return window;
  390. }
  391. private IStorageProvider GetStorageProvider()
  392. {
  393. var forceManaged = this.Get<CheckBox>("ForceManaged").IsChecked ?? false;
  394. return forceManaged
  395. ? new ManagedStorageProvider<Window>(GetWindow(), null)
  396. : GetTopLevel().StorageProvider;
  397. }
  398. private static string FullPathOrName(IStorageItem? item)
  399. {
  400. if (item is null) return "(null)";
  401. return item.Path is { IsAbsoluteUri: true } path ? path.ToString() : item.Name;
  402. }
  403. Window GetWindow() => TopLevel.GetTopLevel(this) as Window ?? throw new NullReferenceException("Invalid Owner");
  404. TopLevel GetTopLevel() => TopLevel.GetTopLevel(this) ?? throw new NullReferenceException("Invalid Owner");
  405. private void InitializeComponent()
  406. {
  407. AvaloniaXamlLoader.Load(this);
  408. }
  409. }
  410. }
  411. #pragma warning restore CS0618 // Type or member is obsolete