CYFROWY BARON • PROGRAMOWANIE • Zobacz wątek - StringGrid - otwieranie *.csv

StringGrid - otwieranie *.csv

dział ogólny

StringGrid - otwieranie *.csv

Nowy postprzez zasuh » wtorek, 2 października 2012, 11:36

Witam,
Czy ktoś może wie jak otwierać pliki formatu *.csv w komponencie StringGrid (C++ Builder)? Wiem, że w serwisie jest sposób zapisu danych komponentu StringGrid w formacie pliku *.csv, ale nigdzie nie mogę znaleźć jak taki plik otworzyć w StringGrid.
Pozdrawiam
zasuh
Pozdrawiam
zasuh
Avatar użytkownika
zasuh
Bladawiec
Bladawiec
 
Posty: 2
Dołączył(a): poniedziałek, 1 października 2012, 12:09
Podziękował : 0
Otrzymał podziękowań: 0
System operacyjny: Windows 7
Kompilator: C++ Builder6
Gadu Gadu: 0
    Windows XPFirefox

Re: StringGrid - otwieranie *.csv

Nowy postprzez Mironas » wtorek, 2 października 2012, 12:13

Np. tak jak poniżej.
Parametr sep określa separator kolumn w pliku CSV. Przeważnie jest to średnik.
KOD cpp:     UKRYJ  
void TForm1::WczytajCSV(String plik, String sep)
{
  TStrings* plikcsv = new TStringList();
  TStrings* wiersz = new TStringList();
  int fr = StringGrid1->FixedRows;
  int fc = StringGrid1->FixedCols;
  TReplaceFlags rf = TReplaceFlags() << rfReplaceAll;

  // wczytaj plik CSV
  plikcsv->LoadFromFile(plik);
  // ustal ilość wierszy i poczatkową ilość kolumn
  StringGrid1->RowCount = fr + plikcsv->Count;
  StringGrid1->ColCount = fc + 1;

  for (int w=0 ; w<plikcsv->Count ; w++)
  {
    // wczytaj wiersz
    wiersz->Text = StringReplace(plikcsv->Strings[w], sep, "\r\n", rf);
    // jeśli potrzeba to zwiększ ilość kolumn
    if ( StringGrid1->ColCount < fc+wiersz->Count )
      StringGrid1->ColCount = fc+wiersz->Count;
    // wpisz dane do komórek StringGrid-a
    for (int k=0 ; k<wiersz->Count ; k++)
      StringGrid1->Cells[k+fc][w+fr] = wiersz->Strings[k];
  }

  delete plikcsv;
  delete wiersz;
}
 
Avatar użytkownika
Mironas
Programista I
Programista I
 
Posty: 427
Dołączył(a): poniedziałek, 2 stycznia 2012, 19:02
Podziękował : 17
Otrzymał podziękowań: 61
System operacyjny: Windows 10
Kompilator: C++Builder 10.3 Rio
TMS Components Pack
Gadu Gadu: 0
    Windows XPChrome

Re: StringGrid - otwieranie *.csv

Nowy postprzez Corvis » wtorek, 2 października 2012, 21:57

"Sukcesy trwają, dopóki ich ktoś nie spieprzy. Porażki są wieczne"

Dr Gregory House
Avatar użytkownika
Corvis
Programista I
Programista I
 
Posty: 880
Dołączył(a): sobota, 26 lipca 2008, 00:31
Podziękował : 80
Otrzymał podziękowań: 30
System operacyjny: WINDOWS 7 64-bity
Kompilator: Praca - C++ Builder XE2 ENTERPRISE - Update 4, Dom - C++ Builder XE4 - Uddate 1
Gadu Gadu: 0
    Windows 7Chrome

Re: StringGrid - otwieranie *.csv

Nowy postprzez Cyfrowy Baron » środa, 3 października 2012, 11:26

Ja wymyśliłem coś takiego:

KOD cpp:     UKRYJ  
void __fastcall LoadFromCSV(String FileName, TStringGrid *Grid)
{
 std::auto_ptr<TStringList> fileList(new TStringList);
 std::auto_ptr<TStringList> phraseList(new TStringList);

 fileList->LoadFromFile( FileName );
 phraseList->LineBreak = ";"; // średnik separatorem w pliku CSV

 /* w starszej wersji biblioteki może nie być tej funkcji */
 for(int i = 0; i < Grid->RowCount; i++) Grid->Rows[ i ]->Clear();

 Grid->RowCount = fileList->Count + 1;
 Grid->ColCount = 2;

 Grid->FixedRows = 1;
 Grid->FixedCols = 1;

 for(int r = 0; r < fileList->Count; r++)
 {
  phraseList->SetText( fileList->Strings[r].c_str() );

  if( Grid->ColCount + 1 < phraseList->Count ) Grid->ColCount = phraseList->Count + 1;

  for(int c = 0; c < phraseList->Count; c++)
   Grid->Cells[c + 1][r + 1] = phraseList->Strings[c];
 }

}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 LoadFromCSV("plik.csv", StringGrid1);
}


Separatorem jest średnik, więc tekst nie może zawierać średnika, w przeciwnym razie tabelka się posypie. Nie jestem pewien, czy w C++Builder 6 to zadziała, bo występująca tam klasa TStringGrid może pewnych zawartych w tym kodzie metod nie obsługiwać.

Podany wyżej przykład nie wypełnia kolumn i wierszy nagłówka, ale wystarczy go zmodyfikować tak:

KOD cpp:     UKRYJ  
void __fastcall LoadFromCSV(String FileName, TStringGrid *Grid)
{
 std::auto_ptr<TStringList> fileList(new TStringList);
 std::auto_ptr<TStringList> phraseList(new TStringList);

 fileList->LoadFromFile( FileName );
 phraseList->LineBreak = ";"; // średnik separatorem w pliku CSV

 /* w starszej wersji biblioteki może nie być tej funkcji */
 for(int i = 0; i < Grid->RowCount; i++) Grid->Rows[i]->Clear();

 Grid->RowCount = fileList->Count;
 Grid->ColCount = 2;

 Grid->FixedRows = 1;
 Grid->FixedCols = 1;

 for(int r = 0; r < fileList->Count; r++)
 {
  phraseList->SetText( fileList->Strings[r].c_str() );

  if( Grid->ColCount < phraseList->Count ) Grid->ColCount = phraseList->Count;

  for(int c = 0; c < phraseList->Count; c++)
   Grid->Cells[c][r] = phraseList->Strings[c];
 }
}


Ten kod można jeszcze zoptymalizować eliminując jedną z pętli:

KOD cpp:     UKRYJ  
void __fastcall LoadFromCSV(String FileName, TStringGrid *Grid)
{
 std::auto_ptr<TStringList> fileList(new TStringList);
 std::auto_ptr<TStringList> phraseList(new TStringList);

 fileList->LoadFromFile( FileName );
 phraseList->LineBreak = ";"; // średnik separatorem w pliku CSV

 /* w starszej wersji biblioteki może nie być tej funkcji */
 for(int i = 0; i < Grid->RowCount; i++) Grid->Rows[i]->Clear();

 Grid->RowCount = fileList->Count;
 Grid->ColCount = 2;

 Grid->FixedRows = 1;
 Grid->FixedCols = 1;

 for(int r = 0; r < fileList->Count; r++)
 {
  phraseList->SetText( fileList->Strings[r].c_str() );

  if( Grid->ColCount < phraseList->Count ) Grid->ColCount = phraseList->Count;

  /* w starszej wersji biblioteki może nie być tej funkcji */
  Grid->Rows[r]->AddStrings( phraseList.get() );
 }
}
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    Windows XPFirefox

Re: StringGrid - otwieranie *.csv

Nowy postprzez zasuh » piątek, 5 października 2012, 13:38

Mironas,
Twój kod odczytuje plik o formacie csv, ale jest pewien mankament. Dane w komórkach StringGrid są w cudzysłowiu (""), cudzysłów wstawiany jest również w puste komórki. Czy jest jakaś możliwość żeby ten cudzysłów usunąć?
Ponadto pozwoliłem sobie na małą korektę w kodzie. Wartość zmiennej w i k zmieniłem na 1. Funkcja zapisywała tabelę od indeksu [0,0]. W moim programie w StringGrid wykorzystuję FixedCol i FixedRow, dlatego pole robocze zaczyna się od indeksu [1,1].
Dzięki za Twój post.
Pozdrawiam
zasuh
Pozdrawiam
zasuh
Avatar użytkownika
zasuh
Bladawiec
Bladawiec
 
Posty: 2
Dołączył(a): poniedziałek, 1 października 2012, 12:09
Podziękował : 0
Otrzymał podziękowań: 0
System operacyjny: Windows 7
Kompilator: C++ Builder6
Gadu Gadu: 0
    Windows XPFirefox

Re: StringGrid - otwieranie *.csv

Nowy postprzez Mironas » piątek, 5 października 2012, 14:14

zasuh napisał(a):Dane w komórkach StringGrid są w cudzysłowiu ("")

Nie rozumiem co masz na myśli. Funkcja wstawia teksty w komórki StringGrid-a tak jak są w pliku CSV. Nie dodaje cudzysłowów.

Jeśli w pliku CSV masz cudzysłowy i chcesz aby puste wartości ("") nie wpisywały się do komórek to musisz dodać warunek:
KOD cpp:     UKRYJ  
    // wpisz dane do komórek StringGrid-a
    for (int k=0 ; k<wiersz->Count ; k++)
      if ( wiersz->Strings[k] != "\"\"" )    // <- to jest dodany warunek
        StringGrid1->Cells[k+fc][w+fr] = wiersz->Strings[k];
 

Tylko po co wartości w pliku CSV trzymać w cudzysłowach?
Avatar użytkownika
Mironas
Programista I
Programista I
 
Posty: 427
Dołączył(a): poniedziałek, 2 stycznia 2012, 19:02
Podziękował : 17
Otrzymał podziękowań: 61
System operacyjny: Windows 10
Kompilator: C++Builder 10.3 Rio
TMS Components Pack
Gadu Gadu: 0
    Windows XPChrome

Re: StringGrid - otwieranie *.csv

Nowy postprzez Mironas » piątek, 5 października 2012, 14:21

zasuh napisał(a):Wartość zmiennej w i k zmieniłem na 1. Funkcja zapisywała tabelę od indeksu [0,0]. W moim programie w StringGrid wykorzystuję FixedCol i FixedRow, dlatego pole robocze zaczyna się od indeksu [1,1].

Przykładowa funkcja WczytajCSV wczytuje dane do StringGrid-a z pliku z uwzględnieniem FixedCol i FixedRow. Odpowiadają za to zmienne fr i fc. Oczywiście wartości FixedCol i FixedRow muszą być ustawione przed wykonaniem tej funkcji.
Avatar użytkownika
Mironas
Programista I
Programista I
 
Posty: 427
Dołączył(a): poniedziałek, 2 stycznia 2012, 19:02
Podziękował : 17
Otrzymał podziękowań: 61
System operacyjny: Windows 10
Kompilator: C++Builder 10.3 Rio
TMS Components Pack
Gadu Gadu: 0
    Windows XPChrome

Re: StringGrid - otwieranie *.csv

Nowy postprzez Cyfrowy Baron » sobota, 6 października 2012, 09:17

Właściwie to istnieje bardzo prosty sposób, który działa z plikami CSV i prawidłowo obsługuje cudzysłowy - prawidłowo, czyli jeśli wstawisz tam cudzysłowy jako tekst to zostaną wczytane, w innych przypadkach nie. Jakie to przypadki?

Jeżeli np. wprowadzasz do komórek takie dane: | tekst 1 | tekst 2 | tekst 3 | to w pliku CSV zostaną zapisane tak: tekst 1;tekst 2;tekst 3, a do tabeli StringGrid zostaną wczytane tak: | tekst 1 | tekst 2 | tekst 3 |
Jeżeli jednak wprowadzisz takie dane: | tekst 1 | tekst 2;a | "tekst 3" | to w pliku CSV zostaną zapisane tak: tekst 1;"tekst 2;a";"""tekst 3"""

Jak widać tekst zawierający średnik został ujęty w cudzysłowy, a same cudzysłowy zostały dodatkowo jeszcze ujęte w cudzysłowy. W zaprezentowanych we wcześniejszych postach metodach te cudzysłowy zostaną wczytane jako tekst, czyli komórki będą zawierałby takie treści: | tekst 1 | "tekst 2 | a" | """tekst 3""" |. Jak widać doszła jeszcze jedna komórka, gdyż tekst zawierający średnik został podzielony na dwie części. Cudzysłowy pozostały.

Ja proponuję metodę, która wczyta dane tak jak wczytane być powinny, czyli: | tekst 1 | tekst 2;a | "tekst 3" |

Pierwszy, szybszy sposób, ale może nie działać w starszej wersji środowiska:

KOD cpp:     UKRYJ  
#include "memory" /* biblioteka zawierająca metodę auto_ptr */

void __fastcall LoadFromCSV(String FileName, TStringGrid *Grid)
{
 std::auto_ptr<TStringList> fileList(new TStringList);
 std::auto_ptr<TStringList> phraseList(new TStringList);

 fileList->LoadFromFile( FileName );
 phraseList->Delimiter = ';';
 phraseList->StrictDelimiter = true;

 /* w starszej wersji biblioteki może nie być tej funkcji */
 for(int i = 0; i < Grid->RowCount; i++) Grid->Rows[i]->Clear();

 Grid->RowCount = fileList->Count;
 Grid->ColCount = 2;

 /* wiersz i kolumna nagłówka */
 Grid->FixedRows = 1;
 Grid->FixedCols = 1;

 for(int r = 0; r < fileList->Count; r++)
 {
  phraseList->DelimitedText = fileList->Strings[r];

  if( Grid->ColCount < phraseList->Count ) Grid->ColCount = phraseList->Count;

  /* w starszej wersji biblioteki może nie być tej funkcji */
  Grid->Rows[r]->AddStrings( phraseList.get() );
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 LoadFromCSV("plik.csv", StringGrid1);
}


Drugi sposób, który zadziała w starszych wersjach środowiska, ale działa wolniej:

KOD cpp:     UKRYJ  
#include "memory" /* biblioteka zawierająca metodę auto_ptr */

void __fastcall ClearGrid(TStringGrid *Grid)
{
  for(int r = 0; r < Grid->RowCount; r++)
  {
        for(int c = 0; c < Grid->ColCount; c++)
        {
         Grid->Cells[c][r] = "";
    }
  }
}
//---------------------------------------------------------------------------
void __fastcall LoadFromCSV(String FileName, TStringGrid *Grid)
{
 std::auto_ptr<TStringList> fileList(new TStringList);
 std::auto_ptr<TStringList> phraseList(new TStringList);

 fileList->LoadFromFile( FileName );
 phraseList->Delimiter = ';';
 phraseList->StrictDelimiter = true;

 ClearGrid(Grid);

 Grid->RowCount = fileList->Count;
 Grid->ColCount = 2;

 /* wiersz i kolumna nagłówka */
 Grid->FixedRows = 1;
 Grid->FixedCols = 1;

 for(int r = 0; r < fileList->Count; r++)
 {
  phraseList->DelimitedText = fileList->Strings[r];

  if( Grid->ColCount < phraseList->Count ) Grid->ColCount = phraseList->Count;

  for(int c = 0; c < phraseList->Count; c++)
   Grid->Cells[c][r] = phraseList->Strings[c];
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 LoadFromCSV("plik.csv", StringGrid1);
}


U mnie obie funkcje działają bez zarzutu
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    Windows XPFirefox

Re: StringGrid - otwieranie *.csv

Nowy postprzez Mironas » sobota, 6 października 2012, 16:16

Cyfrowy Baron napisał(a):U mnie obie funkcje działają bez zarzutu

Sprawdziłem pierwszą funkcję i faktycznie działa sprawnie - jednak poza jednym specyficznym wyjątkiem. Jeśli w pliku CSV na początku pola będzie podwójny cudzysłów to w StringGrid pojawi się w tym miejscu pusta komórka. Dalszy ciąg tekstu (po podwójnym cudzysłowie) będzie wstawiony do następnej komórki.
W większości przypadków taki problem pewnie nie wystąpi bo po co wstawiać podwójny cudzysłów na początku tekstu - ale w specyficznej sytuacji może się to zdażyć.

Dlatego proponuję jako separator tekstu zamiast średnika wykorzystać inny znak niedostępny z klawiatury (o ile jest to możliwe). Poniżej poprzedni przykład Cyfrowego Barona gdzie zmieniłem średnik na znak asci=3 (ETX):
KOD cpp:     UKRYJ  
void __fastcall TForm1::ToolButton3Click(TObject *Sender)
{
  String plik = "Dane02.csv";
  String sep = (char)3;      // znak ETX (ascii=3) jako separator komórek
  LoadFromCSV(plik, StringGrid1, sep);
}
//---------------------------------------------------------------------------
void TForm1::LoadFromCSV(String FileName, TStringGrid* Grid, String Sep)
{
  std::auto_ptr<TStringList> fileList(new TStringList);
  std::auto_ptr<TStringList> phraseList(new TStringList);

  fileList->LoadFromFile( FileName );
  phraseList->LineBreak = Sep; // TUTAJ ZMIENIŁEM

  /* w starszej wersji biblioteki może nie być tej funkcji */
  for(int i = 0; i < Grid->RowCount; i++)
    Grid->Rows[i]->Clear();

  Grid->RowCount = fileList->Count;
  Grid->ColCount = 2;

  Grid->FixedRows = 1;
  Grid->FixedCols = 1;

  for(int r = 0; r < fileList->Count; r++)
  {
    phraseList->SetText( fileList->Strings[r].c_str() );
    if( Grid->ColCount < phraseList->Count )
      Grid->ColCount = phraseList->Count;
    /* w starszej wersji biblioteki może nie być tej funkcji */
    Grid->Rows[r]->AddStrings( phraseList.get() );
  }
}
//---------------------------------------------------------------------------
 
Avatar użytkownika
Mironas
Programista I
Programista I
 
Posty: 427
Dołączył(a): poniedziałek, 2 stycznia 2012, 19:02
Podziękował : 17
Otrzymał podziękowań: 61
System operacyjny: Windows 10
Kompilator: C++Builder 10.3 Rio
TMS Components Pack
Gadu Gadu: 0
    Windows XPChrome

Re: StringGrid - otwieranie *.csv

Nowy postprzez Cyfrowy Baron » sobota, 6 października 2012, 19:19

Mironas napisał(a):Jeśli w pliku CSV na początku pola będzie podwójny cudzysłów to w StringGrid pojawi się w tym miejscu pusta komórka.


W prawidłowo skonstruowanym pliku CSV - a o takim chyba mówimy - taka sytuacja nie może się zdarzyć, gdyż jeżeli nawet w komórkę wstawisz taki tekst: |""tekst 1| tekst 2 | to w pliku pojawi się to jako: """""tekst 1";tekst 2; i tylko taka konstrukcja pliku jest zgodna ze standardem CSV.

Mironas napisał(a):Dlatego proponuję jako separator tekstu zamiast średnika wykorzystać inny znak niedostępny z klawiatury (o ile jest to możliwe)


Albo mówimy o pliku CSV, a w takim przypadku separatorem zawsze jest średnik, przecinek lub inny znak typowy dla danego regionu, albo też rozmawiamy o innym formacie podobnym do CSV i wtedy używamy dowolnego separatora, ale należy pamiętać, że CSV używa średnika lub przecinka - Excel używa średnika.

Proponuję Ci utworzyć plik CSV w programie Excel, a wtedy zobaczysz, że niezależnie jakie dane wprowadzić do komórek tabeli Excel, to zaproponowany przeze mnie kod zawsze pobierze je prawidłowo.

W załączniku znajdziesz ekstremalnie skonstruowany plik CSV, ale prawidłowo sformatowany. Otwórz go sobie w Excelu i za pomocą podanego przeze mnie kodu w StringGrid, porównaj zawartość komórek, a zobaczysz, że plik jest odczytywany prawidłowo.

csv.png


Plik CSV:
KOD text:     UKRYJ  
"""""cell 1A";";;;cell 1B";cell 1C;;
cell 2A;";";;cell 2D;
;cell 3B;cell 3C;";;;";";;;"
"""""""""""""""""";"""""""""""""";"""""""""""";"""""""""""";""""""""""
";;;;;;;;";cell 5B;cell 5C;cell 5D;
Mironas napisał(a):Poniżej poprzedni przykład Cyfrowego Barona gdzie zmieniłem średnik na znak asci=3 (ETX):


To stary (wcześniejszy) kod, nie będzie działał dla prawidłowo skonstruowanych plików CSV. Jedynym prawidłowo działającym kodem dla prawidłowo skonstruowanych plików CSV zgodnych z typem MIME text/csv oraz z programem Excel jest kod, który zaproponowałem w poprzednim poście.


Nie masz wystarczających uprawnień, aby zobaczyć pliki załączone do tego postu.
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    Windows XPFirefox


Powrót do Ogólne problemy z programowaniem

Kto przegląda forum

Użytkownicy przeglądający ten dział: Brak zalogowanych użytkowników i 5 gości