Как совместить использование ClosedXML.Excel с BackgroundWorker для отслеживания прогресса выполнения операции с помощью progressBar?

Как можно реализовать импорт Excel-таблицы и отображение прогресса загрузки данных с помощью ProgressBar в WPF проекте, чтобы избежать зависания интерфейса при предварительном считывании файла?
  • 29 апреля 2024 г. 13:10
Ответы на вопрос 2
Для совмещения использования ClosedXML.Excel с BackgroundWorker и отслеживания прогресса с помощью ProgressBar, вам нужно выполнить следующие шаги:

1. Создайте новый BackgroundWorker в вашем WPF проекте и настройте его для выполнения операции загрузки данных из Excel-файла.

2. В методе, который будет выполнять загрузку данных из Excel-файла, используйте библиотеку ClosedXML.Excel для чтения данных из файла.

3. Разделите операцию чтения данных на части и обновляйте прогресс выполнения с помощью метода ReportProgress BackgroundWorker. Например, можно обновлять прогресс после каждой обработанной строки данных.

4. В обработчике события ProgressChanged BackgroundWorker обновите значение прогресса ProgressBar согласно значению, переданному методом ReportProgress.

Пример кода может выглядеть следующим образом:

```csharp
private void StartBackgroundWorker()
{
    BackgroundWorker worker = new BackgroundWorker();
    worker.WorkerReportsProgress = true;
    worker.DoWork += LoadExcelData;
    worker.ProgressChanged += UpdateProgressBar;
    
    worker.RunWorkerAsync();
}

private void LoadExcelData(object sender, DoWorkEventArgs e)
{
    using (XLWorkbook workbook = new XLWorkbook("path/to/excel/file.xlsx"))
    {
        IXLWorksheet worksheet = workbook.Worksheet(1);
        
        int totalRows = worksheet.Rows().Count();
        int processedRows = 0;
        
        foreach (IXLRow row in worksheet.RowsUsed())
        {
            // Process row data
            
            processedRows++;
            int progress = (int)((double)processedRows / totalRows * 100);
            
            (sender as BackgroundWorker).ReportProgress(progress);
        }
    }
}

private void UpdateProgressBar(object sender, ProgressChangedEventArgs e)
{
    progressBar.Value = e.ProgressPercentage;
}
```

Таким образом, при выполнении операции чтения данных из Excel-файла, значение прогресса будет обновляться и отображаться на ProgressBar, позволяя пользователю отслеживать прогресс выполнения операции и избежать зависания интерфейса.
Проблема с замерзанием окна была в том, что считывание файла через new XLWorkbook(xlsxpath) необходимо размещать в DoWork событии. 

Следующий нюанс - это считывание прогресса чтения файла, так как я не уверен, что это возможно или же недостаточно компетентен для работы с потоками, то я решил отобразить ProgressBar с бесконечной загрузкой (IsIndeterminate="True", так как мы не знаем сколько времени будет считываться файл).

Весь код я решил вынести в отдельное окно. Получилось следующее, на правильность не претендую:

// ImportWindow.xaml.cs
public partial class ImportWindow : Window
{
    XLWorkbook Workbook = new XLWorkbook();

    public ImportWindow()
    {
        InitializeComponent();
    }

    // Кнопка Обзор, при помощи которой мы выбираем необходимый файл
    private void ChooseButton_Click(object sender, RoutedEventArgs e)
    {
        // Системный диалог для выбора файла
        Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog
        {
            // Ограничиваем расширения для выбора
            DefaultExt = ".xlsx",
            Filter = "Excel Files|*.xls;*.xlsx;*.xlsm"
        };

        if (dialog.ShowDialog() == true)
        {
            string path = dialog.FileName;

            PathTextBox.Text = path; // Поле ввода для отображения пути файла
            // Передаём в конструктор системный путь к файлу
            LoadingWindow loadingWindow = new LoadingWindow(path); 
            // Событие, через которое мы будем получать результат из дочернего окна
            loadingWindow.DataChanged += ImportWindow_DataChanged;
            // Открываем дочернее окно в модальном режиме
            loadingWindow.ShowDialog();
        }
    }

    // Обработка данных полученных из дочернего окна
    private void ImportWindow_DataChanged(object sender, XLWorkbook workbook)
    {
        Workbook = workbook;
        // Наполним выпадающий список страницами файла
        // В будущем здесь будет ObservableCollection, которую можно забиндить на выпадающий список
        PageComboBox.ItemsSource = workbook.Worksheets.ToList();
    }
}

//LoadingWindow.xaml.cs
public partial class LoadingWindow : Window
{
    BackgroundWorker worker = new BackgroundWorker();

    public delegate void DataChangedEventHandler(object sender, XLWorkbook e);
    public event DataChangedEventHandler DataChanged;

    public LoadingWindow(string filepath)
    {
        InitializeComponent();
        // Здесь мы создаём сам воркер и привязываем к нему события
        worker = new BackgroundWorker();
        worker.DoWork += DoWork; // событие для чтения файла в асинхронном потоке
        worker.RunWorkerCompleted += RunWorkerCompleted; // действия при успешном завершении
        worker.RunWorkerAsync(filepath); // Вызов асинхронной функции, нужно передать путь к файлу
    }

    // Это событие выполняется в отдельном потоке, поэтому у нас есть доступ только к аргументам
    void DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            e.Result = new XLWorkbook((string)e.Argument);
        }
        catch (Exception ex)
        {
            e.Result = ex;
        }
    }

    // Здесь мы обрабатываем данные после завершения чтения
    private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            MessageBox.Show("Операция отменена");
        }
        else if (e.Result is Exception) // Здесь мы ловим ошибки возникшие в процессе чтения
        {
            Exception ex = e.Result as Exception;
            MessageBox.Show(ex.Message);
        }
        else
        {
            // Здесь мы вызываем событие родительского окна и передаём в него результат работы
            // Результат работы мы кастим к типу XLWorkbook
            DataChanged?.Invoke(this, (XLWorkbook)e.Result);
            Close();
        }
    }
}


Возможно это пригодится кому нибудь в будущем
Похожие вопросы