ProgressBar w ExcelDataReader AsDataSet

0

Witam,
Proszę o pomoc w rozwiązaniu problemu.
Wczytuję plik excela, z którego pobieram arkusz "Baza" do datagridview poprzez ExcelDataReader AsDataSet
Po wciśnięciu przycisku wykonuje się zdarzenie poniżej:

private void btnImportFile_Click(object sender, EventArgs e)
        {
            try
            {
                using (OpenFileDialog ofdImportFile = new OpenFileDialog() { Filter = "Excel file | *.xlsm" })
                {
                    if (ofdImportFile.ShowDialog() == DialogResult.OK)
                    {
                        // wersja stream
                        using (FileStream stream = File.Open(ofdImportFile.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                        {

                            using (IExcelDataReader reader = ExcelReaderFactory.CreateOpenXmlReader(stream))
                            {
                                bgWorker.WorkerReportsProgress = true;
                                bgWorker.RunWorkerAsync();

                                dsImport = reader.AsDataSet(new ExcelDataSetConfiguration()
                                {
                                    ConfigureDataTable = (_) => new ExcelDataTableConfiguration()
                                    {
                                        UseHeaderRow = true,
                                        FilterRow = (rowReader) =>
                                        {
                                            var progress = rowReader.Depth / rowReader.RowCount;
                                            return true;
                                            bgWorker.ReportProgress(progress);
                                        }
                                    }
                                });
                                dt = dsImport.Tables["Baza"];

                                //wypełnienie datagridview danymi
                                dgvImportExcel.DataSource = dt;
                                
                            }
                        }
                    }
                }
            }
            catch (Exception ex) { MessageBox.Show(ex.Message); }
           
        }
        
        private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage; 
        }
        
        private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i <= 100; i++)
            {
                //Thread.Sleep(100);
                bgWorker.ReportProgress(i);
            }
        }

Problem polega na tym, że progressBar pokazuje postęp dopiero po wypełnieniu datagridview i nie wiem dlaczego a oczekiwanym efektem jest pokazanie stanu wczytywania pliku.
Proszę o wskazanie powodu nieprawidłowości i ewentualne rozwiązanie.

0

Nie mam pewności bo w C# ostatnio pisałem 15 lat temu ale możesz zobaczyć czy zadziała taka zmiana?

 private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage; 
            Application.DoEvents();
        }
0
katakrowa napisał(a):

Nie mam pewności bo w C# ostatnio pisałem 15 lat temu ale możesz zobaczyć czy zadziała taka zmiana?

 private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage; 
            Application.DoEvents();
        }

Powyższa zmiana nie rozwiązuje problemu :(

1
private delegate void CallInvalidate();
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage; 
            Invoke(new CallInvalidate(progressBar.Invalidate),null);
    }

lub

private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
         progressBar.Value = e.ProgressPercentage; 
         Invoke(new Action(() => progressBar.Invalidate));
    }
0
_13th_Dragon napisał(a):
private delegate void CallInvalidate();
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage; 
            Invoke(new CallInvalidate(progressBar.Invalidate),null);
    }

Dzięki za odpowiedź.
Powyższe rozwiązanie wydłuża czas przy wczytywaniu, natomiast sam wskaźnik postępu wciąż pokazuje się po załadowaniu datagridview i jest jak było.

Druga wersja podobnie.

0
RootX93 napisał(a):
_13th_Dragon napisał(a):

Powyższe rozwiązanie wydłuża czas przy wczytywaniu, natomiast sam wskaźnik postępu wciąż pokazuje się po załadowaniu datagridview i jest jak było.

Więc użyj normalnego wątku zamiast BackgroundWorker'a

0
_13th_Dragon napisał(a):
RootX93 napisał(a):
_13th_Dragon napisał(a):

Powyższe rozwiązanie wydłuża czas przy wczytywaniu, natomiast sam wskaźnik postępu wciąż pokazuje się po załadowaniu datagridview i jest jak było.

Więc użyj normalnego wątku zamiast BackgroundWorker'a

Nie jestem mistrzem programowania, raczej adeptem, więc nie kojarzę co masz na myśli. Może jakiś przykład.
Poza tym, może problem nie jest w tym wariancie a miejscu wywołania lub parametrze, który jest użyty.
Dlaczego progressBar zaczyna działanie po wypełnieniu grida a nie w czasie ładowania danych z pliku.
Takie tam pytania :)

1

Na pewno linijka 27. nie powinna tak wyglądać. Aktualizujesz progres po zwróceniu true?

0

Rozwiązanie @_13th_Dragon jest poprawne. Pokaz, jak tworzysz progress i jakie wartości ustawiasz

0
wielki_bebzon napisał(a):

Na pewno linijka 27. nie powinna tak wyglądać. Aktualizujesz progres po zwróceniu true?

Słuszna uwaga. Spostrzegłem ją.
Dodatkowo rozwiązałem problem choć nie do końca mnie satysfakcjonuje.

int progress = (int)Math.Ceiling((decimal)rowReader.Depth / (decimal)rowReader.RowCount * (decimal)100);
// progress is in the range 0..100
progressBar.Value = progress;

Zmieniam bezpośrednio wartość Value progressBar
Lecz zauważyłem, że w przypadku gdy w skoroszycie jest więcej niż jeden arkusz to nie zawsze progress dochodzi do 100 na koniec wczytania pliku.
Chyba, że ktoś poda mi jak wczytać w prezentowanym przykładzie, jak wczytać dokładnie ten arkusz, który mnie interesuje bo tego jeszcze nie rozgryzłem :)

0
gswidwa1 napisał(a):

Rozwiązanie @_13th_Dragon jest poprawne. Pokaz, jak tworzysz progress i jakie wartości ustawiasz

Nie napisałem, że jest błędne tylko, że po wprowadzeniu zmian nic nie zmieniło.
Wszystko co tworzy progress jest w zamieszczonych listingach kropka w kropkę.
Jak napisał wielki_bebzon i co sam zauważyłem wywołanie progressa po zwróceniu true z FilterRow było co najmniej dziwne więc szukałem dlaczego ale nie znalazłem ale wstawienie wywołania progressa przed return true nie wyświetlało danych w gridzie i zwracało komunikat.

0

Nie przejrzałem się całości, dopiero teraz widzę

private void btnImportFile_Click(object sender, EventArgs e)
{
     bgWorker.WorkerReportsProgress = true;
     bgWorker.RunWorkerAsync();
}
        
        private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage;
            Invoke(new Action(() => progressBar.Invalidate));
        }
        
        private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                using (OpenFileDialog ofdImportFile = new OpenFileDialog() { Filter = "Excel file | *.xlsm" })
                {
                    if (ofdImportFile.ShowDialog() == DialogResult.OK)
                    {
                        // wersja stream
                        using (FileStream stream = File.Open(ofdImportFile.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                        {

                            using (IExcelDataReader reader = ExcelReaderFactory.CreateOpenXmlReader(stream))
                            {
                                dsImport = reader.AsDataSet(new ExcelDataSetConfiguration()
                                {
                                    ConfigureDataTable = (_) => new ExcelDataTableConfiguration()
                                    {
                                        UseHeaderRow = true,
                                        FilterRow = (rowReader) =>
                                        {
                                            var progress = rowReader.Depth / rowReader.RowCount;
                                            return true;
                                            bgWorker.ReportProgress(progress);
                                        }
                                    }
                                });
                                dt = dsImport.Tables["Baza"];

                                //wypełnienie datagridview danymi
                                dgvImportExcel.DataSource = dt;
                                
                            }
                        }
                    }
                }
            }
            catch (Exception ex) { MessageBox.Show(ex.Message); }
        }

Wydaje mi się że warto zacząć od obejrzenia przykładów użycia backgroundworkera

0
_13th_Dragon napisał(a):

Nie przejrzałem się całości, dopiero teraz widzę

private void btnImportFile_Click(object sender, EventArgs e)
{
     bgWorker.WorkerReportsProgress = true;
     bgWorker.RunWorkerAsync();
}
        
        private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage;
            Invoke(new Action(() => progressBar.Invalidate));
        }
        
        private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                using (OpenFileDialog ofdImportFile = new OpenFileDialog() { Filter = "Excel file | *.xlsm" })
                {
                    if (ofdImportFile.ShowDialog() == DialogResult.OK)
                    {
                        // wersja stream
                        using (FileStream stream = File.Open(ofdImportFile.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                        {

                            using (IExcelDataReader reader = ExcelReaderFactory.CreateOpenXmlReader(stream))
                            {
                                dsImport = reader.AsDataSet(new ExcelDataSetConfiguration()
                                {
                                    ConfigureDataTable = (_) => new ExcelDataTableConfiguration()
                                    {
                                        UseHeaderRow = true,
                                        FilterRow = (rowReader) =>
                                        {
                                            var progress = rowReader.Depth / rowReader.RowCount;
                                            return true;
                                            bgWorker.ReportProgress(progress);
                                        }
                                    }
                                });
                                dt = dsImport.Tables["Baza"];

                                //wypełnienie datagridview danymi
                                dgvImportExcel.DataSource = dt;
                                
                            }
                        }
                    }
                }
            }
            catch (Exception ex) { MessageBox.Show(ex.Message); }
        }

Wydaje mi się że warto zacząć od obejrzenia przykładów użycia backgroundworkera

Przetestowałem przedstawiony przypadek ale zwraca on wyjątek:
"Bieżący wątek musi być ustawiony na tryb jednowątkowego apartamentu, aby można było wykonywać wywołania OLE.
Upewnij się, że w funkcji Main jest zaznaczony element STAThreadAttribute...."

0

Operacje interfejsowe (między innymi otwarcie dialogu) muszą być w wątku główny, zaś całą praca musi być w wątku.

        private string openedFileName;
        private object ExcelDataSource;
        private void btnImportFile_Click(object sender, EventArgs e)
        {
                using (OpenFileDialog ofdImportFile = new OpenFileDialog() { Filter = "Excel file | *.xlsm" })
                {
                    if (ofdImportFile.ShowDialog() == DialogResult.OK)
                    {
                        openedFileName=ofdImportFile.FileName;
                        bgWorker.WorkerReportsProgress = true;
                        bgWorker.RunWorkerAsync();
                    }
                }
        }
        
        private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage;
            Invoke(new Action(() => progressBar.Invalidate));
        }
        
        private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                      // wersja stream
                      using (FileStream stream = File.Open(openedFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                      {

                          using (IExcelDataReader reader = ExcelReaderFactory.CreateOpenXmlReader(stream))
                          {
                              dsImport = reader.AsDataSet(new ExcelDataSetConfiguration()
                              {
                                  ConfigureDataTable = (_) => new ExcelDataTableConfiguration()
                                  {
                                      UseHeaderRow = true,
                                      FilterRow = (rowReader) =>
                                      {
                                          var progress = rowReader.Depth / rowReader.RowCount;
                                          bgWorker.ReportProgress(progress);
                                          return true;
                                      }
                                  }
                              });
                              ExcelDataSource = dsImport.Tables["Baza"];
                          }
                      }
            }
            catch (Exception ex) { MessageBox.Show(ex.Message); }
        }

        //nie zapomnieć podpiąć pod zdarzenie!
        private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
          dgvImportExcel.DataSource = ExcelDataSource;
        }
0
_13th_Dragon napisał(a):

Operacje interfejsowe (między innymi otwarcie dialogu) muszą być w wątku główny, zaś całą praca musi być w wątku.
...

Obecna wersja zgłasza wyjątek po pewnym czasie:
"Nieprawidłowa operacja między wątkami: dla formantu datagridview uzyskiwany jest dostęp z wątku innego niż wątek, w którym został utworzony. "
Na pewno muszę bardziej zgłębić backgroudworker by go zrozumieć bo przykłady, które już poznałem nie dały rozwiązania poza tym, który przedstawiłem bez wykorzystania wspomnianej metody.

0

To daj dgvImportExcel.DataSource = ExcelDataSource; w Invoke:
Invoke(new Action(() => dgvImportExcel.DataSource = ExcelDataSource));

0
_13th_Dragon napisał(a):

To daj dgvImportExcel.DataSource = ExcelDataSource; w Invoke:
Invoke(new Action(() => dgvImportExcel.DataSource = ExcelDataSource));

Dokonane zmiany sprawiły, że dane wczytują się a w progressBar nic się nie wyświetla.
Ale zmiana zmiennej var progress na int progress już działa:

int progress = (int)Math.Ceiling((decimal)rowReader.Depth / (decimal)rowReader.RowCount * (decimal)100);
0

Postaw breakpoint na 40 wiersz i zobacz ile razy on tam się zatrzyma na jedno odświeżenie.

0

Można rzec, że problem został rozwiązany a cel osiągnięty.
Pytanie następne czy można od razu wczytać konkretny arkusz w tym przykładzie, bo gdy skoroszyt zawiera wiele arkuszy to progressBar pokazuje błędy wskazania postępu.
W tym przypadku interesuje mnie tylko arkusz "Baza" a nie wszystkie. Jak zmodyfikować ExcelDataReader do wczytania tylko tego arkusza z uwzględnieniem progresu.

0
_13th_Dragon napisał(a):

Postaw breakpoint na 40 wiersz i zobacz ile razy on tam się zatrzyma na jedno odświeżenie.

Breakpointa umiem postawić ale jak mam odczytać to o czym piszesz w vs2022 nie wiem.

0
_13th_Dragon napisał(a):

https://lmgtfy.app/?q=exceldatareader+read+only+one+sheet

Rozwiązanie jest następujące:

dsImport = reader.AsDataSet(new ExcelDataSetConfiguration()
                        {
                            FilterSheet = (tableReader, sheetIndex) =>
                            {
                                string sheet = tableReader.Name;
                                if (sheet == "Baza")
                                    return true;
                                else return false;
                            },
                            ConfigureDataTable = (_) => new ExcelDataTableConfiguration()
                            {
                                UseHeaderRow = true,
                                FilterRow = (rowReader) =>
                                {
                                    int progress = (int)Math.Ceiling((decimal)rowReader.Depth / (decimal)rowReader.RowCount * (decimal)100);
                                    bgWorker.ReportProgress(progress);
                                    return true;
                                }
                            }
                        });

Należy skorzystać z właściwości FilterSheet, której sprawdzamy czy dany arkusz jest tym, który nas interesuje i dopiero go wczytujemy, pomijając wszystkie pozostałe.
Tamat zamykam.

1
FilterSheet = (tableReader, sheetIndex) => tableReader.Name=="Baza",
0
_13th_Dragon napisał(a):
FilterSheet = (tableReader, sheetIndex) => tableReader.Name=="Baza",

Niech żyje zgrabność :)
Dzięki!!!

1 użytkowników online, w tym zalogowanych: 0, gości: 1