ProgressBar w ExcelDataReader AsDataSet

ProgressBar w ExcelDataReader AsDataSet
RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
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:

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

katakrowa
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Chorzów
  • Postów: 1670
0

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

Kopiuj
 private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage; 
            Application.DoEvents();
        }
RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
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?

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

Powyższa zmiana nie rozwiązuje problemu :(

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
1
Kopiuj
private delegate void CallInvalidate();
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage; 
            Invoke(new CallInvalidate(progressBar.Invalidate),null);
    }

lub

Kopiuj
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
         progressBar.Value = e.ProgressPercentage; 
         Invoke(new Action(() => progressBar.Invalidate));
    }
RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
0
_13th_Dragon napisał(a):
Kopiuj
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.

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
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

RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
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 :)

RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
WB
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 18
1

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

G1
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 507
0

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

RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
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.

Kopiuj
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 :)

RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
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.

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
0

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

Kopiuj
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

RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
0
_13th_Dragon napisał(a):

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

Kopiuj
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...."

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
0

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

Kopiuj
        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;
        }
RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
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.

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
0

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

RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
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:

Kopiuj
int progress = (int)Math.Ceiling((decimal)rowReader.Depth / (decimal)rowReader.RowCount * (decimal)100);
_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
0

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

RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
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.

RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
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.

RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
0
_13th_Dragon napisał(a):

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

Rozwiązanie jest następujące:

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

_13th_Dragon
  • Rejestracja: dni
  • Ostatnio: dni
1
Kopiuj
FilterSheet = (tableReader, sheetIndex) => tableReader.Name=="Baza",
RootX93
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 19
0
_13th_Dragon napisał(a):
Kopiuj
FilterSheet = (tableReader, sheetIndex) => tableReader.Name=="Baza",

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

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.