Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added popupviewer attachment click event #575

Merged
merged 8 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading