1

My first WinUI 3 project. My second C# project. I have been able to get the FileSavePicker to work and display a dialog:

What I am going to show is a stub program created just to workout the issue I am having with the FileSavePicker.

Issue description: I don't know how to use the non-async method 'CreateReport()' with the async FileSavePicker.

I have tested CreateReport() method with the 'Create Report' button, and it successfully creates an excel file and successfully writes to the file (using EPPlus).

How can I make the CreateReport() method wait for the FileSavePicker to finish and then use the file.name from the FileSavePicker?

My attempts to do it produce an excel file, but when I attempt to open the file, excel gives me this error:

enter image description here

MainWindow: enter image description here

XAML:

<Window
    x:Class="App_WinUI3_FileSavePicker_Sandbox.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App_WinUI3_FileSavePicker_Sandbox"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Width="800" BorderBrush="black" BorderThickness="8">

        <TextBlock Text="Test of File Save Picker and Creating an Excel file" HorizontalAlignment="Center" Margin="10" FontSize="20"/>

        <StackPanel Orientation="Horizontal">

            <Button x:Name="myButton" Click="myButton_Click" Margin="10" Padding="10">Start File Save Picker</Button>

            <Button x:Name="Button2" Click="Button2_Click"  Margin="10" Padding="10" ToolTipService.ToolTip="Tests the creation of an excel file using a hardcoded filename and not using the File Save Picker" >Create Report</Button>

        </StackPanel>

        <TextBox x:Name="ReportStatus1"  Header="Report Status 1" Margin="10" FontSize="20"/>

        <TextBox x:Name="ReportStatus2"  Header="Report Status 2" Margin="10" FontSize="20"/>

        <TextBox x:Name="ReportStatus3"  Header="Report Status 3" Margin="10" FontSize="20"/>

        <TextBox x:Name="ReportStatus4"  Header="Report Status 4" Margin="10" FontSize="20"/>

        <TextBox x:Name="ReportStatus5"  Header="Report Status 5" Margin="10" FontSize="20"/>
    </StackPanel>
</Window>

C# Code behind

using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Provider;
using OfficeOpenXml;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

namespace App_WinUI3_FileSavePicker_Sandbox
{
    /// <summary>
    /// An empty window that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            // Retrieve the window handle (HWND) of the current WinUI 3 window.
            var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
            // For EPPlus spreadsheet library for .NET         
            ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
        }

        public string File_Name = string.Empty;

        private void myButton_Click(object sender, RoutedEventArgs e)
        {
            DisplayReportSaveDialog("Report1");
            CreateReport(File_Name);
        }

        private async void DisplayReportSaveDialog(string ReportName)
        {
            FileSavePicker savePicker = new FileSavePicker();

            // Retrieve the window handle (HWND) of the current WinUI 3 window.
            var window = (Application.Current as App)?.m_window as MainWindow;
            var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);

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

            savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;

            // Dropdown of file types the user can save the file as
            savePicker.FileTypeChoices.Add("Excel Workbook", new List<string>() { ".xlsx" });
            // I don't want a text file.  I need to create an excel file. 
            // savePicker.FileTypeChoices.Add("Plain Text", new List<string>() { ".txt" });

            // Default file name if the user does not type one in or select a file to replace
            switch (ReportName)
            {
                case "Report1":
                    savePicker.SuggestedFileName = "Report1_" + String.Format("{0:MMddyyyy_HHmmss}", DateTime.Now) + ".xlsx";
                    break;
                case "Report2":
                    savePicker.SuggestedFileName = "Report2_" + String.Format("{0:MMddyyyy_HHmmss}", DateTime.Now) + ".xlsx";
                    break;
            }

            // Display the file picker dialog by calling PickSaveFileAsync
            StorageFile file = await savePicker.PickSaveFileAsync();
            if (file != null)
            {
                // Prevent updates to the remote version of the file until we finish making changes and call CompleteUpdatesAsync.
                CachedFileManager.DeferUpdates(file);

                // write the file name into the file.  
                // This is the sample code from the microsoft docs. 
                // It shows how to write simple text to a text file. 
                // It does work. 
                // Is there a similar way I can do this with my method 'CreateReport()'?

                //await FileIO.WriteTextAsync(file, file.Name);

                // Let Windows know that we're finished changing the file so the other app can update the remote version of the file.
                // Completing updates may require Windows to ask for user input.

                File_Name = file.Name;

                // What does this do?  
                // Why did I add this? 
                //await file.DeleteAsync(); 

                ReportStatus3.Text = $"path = {file.Path}";
                ReportStatus4.Text = $"file_Name = {File_Name}";

                FileUpdateStatus status = await CachedFileManager.CompleteUpdatesAsync(file);
                if (status == FileUpdateStatus.Complete)
                {
                    ReportStatus1.Text = "File " + file.Name + " was saved....";
                }
                else
                {
                    ReportStatus1.Text = "File " + file.Name + " couldn't be saved.";
                }
            }
            else
            {
                ReportStatus2.Text = $"{ReportName} report cancelled.";
            }
        }

        private void CreateReport(string filename)
        {
            try
            {
                filename = "c:\\sandbox\\" + filename;
                ReportStatus5.Text = $"filename = {filename} - Message from (CreateReport())";
                using (var package = new ExcelPackage(filename))
                {
                    // EPPLUS - Add a new worksheet. 
                    var ws = package.Workbook.Worksheets.Add($"EMB High Level Goals");
                    ws.Cells["A1"].Value = "1";
                    ws.Cells["A2"].Value = "2";
                    ws.Cells["B1"].Value = "Lenny";
                    ws.Cells["B2"].Value = "Created by App_WinUI3_FileSavePicker_Sandbox";
                    package.Save();

                } // end using

                ReportStatus3.Text = $"CreateReport: File {filename} was saved....";

            }
            catch (Exception e)
            {
                ReportStatus4.Text = $"CreateReport error: {e.Message}";
            }
        }

        private void Button2_Click(object sender, RoutedEventArgs e)
        {
            string filename = "Test_Report_" + String.Format("{0:MMddyyyy_HHmmss}", DateTime.Now) + ".xlsx";
            CreateReport(filename);
        }
    }
}
LennyL
  • 225
  • 3
  • 9
  • Did you try just to call you CreateReport() instead of Microsoft stub code? You just don't need await in this case – demonplus Apr 08 '22 at 10:13
  • Sure you don't need await file.DeleteAsync() (it deletes file) – demonplus Apr 08 '22 at 10:15
  • Remove two commented Microsoft lines and call your method, it should work – demonplus Apr 08 '22 at 10:15
  • Hi @demonplus, yes, I did call CreateReport(), and it does work, it successfully will create an excel file, that I can open successfully, and it contains the content that was written to the file. For that test call of CreateReport(), I provided a hard-coded filename and I did not use the FileSavePicker. I used file dialogs in wpf successfully before. I'll have to check to see if they were async methods, and how I did it. My whole intent of using the FileSavePicker is to allow the user to determine where they may want to save the file. – LennyL Apr 09 '22 at 07:42
  • Hi @demonplus, which 'two commented Microsoft lines' should I remove? I need clarification of your directions. I appreciate your help. – LennyL Apr 09 '22 at 07:43

2 Answers2

1

Just remove commented Microsoft stuff:

//await FileIO.WriteTextAsync(file, file.Name);

//await file.DeleteAsync(); 

and instead of that call your own function:

CreateReport(file.Name);
demonplus
  • 5,613
  • 12
  • 49
  • 68
  • 1
    Thank you @demonplus for you help with this matter. Using mm8's direction and yours, I can call my CreateReport() method from within or from outside the DisplayReportSaveDialog async method and I get the results I am looking for. Thanks again for taking the time to help me. – LennyL Apr 21 '22 at 22:14
1

You should await the DisplayReportSaveDialog before you call CreateReport.

For you to be able to do this, you need to change the return type of the former method from void to Task so you code will look like this:

private async void myButton_Click(object sender, RoutedEventArgs e)
{
    await DisplayReportSaveDialog("Report1");
    CreateReport(File_Name);
}

private async Task DisplayReportSaveDialog(string ReportName)
{
    //same code as before...
}

private void CreateReport(string filename)
{
    //same code as before...
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Thank you @mm8 this worked. I also found out a few things. I need to pass the file.Path to my CreateReport() method. Second, the FileSavePicker actually creates a physical file. I wanted only a file specification, that I would then pass to the CreateReport() method. Despite this, my CreateReport() method is able to successfully write to the file created by the FileSavePicker. Thank you again for your help. You always seem to come through for me :-) – LennyL Apr 20 '22 at 19:06