4

I would to do a downloader app that save pictures to a folder. The app should work on windows and macos, and may be later on android and ios.

I haven't found a way to pick the target folder. Any idea on how it can be achieve either with blazor or xaml .NET MAUI app?

fred_
  • 1,486
  • 1
  • 19
  • 31

2 Answers2

21

I've made a start implementing this for Windows and macOS. You can review the code here: https://github.com/jfversluis/MauiFolderPickerSample and wrote a little blog post about this as well here: https://blog.verslu.is/maui/folder-picker-with-dotnet-maui/

This follows kind of the basic pattern you'd want to use if you want to access platform-specific APIs:

  • Define an interface
  • Implement interface on each supported platform
  • Consume functionality

For this I have created a very simple but effective interface

public interface IFolderPicker
{
    Task<string> PickFolder();
}

Then we create an implementation for Windows, by adding a new file FilePicker.cs to the Platforms\Windows\ folder. This makes it specific to Windows and allows us to write Windows specific code. The file contains this code:

using WindowsFolderPicker = Windows.Storage.Pickers.FolderPicker;

namespace MauiFolderPickerSample.Platforms.Windows
{
    public class FolderPicker : IFolderPicker
    {
        public async Task<string> PickFolder()
        {
            var folderPicker = new WindowsFolderPicker();
            // Make it work for Windows 10
            folderPicker.FileTypeFilter.Add("*");
            // Get the current window's HWND by passing in the Window object
            var hwnd = ((MauiWinUIWindow)App.Current.Windows[0].Handler.PlatformView).WindowHandle;

            // Associate the HWND with the file picker
            WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd);

            var result = await folderPicker.PickSingleFolderAsync();

            return result.Path;
        }
    }
}

Because I chose FolderPicker as the name for my own object here, there is a naming conflict with the Windows FolderPicker that is why there is that weird using at the top. If you go for MyFolderPicker as your object name that wouldn't be needed.

Now we register this interface and implementation with the generic host builder in our MauiProgram.cs:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            });

// Note: this part was added

#if WINDOWS
        builder.Services.AddTransient<IFolderPicker, Platforms.Windows.FolderPicker>();
#elif MACCATALYST
        builder.Services.AddTransient<IFolderPicker, Platforms.MacCatalyst.FolderPicker>();
#endif
        builder.Services.AddTransient<MainPage>();
        builder.Services.AddTransient<App>();
// Note: end added part

        return builder.Build();
    }
}

Note that I also added MainPage and App here so that our constructor injection works (have a look at MainPage.xaml.cs in the linked repository).

Now we can consume our functionality as follows:

namespace MauiFolderPickerSample;

public partial class MainPage : ContentPage
{
    private readonly IFolderPicker _folderPicker;

    public MainPage(IFolderPicker folderPicker)
    {
        InitializeComponent();
        _folderPicker = folderPicker;
    }

    private async void OnPickFolderClicked(object sender, EventArgs e)
    {
        var pickedFolder = await _folderPicker.PickFolder();

        FolderLabel.Text = pickedFolder;

        SemanticScreenReader.Announce(FolderLabel.Text);
    }
}

Implementing other platforms would require you to implement the interface for the platform you want to support and register it in the generic host builder. This should get you started for Windows and macOS.

Actually calling this should not be any different between .NET MAUI (regular) or .NET MAUI Blazor.

Gerald Versluis
  • 30,492
  • 6
  • 73
  • 100
  • 1
    Hey Gerald, from testing this solution it seems that while this is working on windows 11, on windows 10 it returns the following error: System.Runtime.InteropServices.COMException: 'Error HRESULT E_FAIL has been returned from a call to a COM component.' I've tested on multiple machines with your demo project and 11 works and 10 always throws that error. Here's a bug report that we created for maui: https://github.com/dotnet/maui/issues/5443 – Kyle Fuller Mar 19 '22 at 15:41
  • 6
    After further testing we eventually figured out that the issue on windows 10 can be resolved from adding the following to the windows platform code: folderPicker.FileTypeFilter.Add("*"); – Kyle Fuller Apr 02 '22 at 14:21
  • Hi Gerald. I'm looking for a folder picker for a small oss project. I noticed that your sample has no specific licence, and I wondered if you'd be happy to licence it for others to use? – Andy Johnson Jul 25 '22 at 07:37
  • @AndyJohnson This applies to all of Stack Overflow: https://stackoverflow.com/help/licensing – Gerald Versluis Jul 25 '22 at 07:43
  • 1
    @GeraldVersluis Thanks for this. I implemented your solution for Mac Catalyst. When I can choose a folder, it returns a path as follows: file:///Users/macminim1/Documents/Sorted%20Testing/ if I use System.IO.Directory.Exists(pickedFolder)) right after choosing the folder, the above returns false. Any ideas? Thanks. – Julian Dormon Aug 11 '22 at 13:09
  • Hi @GeraldVersluis, are there any plans to make FolderPicker work in Android and iOS too? Thank you! – Ignotus Sep 16 '22 at 15:25
  • There is some discussion on that here: https://github.com/CommunityToolkit/Maui/discussions/381 make sure to make your voice heard! – Gerald Versluis Sep 16 '22 at 20:41
  • There is a small issue with this, at least on Windows. When the picker dialog initially appears, the "Ok" button is disabled. So in order to select the initial directory, the user must navigate out, then return to the directory, in order for the "Ok" button to become clickable. – Pancake Oct 29 '22 at 17:40
  • Hi Gerald, is something similar possible with Android and iOS? – Sikandar Amla Oct 31 '22 at 20:28
  • Hi all, I'm pretty new in MAUI and I'm trying to implement the folder picker in a .NET MAUI class library. I implemented the interface as in your project and the two FolderPicker.cs files for Windows and Mac platforms. But in the FolderPicker.cs related to the Windows platform, the line `var hwnd = ((MauiWinUIWindow)App.Current.Windows[0].Handler.PlatformView).WindowHandle;` gives me an error because _App_ doesn't exist in that context. How can I manage it? – eljamba Nov 14 '22 at 17:17
  • @GeraldVersluis, I just hit this issue on MacCatalyst with the path starting with "file", if I manually remove the "file" but, it works with System.IO.Directory.Exists() I will try that in other operations but have found any comments/fixes for this. – Mark Redman Jan 31 '23 at 11:36
  • Thanks for the example, especially the WinRT.Interop part. I like it that this solution doesn't need any privileges. – martinstoeckli Jun 17 '23 at 12:30
0

Good news, .NET Maui now has a folder selector plugin. Ok, so the NuGet package does more than just picking folders, but that's for a different question

https://learn.microsoft.com/en-us/dotnet/communitytoolkit/maui/essentials/folder-picker?tabs=android

The .NET MAUI Community Toolkit (CommunityToolkit.Maui in NuGet) gives you a folder picker that's pretty easy to use and set up.

iOS and Windows don't need any permissions, but Android does, if you don't already have this set up. Open the Platforms/Android/AndroidManifest.xml file and add the following in the manifest node:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Then include the following code wherever you need it:

async Task PickFolder(CancellationToken cancellationToken)
{
    var result = await FolderPicker.Default.PickAsync(cancellationToken);
    if (result.IsSuccessful)
    {
        await Toast.Make($"The folder was picked: Name - {result.Folder.Name}, Path - {result.Folder.Path}", ToastDuration.Long).Show(cancellationToken);
    }
    else
    {
        await Toast.Make($"The folder was not picked with error: {result.Exception.Message}").Show(cancellationToken);
    }
}

Or you can make it into a button click event like I did:

private async void PickFolder_Clicked(object sender, EventArgs e)
{
    CancellationTokenSource source = new();
    CancellationToken token = source.Token;
    var result = await FolderPicker.Default.PickAsync(token);
    
    if (result.IsSuccessful)
    {
        SaveLocation.Text = result.Folder.Path; // SaveLocation is a textbox in my XAML file
    }
}

Just make sure to use the CommunityToolkit package for the FolderPicker, instead of the Windows library VS 2022 auto-populated for me.

using CommunityToolkit.Maui.Storage;

You will also need to add the Community Toolkit to your startup class.

builder
    ...
    .UseMauiCommunityToolkit()

So far, I have it working fine in Android, but I haven't tested it in anything else. YMMV.

computercarguy
  • 2,173
  • 1
  • 13
  • 27