Skip to content

Commit

Permalink
Added popupviewer attachment click event (#575)
Browse files Browse the repository at this point in the history
* Added attachment click event

Also added ability to just trigger download, and make sure icons reflect current load status
  • Loading branch information
dotMorten authored Jun 4, 2024
1 parent a6ee2c2 commit 44c4114
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<Grid Background="#AA333333" Visibility="Collapsed" x:Name="PopupBackground" MouseDown="PopupBackground_MouseDown">
<Border BorderBrush="Black" BorderThickness="1" Background="White" HorizontalAlignment="Center" VerticalAlignment="Center" >
<esri:PopupViewer x:Name="popupViewer" Margin="5" Width="400" MaxHeight="400" />
<esri:PopupViewer x:Name="popupViewer" Margin="5" Width="400" MaxHeight="400" PopupAttachmentClicked="popupViewer_PopupAttachmentClicked" />
</Border>
</Grid>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,57 @@
<Setter.Value>
<ControlTemplate TargetType="{x:Type primitives:AttachmentsPopupElementView}">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="TextBlock" x:Key="DownloadIcon" xmlns:es="clr-namespace:Esri.ArcGISRuntime;assembly=Esri.ArcGISRuntime">
<Setter Property="FontFamily" Value="Segoe MDL2 Assets" />
<Setter Property="FontSize" Value="18" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,0,5,0" />
<Setter Property="Text" Value="&#xF13E;" />
<Setter Property="RenderTransformOrigin" Value=".5,.5" />
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform />
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding LoadStatus}" Value="{x:Static es:LoadStatus.Loading}">
<Setter Property="Text" Value="&#xE72C;"/>
<Setter Property="ToolTip" Value="Downloading"/>
<DataTrigger.EnterActions>
<BeginStoryboard Name="SpinnerStoryboard">
<Storyboard RepeatBehavior="Forever" >
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Angle"
Duration="0:0:1" From="0" To="360" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="SpinnerStoryboard" />
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding LoadStatus}" Value="{x:Static es:LoadStatus.NotLoaded}">
<Setter Property="Text" Value="&#xE896;"/>
</DataTrigger>
<DataTrigger Binding="{Binding LoadStatus}" Value="{x:Static es:LoadStatus.FailedToLoad}">
<Setter Property="Text" Value="&#xEA6A;"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsLocal}" Value="true">
<Setter Property="Text" Value="&#xF13E;"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding Title, Mode=OneWay}" Style="{StaticResource PopupViewerTitleStyle}" Visibility="{Binding Title, Converter={StaticResource PopupViewerVisibilityConverter}}" />
<TextBlock Text="{Binding Description, Mode=OneWay}" Style="{StaticResource PopupViewerCaptionStyle}" Visibility="{Binding Description, Converter={StaticResource PopupViewerVisibilityConverter}}" />
<ListView x:Name="AttachmentList" BorderThickness="0" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Margin="5" Cursor="Hand"/>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource DownloadIcon}"/>
<TextBlock Text="{Binding Name}" Margin="5" Cursor="Hand"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,40 +152,6 @@ private static void Attachment_Tapped(object? sender, EventArgs e)
return parent as PopupViewer;
}

/// <summary>
/// Occurs when an attachment is clicked.
/// </summary>
/// <remarks>
/// <para>Override this to prevent the default open action.</para></remarks>
/// <param name="attachment">Attachment clicked.</param>
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,15 @@ public partial class AttachmentsPopupElementView : Control
{
private const string AttachmentListName = "AttachmentList";

/// <summary>
/// Occurs when an attachment is clicked.
/// </summary>
/// <remarks>Override this to prevent the default "save to file dialog" action.</remarks>
/// <param name="attachment">Attachment clicked.</param>
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
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,56 @@ public AttachmentsPopupElement? Element
/// </summary>
public static readonly DependencyProperty ElementProperty =
PropertyHelper.CreateProperty<AttachmentsPopupElement, AttachmentsPopupElementView>(nameof(Element), null, (s, oldValue, newValue) => s.LoadAttachments());


/// <summary>
/// Occurs when an attachment is clicked.
/// </summary>
/// <remarks>
/// <para>Override this to prevent the default open action.</para></remarks>
/// <param name="attachment">Attachment clicked.</param>
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
53 changes: 0 additions & 53 deletions src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.Maui.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/// <summary>
/// Raised when a loaded popup attachment is clicked
/// </summary>
/// <remarks>
/// <para>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 <see cref="PopupAttachmentClickedEventArgs.Handled"/> property to <c>true</c> and perform
/// your own logic. </para>
/// <example>
/// Example: Use the .NET MAUI share API for the attachment:
/// <code language="csharp">
/// 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)));
/// }
/// </code>
/// </example>
/// </remarks>
public event EventHandler<PopupAttachmentClickedEventArgs>? 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;
}
}

/// <summary>
/// Event argument for the <see cref="PopupViewer.PopupAttachmentClicked"/> event.
/// </summary>
public class PopupAttachmentClickedEventArgs : EventArgs
{
internal PopupAttachmentClickedEventArgs(PopupAttachment attachment)
{
Attachment = attachment;
}

/// <summary>
/// Gets or sets a value indicating whether the event handler has handled the event and the default action should be prevented.
/// </summary>
public bool Handled { get; set; }

/// <summary>
/// Gets the attachment that was clicked.
/// </summary>
public PopupAttachment Attachment { get; }
}
}
#endif
53 changes: 53 additions & 0 deletions src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,59 @@ public ScrollBarVisibility VerticalScrollBarVisibility
ScrollBarVisibility.Auto
#endif
);

/// <summary>
/// Raised when a popup attachment is clicked
/// </summary>
/// <remarks>
/// <para>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 <see cref="PopupAttachmentClickedEventArgs.Handled"/> property to <c>true</c> and perform
/// your own logic. </para>
/// <example>
/// Example: Use the .NET MAUI share API for the attachment:
/// <code language="csharp">
/// 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)));
/// }
/// </code>
/// </example>
/// </remarks>
public event EventHandler<PopupAttachmentClickedEventArgs>? 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;
}
}

/// <summary>
/// Event argument for the <see cref="PopupViewer.PopupAttachmentClicked"/> event.
/// </summary>
public class PopupAttachmentClickedEventArgs : EventArgs
{
internal PopupAttachmentClickedEventArgs(PopupAttachment attachment)
{
Attachment = attachment;
}

/// <summary>
/// Gets or sets a value indicating whether the event handler has handled the event and the default action should be prevented.
/// </summary>
public bool Handled { get; set; }

/// <summary>
/// Gets the attachment that was clicked.
/// </summary>
public PopupAttachment Attachment { get; }
}
}
#endif

0 comments on commit 44c4114

Please sign in to comment.