DialogsPage.xaml.cs 19 KB

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