C# WinUI 3 I cannot use the file specified from a FileSavePicker dialog to create an excel report file

1.4k views Asked by At

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);
        }
    }
}
2

There are 2 answers

1
demonplus On BEST ANSWER

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);
1
mm8 On

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...
}