Skip to content
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
@@ -0,0 +1,35 @@
using System;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Platform.Storage;

namespace Avalonia.Xaml.Interactions.Core;

/// <summary>
/// Converts a <seealso cref="IStorageFile"/> to a read stream.
/// </summary>
public class StorageFileToReadStreamConverter : IValueConverter
{
/// <summary>
/// Gets a static instance of <see cref="StorageFileToReadStreamConverter"/>.
/// </summary>
public static StorageFileToReadStreamConverter Instance { get; } = new ();

/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is IStorageFile storageFile)
{
return storageFile.OpenReadAsync();
}

return BindingOperations.DoNothing;
}

/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return BindingOperations.DoNothing;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Platform.Storage;

namespace Avalonia.Xaml.Interactions.Core;

/// <summary>
/// Converts a <seealso cref="IStorageFile"/> to a path.
/// </summary>
public class StorageFileToWriteStreamConverter : IValueConverter
{
/// <summary>
/// Gets a static instance of <see cref="StorageFileToWriteStreamConverter"/>.
/// </summary>
public static StorageFileToWriteStreamConverter Instance { get; } = new ();

/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is IStorageFile storageFile)
{
return storageFile.OpenWriteAsync();
}

return BindingOperations.DoNothing;
}

/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return BindingOperations.DoNothing;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Platform.Storage;

namespace Avalonia.Xaml.Interactions.Core;

/// <summary>
/// Converts a <seealso cref="IStorageItem"/> to a path.
/// </summary>
public class StorageItemToPathConverter : IValueConverter
{
/// <summary>
/// Gets a static instance of <see cref="StorageItemToPathConverter"/>.
/// </summary>
public static StorageItemToPathConverter Instance { get; } = new ();

/// <inheritdoc />
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is IStorageItem storageItem)
{
return storageItem.Path;
}

return BindingOperations.DoNothing;
}

/// <inheritdoc />
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return BindingOperations.DoNothing;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Avalonia.Platform.Storage;

namespace Avalonia.Xaml.Interactions.Core;

/// <summary>
/// Base class for picker actions.
/// </summary>
public abstract class PickerActionBase : InvokeCommandActionBase
{
/// <summary>
/// Identifies the <seealso cref="Title"/> avalonia property.
/// </summary>
public static readonly StyledProperty<string?> TitleProperty =
AvaloniaProperty.Register<PickerActionBase, string?>(nameof(Title));

/// <summary>
/// Identifies the <seealso cref="SuggestedStartLocation"/> avalonia property.
/// </summary>
public static readonly StyledProperty<IStorageFolder?> SuggestedStartLocationProperty =
AvaloniaProperty.Register<PickerActionBase, IStorageFolder?>(nameof(SuggestedStartLocation));

/// <summary>
/// Identifies the <seealso cref="SuggestedFileName"/> avalonia property.
/// </summary>
public static readonly StyledProperty<string?> SuggestedFileNameProperty =
AvaloniaProperty.Register<PickerActionBase, string?>(nameof(SuggestedFileName));

/// <summary>
/// Gets or sets the text that appears in the title bar of a picker. This is an avalonia property.
/// </summary>
public string? Title
{
get => GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}

/// <summary>
/// Gets or sets the initial location where the file open picker looks for files to present to the user. This is an avalonia property.
/// </summary>
public IStorageFolder? SuggestedStartLocation
{
get => GetValue(SuggestedStartLocationProperty);
set => SetValue(SuggestedStartLocationProperty, value);
}

/// <summary>
/// Gets or sets the file name that the file picker suggests to the user. This is an avalonia property.
/// </summary>
public string? SuggestedFileName
{
get => GetValue(SuggestedFileNameProperty);
set => SetValue(SuggestedFileNameProperty, value);
}
}
108 changes: 108 additions & 0 deletions src/Avalonia.Xaml.Interactions/StorageProvider/OpenFilePickerAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Avalonia.VisualTree;

namespace Avalonia.Xaml.Interactions.Core;

/// <summary>
/// An action that will open a file picker dialog.
/// </summary>
public class OpenFilePickerAction : PickerActionBase
{
/// <summary>
/// Identifies the <seealso cref="AllowMultiple"/> avalonia property.
/// </summary>
public static readonly StyledProperty<bool> AllowMultipleProperty =
AvaloniaProperty.Register<OpenFilePickerAction, bool>(nameof(AllowMultiple));

/// <summary>
/// Identifies the <seealso cref="FileTypeFilter"/> avalonia property.
/// </summary>
public static readonly StyledProperty<string?> FileTypeFilterProperty =
AvaloniaProperty.Register<OpenFilePickerAction, string?>(nameof(FileTypeFilter));

/// <summary>
/// Gets or sets an option indicating whether open picker allows users to select multiple files. This is an avalonia property.
/// </summary>
public bool AllowMultiple
{
get => GetValue(AllowMultipleProperty);
set => SetValue(AllowMultipleProperty, value);
}

/// <summary>
/// Gets or sets the collection of file types that the file open picker displays. This is an avalonia property.
/// </summary>
public string? FileTypeFilter
{
get => GetValue(FileTypeFilterProperty);
set => SetValue(FileTypeFilterProperty, value);
}

/// <summary>
/// Initializes a new instance of the <see cref="OpenFilePickerAction"/> class.
/// </summary>
public OpenFilePickerAction()
{
PassEventArgsToCommand = true;
}

/// <summary>
/// Executes the action.
/// </summary>
/// <param name="sender">The <see cref="object"/> that is passed to the action by the behavior. Generally this is <seealso cref="Avalonia.Xaml.Interactivity.IBehavior.AssociatedObject"/> or a target object.</param>
/// <param name="parameter">The value of this parameter is determined by the caller.</param>
/// <returns>True if the command is successfully executed; else false.</returns>
public override object Execute(object? sender, object? parameter)
{
if (sender is not Visual visual)
{
return false;
}

Dispatcher.UIThread.InvokeAsync(async () => await OpenFilePickerAsync(visual));

return true;
}

private async Task OpenFilePickerAsync(Visual visual)
{
if (IsEnabled != true || Command is null)
{
return;
}

var storageProvider = (visual.GetVisualRoot() as TopLevel)?.StorageProvider;
if (storageProvider is null)
{
return;
}

var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = Title,
SuggestedStartLocation = SuggestedStartLocation,
SuggestedFileName = SuggestedFileName,
AllowMultiple = AllowMultiple,
FileTypeFilter = FileTypeFilter is not null
? FileFilterParser.ConvertToFilePickerFileType(FileTypeFilter)
: null
});

if (files.Count <= 0)
{
return;
}

var resolvedParameter = ResolveParameter(files);

if (!Command.CanExecute(resolvedParameter))
{
return;
}

Command.Execute(resolvedParameter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Avalonia.VisualTree;

namespace Avalonia.Xaml.Interactions.Core;

/// <summary>
/// An action that will open a folder picker dialog.
/// </summary>
public class OpenFolderPickerAction : PickerActionBase
{
/// <summary>
/// Identifies the <seealso cref="AllowMultiple"/> avalonia property.
/// </summary>
public static readonly StyledProperty<bool> AllowMultipleProperty =
AvaloniaProperty.Register<OpenFolderPickerAction, bool>(nameof(AllowMultiple));

/// <summary>
/// Gets or sets an option indicating whether open picker allows users to select multiple folders. This is an avalonia property.
/// </summary>
public bool AllowMultiple
{
get => GetValue(AllowMultipleProperty);
set => SetValue(AllowMultipleProperty, value);
}

/// <summary>
/// Initializes a new instance of the <see cref="OpenFolderPickerAction"/> class.
/// </summary>
public OpenFolderPickerAction()
{
PassEventArgsToCommand = true;
}

/// <summary>
/// Executes the action.
/// </summary>
/// <param name="sender">The <see cref="object"/> that is passed to the action by the behavior. Generally this is <seealso cref="Avalonia.Xaml.Interactivity.IBehavior.AssociatedObject"/> or a target object.</param>
/// <param name="parameter">The value of this parameter is determined by the caller.</param>
/// <returns>True if the command is successfully executed; else false.</returns>
public override object Execute(object? sender, object? parameter)
{
if (sender is not Visual visual)
{
return false;
}

Dispatcher.UIThread.InvokeAsync(async () => await OpenFolderPickerAsync(visual));

return true;
}

private async Task OpenFolderPickerAsync(Visual visual)
{
if (IsEnabled != true || Command is null)
{
return;
}

var storageProvider = (visual.GetVisualRoot() as TopLevel)?.StorageProvider;
if (storageProvider is null)
{
return;
}

var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = Title,
SuggestedStartLocation = SuggestedStartLocation,
SuggestedFileName = SuggestedFileName,
AllowMultiple = AllowMultiple
});

if (folders.Count <= 0)
{
return;
}

var resolvedParameter = ResolveParameter(folders);

if (!Command.CanExecute(resolvedParameter))
{
return;
}

Command.Execute(resolvedParameter);
}
}
Loading
Loading