C# WinUI 3 我无法使用从 FileSavePicker 对话框指定的文件来创建 excel 报告文件

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

我的第一个 WinUI 3 项目。我的第二个 C# 项目。我已经能够让 FileSavePicker 工作并显示一个对话框:

我要展示的是一个存根程序,它只是为了解决我在使用 FileSavePicker 时遇到的问题而创建的。

问题描述:我不知道如何将非异步方法'CreateReport()'与异步FileSavePicker一起使用。

我已经用 'Create Report' 按钮测试了 CreateReport() 方法,它成功地创建了一个 excel 文件并成功写入该文件(使用 EPPlus)。

如何让 CreateReport() 方法等待 FileSavePicker 完成,然后使用 FileSavePicker 中的 file.name?

我尝试这样做会生成一个 excel 文件,但是当我尝试打开该文件时,excel 给我这个错误:

主窗口:

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# 代码隐藏

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

只需删除评论的 Microsoft 内容:

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

//await file.DeleteAsync(); 

而不是调用您自己的函数:

CreateReport(file.Name);

您应该等待 DisplayReportSaveDialog,然后再致电 CreateReport

为了能够做到这一点,您需要将前一个方法的 return 类型从 void 更改为 Task,这样您的代码将如下所示:

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