tomgif
@tomgif
Веб-разработчик

Как подружить ClosedXML.Excel с BackgroundWorker для progressBar?

Я новичок в разработке WPF приложений, так как у меня немного другая специализация.
Я пишу приложение в котором хочу импортировать данные из Excel-таблицы. Наткнулся на решение, в котором чтение файла происходит через XLWorkbook из библиотеки ClosedXML.Excel.

static IEnumerable<Metric> EnumerateMetrics(string xlsxpath)
{
    using (var workbook = new XLWorkbook(xlsxpath))
    using (var worksheet = workbook.Worksheets.Worksheet(1))
    {
        for (int row = 2; row <= 3; ++row)
        {
            var metric = new Metric
            {
                Alpha = worksheet.Cell(row, 1).GetValue<int>(),
                Beta = worksheet.Cell(row, 2).GetValue<int>(),
                Gamma = worksheet.Cell(row, 3).GetValue<int>(),
                Delta = worksheet.Cell(row, 4).GetValue<int>(),
            };
            yield return metric;
        }
    }
}


Мне необходимо предварительно считать файл, для того чтобы определить количество страниц, а также дать пользователю выбрать необходимую страницу для загрузки данных.

Проблема: при выборе файла и пропихивании его в конструктор XLWorkbook - окно замерзает до полного считывания данных. Необходимо отобразить прогресс этой загрузки при помощи ProgressBar-а. Немного поисков натолкнули на информацию о том, что такая задача решается в отдельном потоке через BackgroundWorker-ы.

Нужно решить задачу с импортом файла и индикацией прогресса его загрузки.
  • Вопрос задан
  • 46 просмотров
Решения вопроса 1
tomgif
@tomgif Автор вопроса
Веб-разработчик
Проблема с замерзанием окна была в том, что считывание файла через 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();
        }
    }
}


Возможно это пригодится кому нибудь в будущем
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы