microsoft / WindowsAppSDK

The Windows App SDK empowers all Windows desktop apps with modern Windows UI, APIs, and platform features, including back-compat support, shipped via NuGet.
https://docs.microsoft.com/windows/apps/windows-app-sdk/
MIT License
3.78k stars 319 forks source link

How to use FilePicker and FolderPicker in WinUI 3 and other desktop-based apps #1188

Closed andrewleader closed 3 years ago

andrewleader commented 3 years ago

In desktop-based apps (like WinUI 3 desktop or WPF MSIX), The FileOpenPicker, FileSavePicker, and FolderPicker APIs require a HWND associated with them, so that they know which window to display over. That means, unlike UWP, you must add a couple extra lines of code before using the file and folder pickers.

Normally, the following works in UWP...

UWP

var filePicker = new FileOpenPicker();
filePicker.FileTypeFilter.Add("*");
var file = await filePicker.PickSingleFileAsync();

However, for desktop apps, you'll have to add two lines to obtain your current HWND, and set the HWND on the picker...

Desktop-based app

var filePicker = new FileOpenPicker();

// Get the current window's HWND by passing in the Window object
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);

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

// Use file picker like normal!
filePicker.FileTypeFilter.Add("*");
var file = await filePicker.PickSingleFileAsync();

What about FileSavePicker and FolderPicker?

Same thing! Add those two lines.

More info on obtaining the HWND

One key thing to note about GetWindowHandle(this), is that the this object you're passing must be a Window (either a WinUI 3 Window, a WPF Window, or a WinForms Window). If you're calling this code from your Window code-behind, this will often work. If you're calling this code from another file, you're going to have to pass a reference of the window.

Here's an example of a complete MainWindow using FileOpenPicker...

public sealed partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();
    }

    private async void myButton_Click(object sender, RoutedEventArgs e)
    {
        // Create the file picker
        var filePicker = new FileOpenPicker();

        // Get the current window's HWND by passing in the Window object
        var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);

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

        // Use file picker like normal!
        filePicker.FileTypeFilter.Add("*");
        var file = await filePicker.PickSingleFileAsync();
    }
}

More info on interop

To learn more about these interop APIs, see https://docs.microsoft.com/windows/apps/desktop/modernize/winrt-com-interop-csharp

KWodarczyk commented 2 years ago

Can you provide simple example how do you provide reference to window from page object ? Usually an app consists of one main window and multiple pages where main window it's just a container without any logic, everything happens in pages so we need to know how to use it from page level.

andrewleader commented 2 years ago

@KWodarczyk in most cases, for apps that only have one window, you can create a public static reference in App.xaml.cs to your main window

App.xaml.cs

public static Window Window => m_window;

And then from other files, call it like...

GetWindowHandle(App.Window);
KWodarczyk commented 2 years ago

@andrewleader that will work if you have all your pages in same project as App.xmal.cs but I have separated them to two separate project, so views are separate form the winui project so hat I can use views if i want to switch to UWP or WPF etc. The simplest way is just to add reference to winui project to my views project abut I don't want to add whole project just to get to the window. I also tried to use VisualTreeHelper in a Page code behind to get to the window but that does not work either.

AndrewKeepCoding commented 2 years ago

Just in case. Add a FileTypeFilter when using the FolderPicker. It works without the FileTypeFilter on a Windows 11, but you (might?) get a COMException on Windows 10.

FolderPicker folderPicker = new();
folderPicker.FileTypeFilter.Add("*");

IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd);

StorageFolder folder = await folderPicker.PickSingleFolderAsync();
knightmeister commented 2 years ago

Damn, this is a leaky abstraction. It's made even worse given that generally you would invoke this from a view model which has no concept as to the window it's owned by.

TRadigk commented 2 years ago

Just in case. Add a FileTypeFilter when using the FolderPicker. It works without the FileTypeFilter on a Windows 11, but you (might?) get a COMException on Windows 10.

FolderPicker folderPicker = new();
folderPicker.FileTypeFilter.Add("*");

IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd);

StorageFolder folder = await folderPicker.PickSingleFolderAsync();

Yes, there probably is, since the picker does not open on Windows 10. But folderPicker.FileTypeFilter.Add("*"); does the trick.

bogdan-patraucean commented 2 years ago

Tried with Windows 11 and it doesn't work anymore. WASDK 1.1 preview 3, but only if I use my app as elevated, otherwise it works.

rayveenemadotmacdotcom commented 2 years ago

I am trying to use FileSavePicker with the required parameters, including the WindowHandle and it fails. (The commented section for FileSavePicker causes an unhandled exception without further details). If I use the FileOpenPicker with the Window Handle the dialog opens as expected.

The section of code that has been commented out relates to the FileSavePicker and although using the correct window handle it doesn't work.

I believe I have set all required values for the FileSavePicker. What am I missing?

FileOpenPicker filePicker = new();

/ FileSavePicker picker = new(); picker.SuggestedStartLocation = PickerLocationId.Downloads; picker.FileTypeChoices.Add("Csv", new List() { ".csv" }); picker.FileTypeChoices.Add("Plain Text", new List() { ".txt" }); picker.SuggestedFileName = "Activity Codes"; picker.SettingsIdentifier = "settingsIdentifier"; picker.DefaultFileExtension = ".csv"; /

var hwnd = WindowNative.GetWindowHandle(App.m_window); InitializeWithWindow.Initialize(filePicker, hwnd); //InitializeWithWindow.Initialize(picker, hwnd); filePicker.FileTypeFilter.Add("*");

//StorageFile file = await picker.PickSaveFileAsync(); StorageFile file = await filePicker.PickSingleFileAsync();

AndrewKeepCoding commented 2 years ago

Hi @rayveenemadotmacdotcom! I couldn't reproduce your problem on both Win10 and Win11. All I did was just create a WinUI3 app and use the sample button to open the FileSavePicker and replace App.m_window with this which is the MainWindow.

Maybe you should check if you are getting a valid hwnd.

    private async void myButton_Click(object sender, RoutedEventArgs e)
    {
        FileSavePicker picker = new();
        picker.SuggestedStartLocation = PickerLocationId.Downloads;
        picker.FileTypeChoices.Add("Csv", new List<string>() { ".csv" });
        picker.FileTypeChoices.Add("Plain Text", new List<string>() { ".txt" });
        picker.SuggestedFileName = "Activity Codes";
        picker.SettingsIdentifier = "settingsIdentifier";
        picker.DefaultFileExtension = ".csv";

        var hwnd = WindowNative.GetWindowHandle(this);  // App.m_window?
        InitializeWithWindow.Initialize(picker, hwnd);

        StorageFile file = await picker.PickSaveFileAsync();

        myButton.Content = $"{file?.DisplayName}";
    }
rayveenemadotmacdotcom commented 2 years ago

Andrew it works on a Windows, but when you use Pages it doesn’t. I use the navigation view. The window handle works as I can execute the FileOpenPicker, as in the example code showed. So I know from my page I can execute with a valid window handle.

Give it a try from a page from Navigation view and see if it works.

On May 12, 2022, at 10:10 PM, Andrew KeepCoding @.***> wrote:

 Hi @rayveenemadotmacdotcom! I couldn't reproduce your problem on both Win10 and Win11. All I did was just create a WinUI3 app and use the sample button to open the FileSavePicker and replace App.m_window with this which is the MainWindow.

Maybe you should check if you are getting a valid hwnd.

private async void myButton_Click(object sender, RoutedEventArgs e)
{
    FileSavePicker picker = new();
    picker.SuggestedStartLocation = PickerLocationId.Downloads;
    picker.FileTypeChoices.Add("Csv", new List<string>() { ".csv" });
    picker.FileTypeChoices.Add("Plain Text", new List<string>() { ".txt" });
    picker.SuggestedFileName = "Activity Codes";
    picker.SettingsIdentifier = "settingsIdentifier";
    picker.DefaultFileExtension = ".csv";

    var hwnd = WindowNative.GetWindowHandle(this);  // App.m_window?
    InitializeWithWindow.Initialize(picker, hwnd);

    StorageFile file = await picker.PickSaveFileAsync();

    myButton.Content = $"{file?.DisplayName}";
}

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.

AndrewKeepCoding commented 2 years ago

@rayveenemadotmacdotcom I tried it on a page inside MainWindow, a page inside a NavigationView (created with Template Studio) on both Win10 and Win11 and works. I should've ask you this first, but, which code exactly is giving you the exception? the PickSaveFileAsync() ? Can you reproduce this with the minimum code? 🤔

rayveenemadotmacdotcom commented 2 years ago

So I have created a new NavigationView app using Template Studio for VS Professional 2022 and the FileSavePicker works using the GetWindowHandle. This means that my WinUI 3 app that I generated not using Template Studio must have a setting or some discrepancy causing FileSavePicker to not work in the NavigationView solution.

I will dig into my original app and see if I can find the culprit knowing that the FileSavePicker should work.

I’ll post my findings.

On May 13, 2022, at 1:19 AM, Andrew KeepCoding @.***> wrote:

@rayveenemadotmacdotcom https://github.com/rayveenemadotmacdotcom I tried it on a page inside MainWindow, a page inside a NavigationView (created with Template Studio) on both Win10 and Win11 and works. I should've ask you this first, but, which code exactly is giving you the exception? the PickSaveFileAsync() ? Can you reproduce this with the minimum code? 🤔

— Reply to this email directly, view it on GitHub https://github.com/microsoft/WindowsAppSDK/issues/1188#issuecomment-1125666302, or unsubscribe https://github.com/notifications/unsubscribe-auth/AWPTXXLJBPEADQZWKSKGNI3VJXQ6BANCNFSM5BWB2JAQ. You are receiving this because you were mentioned.

Gueztt commented 2 years ago

@andrewleader thank you for the example. However when I click the button rapidly multiple folder picker launches. I am unable to make it so that it only launches once. What I want to achieve is the effect that we normally see, the picker window blinks when we try to launch another picker window. If you have any possible work around. Thank you for all your help.

DiaryChris commented 2 years ago

Thanks. That's useful.

derekhe commented 2 years ago

Just in case. Add a FileTypeFilter when using the FolderPicker. It works without the FileTypeFilter on a Windows 11, but you (might?) get a COMException on Windows 10.

FolderPicker folderPicker = new();
folderPicker.FileTypeFilter.Add("*");

IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd);

StorageFolder folder = await folderPicker.PickSingleFolderAsync();

I still got COM exception on windows 11.

Guillermo-Santos commented 2 years ago

Just in case. Add a FileTypeFilter when using the FolderPicker. It works without the FileTypeFilter on a Windows 11, but you (might?) get a COMException on Windows 10.

FolderPicker folderPicker = new();
folderPicker.FileTypeFilter.Add("*");

IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd);

StorageFolder folder = await folderPicker.PickSingleFolderAsync();

I still got COM exception on windows 11.

This is how you should do it

var FolderPicker = new FolderPicker
{
    ViewMode = PickerViewMode.Thumbnail,
    SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
var hwnd = App.MainWindow.GetWindowHandle();
WinRT.Interop.InitializeWithWindow.Initialize(FolderPicker, hwnd);
var folder = await FolderPicker.PickSingleFolderAsync();

At least, that is what worked to me

Guillermo-Santos commented 2 years ago

File picker is even easier since there is already a method that handle that part for you

string fileExtension = ".txt";
var FilePicker = App.MainWindow.CreateOpenFilePicker();
FilePicker.ViewMode = PickerViewMode.Thumbnail;
FilePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
FilePicker.FileTypeFilter.Add(fileExtension);
derekhe commented 2 years ago

I'm using the function you mentioned in provided by WinUIEx, but it still give me error:

image

I'm using Windows 11 22000.795, .NET 6 with WinUI3 and build unpacked application.

TRadigk commented 2 years ago

@derekhe would you mind sharing more information about your project? Using Visual Studio 202022 17.3, WinAppSdk 1.1.3 and WinUiEx 1.7.0 on Windows 11 22000.856 in unpacked project I don't have this issue using the same exact code as you showed.

bogdan-patraucean commented 2 years ago

@TRadigk bro, are you running as administrator?

TRadigk commented 2 years ago

yes, my user is administrator.

Edit: you meant elevated - No, I don't start my app elevated.

derekhe commented 2 years ago

comexception.zip

Here is my sample project. I find it is caused by running it as administrator. As a normal user it is fine.

I'm using windows 11 22000.856.

My information vs 2022:

Microsoft Visual Studio Community 2022
Version 17.2.6
VisualStudio.17.Release/17.2.6+32630.192
Microsoft .NET Framework
Version 4.8.04161

Installed Version: Community

Visual C++ 2022   00482-90000-00000-AA897
Microsoft Visual C++ 2022

ASP.NET and Web Tools 2019   17.2.393.26812
ASP.NET and Web Tools 2019

Azure App Service Tools v3.0.0   17.2.393.26812
Azure App Service Tools v3.0.0

C# Tools   4.2.0-4.22281.5+8d3180e5f00d42f0f0295165f756f368f0cbfa44
C# components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.

Common Azure Tools   1.10
Provides common services for use by Azure Mobile Services and Microsoft Azure Tools.

IncrediBuild Build Acceleration   1.6.0.2
IncrediBuild effectively reduces compilation and development times by up to 90%.

Microsoft JVM Debugger   1.0
Provides support for connecting the Visual Studio debugger to JDWP compatible Java Virtual Machines

NuGet Package Manager   6.2.1
NuGet Package Manager in Visual Studio. For more information about NuGet, visit https://docs.nuget.org/

Razor (ASP.NET Core)   17.0.0.2218101+885a343b00bcab620a90c1550c37dafd730ce984
Provides languages services for ASP.NET Core Razor.

SQL Server Data Tools   17.0.62204.01010
Microsoft SQL Server Data Tools

Test Adapter for Boost.Test   1.0
Enables Visual Studio's testing tools with unit tests written for Boost.Test.  The use terms and Third Party Notices are available in the extension installation directory.

Test Adapter for Google Test   1.0
Enables Visual Studio's testing tools with unit tests written for Google Test.  The use terms and Third Party Notices are available in the extension installation directory.

TypeScript Tools   17.0.10418.2001
TypeScript Tools for Microsoft Visual Studio

Visual Basic Tools   4.2.0-4.22281.5+8d3180e5f00d42f0f0295165f756f368f0cbfa44
Visual Basic components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.

Visual F# Tools   17.1.0-beta.22329.1+702b8e77f5fbfe21e6743324c1750503e02f182d
Microsoft Visual F# Tools

Visual Studio IntelliCode   2.2
AI-assisted development for Visual Studio.
TRadigk commented 2 years ago

Since this issue is closed, I think it is worth re-opening it and point towards elevated support. I am able to repro the mentioned issue, but as far as I remember WinAppSdk had in the past, and I think still has trouble with elevated privileges (in regards of unpackaged apps). Maybe this topic is addressed in upcoming Community Call (August 17th)?

bogdan-patraucean commented 2 years ago

@TRadigk this is already opened by me some time ago: https://github.com/microsoft/WindowsAppSDK/issues/2504

olumide-oyetoke commented 1 year ago

I have seen several posts on how to use fileOpenPicker in WinUI3 but for me, the app does not even deploy successfully unless I remove the Declaration.

I always get the error: DEP0700: Registration of the app failed. [0x80073CF6] AppxManifest.xml(38,10): error 0x80070032: Cannot register the 82D57A91-4788-4329-A27B-168BB25CAFFC_1.0.0.0_x64__84fc1bza3khrr package because the following error was encountered while registering the windows.fileOpenPicker extension: The request is not supported.

Same thing happens with fileSavePicker. But the app run fine without both declarations.

The manifest file contains this:

Apparently, fileOpenPicker and fileSavePicker are only compatible with "uap"

ewoifuoi commented 1 year ago

It can work successfully in this way:

var picker = new FileOpenPicker();
WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
picker.FileTypeFilter.Add(".jpg");
picker.FileTypeFilter.Add(".jpeg");
picker.FileTypeFilter.Add(".png");
var file = await picker.PickSingleFileAsync();
michalss commented 1 year ago

How to make it work win Page. Why MS so f.. this up. :( im so angry about this. Golden OpenFileDialog.. :(

olumide-oyetoke commented 1 year ago

How to make it work win Page. Why MS so f.. this up. :( im so angry about this. Golden OpenFileDialog.. :(

Your statement is not clear please.

BonaFideBOSS commented 1 year ago

What about Windows.Storage?

cl2raul66 commented 1 year ago

My solution is:

Window w = new();
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(w);
WinRT.Interop.InitializeWithWindow.Initialize(openPicker, hWnd);
openPicker.SuggestedStartLocation = PickerLocationId.Desktop;
openPicker.FileTypeFilter.Add("*");
StorageFolder folder = await openPicker.PickSingleFolderAsync();
w.Close();
zipswich commented 9 months ago

I have tried pretty much every solution suggested here and still get the System.Runtime.InteropServices.COMException on Windows 11 (222H2, 22621.2861 )

Guillermo-Santos commented 9 months ago

I have tried pretty much every solution suggested here and still get the System.Runtime.InteropServices.COMException on Windows 11 (222H2, 22621.2861 )

why don't you try the one showed on the WinUI 3 Gallery app?

<StackPanel VerticalAlignment="Top" Orientation="Horizontal">
    <Button x:Name="PickAFileButton" Content="Open a file"
            Click="PickAFileButton_Click" Margin="0,0,0,10"/>
    <TextBlock x:Name="PickAFileOutputTextBlock" TextWrapping="Wrap" Padding="20,5,0,0"/>
</StackPanel>
private async void PickAFileButton_Click(object sender, RoutedEventArgs e)
{
    // Clear previous returned file name, if it exists, between iterations of this scenario
    OutputTextBlock.Text = "";

    // Create a file picker
    var openPicker = new Windows.Storage.Pickers.FileOpenPicker();

    // Retrieve the window handle (HWND) of the current WinUI 3 window.
    var window = WindowHelper.GetWindowForElement(this);
    var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);

    // Initialize the file picker with the window handle (HWND).
    WinRT.Interop.InitializeWithWindow.Initialize(openPicker, hWnd);

    // Set options for your file picker
    openPicker.ViewMode = PickerViewMode.Thumbnail;
    openPicker.FileTypeFilter.Add("*");

    // Open the picker for the user to pick a file
    var file = await openPicker.PickSingleFileAsync();
    if (file != null)
    {
        PickAFileOutputTextBlock.Text = "Picked file: " + file.Name;
    }
    else
    {
        PickAFileOutputTextBlock.Text = "Operation cancelled.";
    }
}
zipswich commented 9 months ago

@Guillermo-Santos Thank you for the tip. I copied your code to the barebone test app and copied WindowHelper.

Unfortunately, WindowHelper.GetWindowForElement(this) returns null.

zipswich commented 9 months ago

@Guillermo-Santos I realized the cause of the null problem. I changed the code to use WindowHelper.CreateWindow(). Now, WindowHelper.GetWindowForElement(this) returns non-null window. Unfortunately, var fileTest = await openPicker.PickSingleFileAsync() still throws System.Runtime.InteropServices.COMException.

Guillermo-Santos commented 9 months ago

@Guillermo-Santos I realized the cause of the null problem. I changed the code to use WindowHelper.CreateWindow(). Now, WindowHelper.GetWindowForElement(this) returns non-null window. Unfortunately, var fileTest = await openPicker.PickSingleFileAsync() still throws System.Runtime.InteropServices.COMException.

I have not tested this code (xd), but i'll try and see if i have the same result. The only thing i could think that may bring this error is the target of the project (do not really know).

Guillermo-Santos commented 9 months ago

Using your modification throws with 'Access violation' to me, i think you can get rip of the WindowHelper class an intead use var window = App.MainWindow; that one worked for me.

If you are using the blank template, then just add a property to the App class that expose the m_window private field. and change the code to something like var window = ((App)Application.Current).MainWindow.

zipswich commented 9 months ago

Using your modification throws with 'Access violation' to me, i think you can get rip of the WindowHelper class an intead use var window = App.MainWindow; that one worked for me.

I realized that key is Window vs Page. Here is my code in App.cs:

    public static Window? MainWindow { get; private set; }

    protected override void OnLaunched(LaunchActivatedEventArgs args)
    {
        MainWindow = WindowHelper.CreateWindow();

        // Do not repeat app initialization when the Window already has content,
        // just ensure that the window is active
        if (MainWindow.Content is not Frame rootFrame)
        {
            // Create a Frame to act as the navigation context and navigate to the first page
            rootFrame = new Frame();

            // Place the frame in the current Window
            MainWindow.Content = rootFrame;

            rootFrame.NavigationFailed += OnNavigationFailed;
        }

        if (rootFrame.Content == null)
        {
            // When the navigation stack isn't restored navigate to the first page,
            // configuring the new page by passing required information as a navigation
            // parameter
            rootFrame.Navigate(typeof(MainPage), args.Arguments);
        }

        // Ensure the current window is active
        MainWindow.Activate();
    }

MainPage inherits Page, not Window.

Guillermo-Santos commented 9 months ago

This is mine:

        public static Window MainWindow { get; private set; }
        /// <summary>
        /// Initializes the singleton application object.  This is the first line of authored code
        /// executed, and as such is the logical equivalent of main() or WinMain().
        /// </summary>
        public App()
        {
            this.InitializeComponent();
        }

        /// <summary>
        /// Invoked when the application is launched.
        /// </summary>
        /// <param name="args">Details about the launch request and process.</param>
        protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
        {
            MainWindow = new MainWindow();
            MainWindow.Activate();
        }

As I say, you do not really need the WindowHelper. there should be a MainWindow.xaml in your project, right? or are we using different templates?

zipswich commented 9 months ago

This is mine: As I say, you do not really need the WindowHelper. there should be a MainWindow.xaml in your project, right? or are we using different templates?

I use a different template:

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
    }
}
donut2008 commented 4 months ago

Does this not work for an app that's running elevated? I keep getting a COMException with the error code 0x80004005 despite trying all of the solutions suggested in this thread. The code in my App.xaml.cs is the same as that posted by zipswich.

cl2raul66 commented 3 months ago

¿Esto no funciona para una aplicación que se ejecuta con privilegios elevados? Sigo recibiendo una COMException con el código de error 0x80004005 a pesar de probar todas las soluciones sugeridas en este hilo. El código en mi App.xaml.cs es el mismo que el publicado por zipswich

I think you should run your Visual Studio with administrator privileges, that helps your generated app inherit those privileges... If I'm not mistaken, if you set up your project correctly so that you have unlocked unlocked unsafe code executions, it shouldn't give you any problems