From 44c41147fbe4fe4cb9bad459f75d51a084b4aadc Mon Sep 17 00:00:00 2001 From: Morten Nielsen Date: Tue, 4 Jun 2024 13:03:20 -0700 Subject: [PATCH] Added popupviewer attachment click event (#575) * Added attachment click event Also added ability to just trigger download, and make sure icons reflect current load status --- .../PopupViewer/PopupViewerSample.xaml | 2 +- .../PopupViewer/PopupViewerSample.xaml.cs | 30 +++++++++++ .../PopupViewer/PopupViewer.Theme.xaml | 47 +++++++++++++++- .../AttachmentsPopupElementView.Maui.cs | 34 ------------ .../AttachmentsPopupElementView.Windows.cs | 23 +++----- .../AttachmentsPopupElementView.cs | 50 +++++++++++++++++ .../Controls/PopupViewer/PopupViewer.Maui.cs | 53 ------------------- .../UI/Controls/PopupViewer/PopupViewer.cs | 53 +++++++++++++++++++ 8 files changed, 186 insertions(+), 106 deletions(-) diff --git a/src/Samples/Toolkit.SampleApp.WPF/Samples/PopupViewer/PopupViewerSample.xaml b/src/Samples/Toolkit.SampleApp.WPF/Samples/PopupViewer/PopupViewerSample.xaml index cfecda8b8..b1ba464ba 100644 --- a/src/Samples/Toolkit.SampleApp.WPF/Samples/PopupViewer/PopupViewerSample.xaml +++ b/src/Samples/Toolkit.SampleApp.WPF/Samples/PopupViewer/PopupViewerSample.xaml @@ -14,7 +14,7 @@ - + diff --git a/src/Samples/Toolkit.SampleApp.WPF/Samples/PopupViewer/PopupViewerSample.xaml.cs b/src/Samples/Toolkit.SampleApp.WPF/Samples/PopupViewer/PopupViewerSample.xaml.cs index 54ac420df..7dc8f9fad 100644 --- a/src/Samples/Toolkit.SampleApp.WPF/Samples/PopupViewer/PopupViewerSample.xaml.cs +++ b/src/Samples/Toolkit.SampleApp.WPF/Samples/PopupViewer/PopupViewerSample.xaml.cs @@ -124,5 +124,35 @@ private void PopupBackground_MouseDown(object sender, System.Windows.Input.Mouse PopupBackground.Visibility = Visibility.Collapsed; popupViewer.Popup = null; } + + private async void popupViewer_PopupAttachmentClicked(object sender, UI.Controls.PopupAttachmentClickedEventArgs e) + { + if (!e.Attachment.IsLocal) // Attachment hasn't been downloaded + { + try + { + // Make first click just load the attachment (or cancel a loading operation). Otherwise fallback to default behavior + if (e.Attachment.LoadStatus == LoadStatus.NotLoaded) + { + e.Handled = true; + await e.Attachment.LoadAsync(); + } + else if (e.Attachment.LoadStatus == LoadStatus.FailedToLoad) + { + e.Handled = true; + await e.Attachment.RetryLoadAsync(); + } + else if (e.Attachment.LoadStatus == LoadStatus.Loading) + { + e.Handled = true; + e.Attachment.CancelLoad(); + } + } + catch (Exception ex) + { + MessageBox.Show("Failed to download attachment", ex.Message); + } + } + } } } \ No newline at end of file diff --git a/src/Toolkit/Toolkit.WPF/UI/Controls/PopupViewer/PopupViewer.Theme.xaml b/src/Toolkit/Toolkit.WPF/UI/Controls/PopupViewer/PopupViewer.Theme.xaml index d5c49439a..598ac999e 100644 --- a/src/Toolkit/Toolkit.WPF/UI/Controls/PopupViewer/PopupViewer.Theme.xaml +++ b/src/Toolkit/Toolkit.WPF/UI/Controls/PopupViewer/PopupViewer.Theme.xaml @@ -57,12 +57,57 @@ + + + - + + + + diff --git a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.Maui.cs b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.Maui.cs index a58c70802..a99208694 100644 --- a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.Maui.cs +++ b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.Maui.cs @@ -152,40 +152,6 @@ private static void Attachment_Tapped(object? sender, EventArgs e) return parent as PopupViewer; } - /// - /// Occurs when an attachment is clicked. - /// - /// - /// Override this to prevent the default open action. - /// Attachment clicked. - public virtual async void OnAttachmentClicked(PopupAttachment attachment) - { - if (attachment.LoadStatus == LoadStatus.NotLoaded) - { - _ = attachment.LoadAsync(); - return; - } - if (attachment.LoadStatus == LoadStatus.Loaded && attachment.Attachment != null) - { - var viewer = GetPopupViewerParent(); - if(viewer is not null) - { - bool handled = viewer.OnPopupAttachmentClicked(attachment); - if (handled) - return; - } - - try - { - await Microsoft.Maui.ApplicationModel.Launcher.Default.OpenAsync( - new Microsoft.Maui.ApplicationModel.OpenFileRequest(attachment.Name, new ReadOnlyFile(attachment.Filename!, attachment.ContentType))); - } - catch - { - } - } - } - private class AttachmentViewModel : System.ComponentModel.INotifyPropertyChanged { public AttachmentViewModel(PopupAttachment attachment) diff --git a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.Windows.cs b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.Windows.cs index 32ef069bb..9a9f5c895 100644 --- a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.Windows.cs +++ b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.Windows.cs @@ -30,26 +30,15 @@ public partial class AttachmentsPopupElementView : Control { private const string AttachmentListName = "AttachmentList"; - /// - /// Occurs when an attachment is clicked. - /// - /// Override this to prevent the default "save to file dialog" action. - /// Attachment clicked. - public virtual async void OnAttachmentClicked(PopupAttachment attachment) + private UI.Controls.PopupViewer? GetPopupViewerParent() { - SaveFileDialog saveFileDialog = new SaveFileDialog(); - saveFileDialog.FileName = attachment.Name; - if (saveFileDialog.ShowDialog() == true) + var parent = VisualTreeHelper.GetParent(this); + while (parent is not null && parent is not UI.Controls.PopupViewer popup) { - try - { - using var stream = await attachment.Attachment!.GetDataAsync(); - using var outfile = saveFileDialog.OpenFile(); - await stream.CopyToAsync(outfile); - } - catch { } + parent = VisualTreeHelper.GetParent(parent); } + return parent as UI.Controls.PopupViewer; } } } -#endif \ No newline at end of file +#endif diff --git a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.cs b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.cs index ef27677e5..3b9a17405 100644 --- a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.cs +++ b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/AttachmentsPopupElementView.cs @@ -126,6 +126,56 @@ public AttachmentsPopupElement? Element /// public static readonly DependencyProperty ElementProperty = PropertyHelper.CreateProperty(nameof(Element), null, (s, oldValue, newValue) => s.LoadAttachments()); + + + /// + /// Occurs when an attachment is clicked. + /// + /// + /// Override this to prevent the default open action. + /// Attachment clicked. + public virtual async void OnAttachmentClicked(PopupAttachment attachment) + { + if (attachment.Attachment != null) + { + var viewer = GetPopupViewerParent(); + if (viewer is not null) + { + bool handled = viewer.OnPopupAttachmentClicked(attachment); + if (handled) + return; + } +#if MAUI + try + { + if (attachment.LoadStatus == LoadStatus.NotLoaded) + await attachment.LoadAsync(); + await Microsoft.Maui.ApplicationModel.Launcher.Default.OpenAsync( + new Microsoft.Maui.ApplicationModel.OpenFileRequest(attachment.Name, new ReadOnlyFile(attachment.Filename!, attachment.ContentType))); + } + catch(System.Exception ex) + { + System.Diagnostics.Trace.WriteLine($"Failed to open attachment: " + ex.Message); + } +#else + SaveFileDialog saveFileDialog = new SaveFileDialog(); + saveFileDialog.FileName = attachment.Name; + if (saveFileDialog.ShowDialog() == true) + { + try + { + using var stream = await attachment.Attachment!.GetDataAsync(); + using var outfile = saveFileDialog.OpenFile(); + await stream.CopyToAsync(outfile); + } + catch (System.Exception ex) + { + System.Diagnostics.Trace.WriteLine($"Failed to save file to disk: " + ex.Message); + } + } +#endif + } + } } } #endif \ No newline at end of file diff --git a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.Maui.cs b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.Maui.cs index bfb389ab5..64313a67d 100644 --- a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.Maui.cs +++ b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.Maui.cs @@ -107,59 +107,6 @@ internal static Style GetStyle(string resourceKey, Style defaultStyle) internal static Style GetPopupViewerTitleStyle() => GetStyle(PopupViewerTitleStyleName, DefaultPopupViewerTitleStyle); internal static Style GetPopupViewerCaptionStyle() => GetStyle(PopupViewerCaptionStyleName, DefaultPopupViewerCaptionStyle); - - /// - /// Raised when a loaded popup attachment is clicked - /// - /// - /// By default, when an attachment is clicked, the default application for the file type (if any) is launched. To override this, - /// listen to this event, set the property to true and perform - /// your own logic. - /// - /// Example: Use the .NET MAUI share API for the attachment: - /// - /// private async void PopupAttachmentClicked(object sender, PopupAttachmentClickedEventArgs e) - /// { - /// e.Handled = true; // Prevent default launch action - /// await Share.Default.RequestAsync(new ShareFileRequest(new ReadOnlyFile(e.Attachment.Filename!, e.Attachment.ContentType))); - /// } - /// - /// - /// - public event EventHandler? PopupAttachmentClicked; - - internal bool OnPopupAttachmentClicked(PopupAttachment attachment) - { - var handler = PopupAttachmentClicked; - if (handler is not null) - { - var args = new PopupAttachmentClickedEventArgs(attachment); - PopupAttachmentClicked?.Invoke(this, args); - return args.Handled; - } - return false; - } - } - - /// - /// Event argument for the event. - /// - public class PopupAttachmentClickedEventArgs : EventArgs - { - internal PopupAttachmentClickedEventArgs(PopupAttachment attachment) - { - Attachment = attachment; - } - - /// - /// Gets or sets a value indicating whether the event handler has handled the event and the default action should be prevented. - /// - public bool Handled { get; set; } - - /// - /// Gets the attachment that was clicked. - /// - public PopupAttachment Attachment { get; } } } #endif \ No newline at end of file diff --git a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.cs b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.cs index 37a2721b7..6721adcc8 100644 --- a/src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.cs +++ b/src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.cs @@ -217,6 +217,59 @@ public ScrollBarVisibility VerticalScrollBarVisibility ScrollBarVisibility.Auto #endif ); + + /// + /// Raised when a popup attachment is clicked + /// + /// + /// By default, when an attachment is clicked, the default application for the file type (if any) is launched. To override this, + /// listen to this event, set the property to true and perform + /// your own logic. + /// + /// Example: Use the .NET MAUI share API for the attachment: + /// + /// private async void PopupAttachmentClicked(object sender, PopupAttachmentClickedEventArgs e) + /// { + /// e.Handled = true; // Prevent default launch action + /// await Share.Default.RequestAsync(new ShareFileRequest(new ReadOnlyFile(e.Attachment.Filename!, e.Attachment.ContentType))); + /// } + /// + /// + /// + public event EventHandler? PopupAttachmentClicked; + + internal bool OnPopupAttachmentClicked(PopupAttachment attachment) + { + var handler = PopupAttachmentClicked; + if (handler is not null) + { + var args = new PopupAttachmentClickedEventArgs(attachment); + PopupAttachmentClicked?.Invoke(this, args); + return args.Handled; + } + return false; + } + } + + /// + /// Event argument for the event. + /// + public class PopupAttachmentClickedEventArgs : EventArgs + { + internal PopupAttachmentClickedEventArgs(PopupAttachment attachment) + { + Attachment = attachment; + } + + /// + /// Gets or sets a value indicating whether the event handler has handled the event and the default action should be prevented. + /// + public bool Handled { get; set; } + + /// + /// Gets the attachment that was clicked. + /// + public PopupAttachment Attachment { get; } } } #endif \ No newline at end of file