Pozycjonowanie bieżącego wiersza w kontrolce TDBGrid

Pozycjonowanie bieżącego wiersza w kontrolce TDBGrid
GS
  • Rejestracja: dni
  • Ostatnio: dni
0

Mam dataset którego treść wizualizauję w kontrolce TJvDbgrid z pakietu JediComponents.
Potrzebuję wykonać na nim pewną akcję (np. podsumowanie wybranej kolumny), wymagającą chwilowej zmiany pozycji kursora w datacecie, a następnie przywrócić stan datasetu i grida do stanu sprzed akcji, tak aby na ekranie nic się nie zmieniło.
Czyli muszę ustawić w datasecie kursor w pozycji sprzed akcji oraz zadbać aby pozycjonowanie wierszy w gridzie nie uległo zmianie, a w szczególności
pozycja wiersza kursora. Na przykładowym zrzucie ekranu jest to trzeci wiersz od dołu.

W tym celu na podstawie znalezionego w necie przykładu zrobiłem sobie procedurę:

Kopiuj
type
  THDataset = class(TDataset) // a nice "hack" so we can access protected members
  end;

procedure datasetUserAction(Sender: TObject; aDataset: TDataset; aAction: Tnotifyevent);
var
  tb: TBookmark;
  gridRow: integer;
  recNo: integer;
begin
  // zapamiętanie stanu datsetu
  aDataset.DisableControls;
  tb := aDataset.GetBookmark;
  recNo := aDataset.recNo;
  gridRow := THDataset(aDataset).ActiveRecord;
  try
    // wykonanie akcji na datasetcie
    aAction(Sender);
  finally
    // przywrócenie datasetu do stanu sprzed akcji
    THDataset(aDataset).InternalGotoBookmark(tb);
    if THDataset(aDataset).ActiveRecord <> gridRow then
      aDataset.MoveBy(gridRow - THDataset(aDataset).ActiveRecord);
    aDataset.Resync([rmExact]);
    aDataset.freebookmark(tb);
    aDataset.EnableControls;
  end;
  if aDataset.recNo <> recNo then
    raise Exception.Create('Zmiana bieżącego rekoru w datasetcie');
end;

Procedura prawidłowo działa mi od lat.
Ale ...
Wszytsko jest ok jeśli aDataset jest typu Tclientdataset.
Problem pojawia się dla typu TFDMemTable. Kursor skacze po datasecie w dziwny sposób a grid nie trzyma na ekranie stałej pozycji bieżącego wiersza.

Sam sposób działania procedury, a dokładniej metod .InternalGotoBookmark oraz .Resync, nie jest dla mnie jasny, ale dla TclinetDataset było ok, więc nie zastanawiałem się nad tym.
Wydaje mi się że kluczowe są różnice w działaniu metody Tdataset.InternalGotoBookmark(tb) oraz w obsłudze wewnętrznego bufora datasetu zawierającego wiersze widoczne w oknie grida.
Co ciekawe, procedura w ogóle nic nie wie o gridzie. Podejrzewam, że interakcja z gridem odbywa się przez wewnętrzny bufor dataseta, którego jednym z elementów jest property activerecord

Czy ktoś podpowie jakiś sposób modyfikacji procedury dla typu TFDMemtable albo podsunie jakiś inny sposób rozwiązania problemu ?

pict2.jpg

Marius.Maximus
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 2202
0

@grzegorz_so: dlaczego nie używasz GetBookmark / BookmarkValid / GotoBookmark ?

Kopiuj
var
  Bookmark: TBookmark; // Zmienna do przechowywania pozycji rekordu
begin
  try

    // Pobierz znacznik bieżącego rekordu
    Bookmark := DataSet.GetBookmark;
    try
      // Przykład przetwarzania danych w pętli
      DataSet.DisableControls; // Wyłączenie odświeżania kontrolek
      try
        // <<<<<<<<<<<<<<<<< TUTAJ
      finally
        DataSet.EnableControls; // Przywrócenie odświeżania kontrolek
      end;

      // Powrót do oryginalnego rekordu
      if DataSet.BookmarkValid(Bookmark) then
        DataSet.GotoBookmark(Bookmark)
      else
        ShowMessage('Bookmark is invalid.');

    finally
      // Zwolnij zasoby
      DataSet.FreeBookmark(Bookmark);
    end;

  except
    on E: Exception do
      ShowMessage('An error occurred: ' + E.Message);
  end;
end;
GS
  • Rejestracja: dni
  • Ostatnio: dni
0
Marius.Maximus napisał(a):

@grzegorz_so: dlaczego nie używasz GetBookmark / BookmarkValid / GotoBookmark ?

Samo użycie GotoBookmark nie gwarantuje zachowania na ekranie pozycji bieżącego wiersza. Treść grida jest scrollowana, a bieżący rekort jest pozycjonowany na środkowym wierszu grida.
W poście podałem uproszczoną wersje procedury, ograniczając się do najistotniejszego elemntu jakim jest pozycjonowanie wierszy w gridze,
W pełnej wersji zapamiętuje i odtwarza filtry i indeksy oraz waliduje zakładkę przy pomocy metody BookmarkValid

woolfik
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1611
0

Ja tam zawsze do tego używałem query.locate po kluczu głównym i to samo Ci grida ustawia. Nie wiem do końca czy to zadziała na TFDMemtable ale na zwykłych query chodzi bez problemu

GS
  • Rejestracja: dni
  • Ostatnio: dni
0
woolfik napisał(a):

Ja tam zawsze do tego używałem query.locate po kluczu głównym i to samo Ci grida ustawia. Nie wiem do końca czy to zadziała na TFDMemtable ale na zwykłych query chodzi bez problemu

Wiem że ustawia. Podobnie jak GotoBookmark, FindKey, albo ustawienie property Recno. Tyle że bieżący rekord jest pozycjonowany na środkowej pozycji w oknie grida, a ja potrzebuję aby był np. na trzeciej pozycji od dołu

Marius.Maximus
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 2202
0

@grzegorz_so: chyba w zlym miejscu szukasz ! Bo to co oczekujesz nie jest po stronie dataset tylko po stronie GRID-a
locate GotoBookmark ustawiaja pewnie prawidlowo aktywny wiersz, musisz jednak ustawic paski przewijania i wiersz jezeli oczekujesz zeby pozycja w grid sie nie zmienila

woolfik
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 1611
0

Ok to już mniej więcej kumam o co Ci chodzi 😄

Niestety to nie będzie takie proste bo dany wiersz może być np pierwszy w ramach danego order by lub ostatni. No ale załóżmy, że nie jest i faktycznie wypozycjonowało Ci go na środku. W takiej sytuacji będziesz musiał pokombinować z https://docwiki.embarcadero.com/Libraries/Sydney/en/Vcl.DBGrids.TCustomDBGrid.SelectedRows oraz https://docwiki.embarcadero.com/Libraries/Athens/en/Vcl.DBGrids.TCustomDBGrid.SelectedIndex ale obawiam się, że bez dynamicznego wyliczania ile rekordów przy danej wysokości grid wyświetla nie będziesz w stanie odpowiednio przescrollować samego grid'a aby twój interesujący Cię rekord był 3 od góry.

Ja w takiej sytuacji robię to nieco inaczej (od strony GUI) czyli zostawiam locate i grida w trybie selectedrow natomiast nad gridem zostawiam panel z komponentami edycyjnymi tak aby decyzja i wyświetlanie aktualnego rekordu były u góry, a grid jest tylko do swobodnego "skakania" po rekordach bez możliwości edycji itd.

GS
  • Rejestracja: dni
  • Ostatnio: dni
0
woolfik napisał(a):

Ok to już mniej więcej kumam o co Ci chodzi 😄

Dokładnie chodzi mi o to że mam dataset podpięty do grida.
Działający w tle wątek w zależności od potrzeb modyfikuje treść odpowiednich wierszy w datasetcie. Modyfikacje mogą dotyczyć dowolnych wierszy, zarówno tych widocznych w gridzie jak i poza oknem grida. Chciał bym aby grid pozostał statyczny, czyli bez scrollowania i zmiany bieżącego wiersza. Jedyne co może się zmienić to treść komórek grida na ekranie. I coś takiego od dawna mi działa na datasetscie typu Tclientdataset, a kod zamieściłem na początku tego wątku.
Co ciekawe, ten fragment kodu nic nie wie ani o gridzie ani o GUI. Wystarcza dostęp do paru własności samego datasetu.
Dla prawidłowej obsługi GUI kod musi być opakowany w Sychronize , ale nie o tym jest ten wątek.

Ale dla TFDMemTable to rozwiązanie nie działa prawidłowo.

Marius.Maximus
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 2202
0

VCL jest niczym małżeństwo z kobieta, bierzesz ją z dobrodziejstwem inwentarza 😄
W tym co chesz zrobić widac delikatne ograniczenie Delphi (nie mozesz zmodyfikowac niekatywnego wiersza)
Jak bym stanał przed takim problemem i by mi zależało to zajrzał bym na poczatek do źródeł komponentów i zobaczył dlaczego działło do tej pory
jak rozłączasz Dbgrid od datasetu a potem go podlaczasz to na jakiejs podstawie ustawia sie aktywny wiersz na trzecim od dolu, coś musi przechoywać pozycje i nie jets to dataset

Z alternatywncyh pomyslow to zrezygnowac z TDbGrid i zrobic zwykla siatkę albo VirtualTreeVIew

GS
  • Rejestracja: dni
  • Ostatnio: dni
0

@Marius.Maximus

W tym co chesz zrobić widac delikatne ograniczenie Delphi (nie mozesz zmodyfikowac niekatywnego wiersza)

Dzięki za nakierowanie :)
Klonowanie kursora .. !!
Pierwszy testy są obiecujące

GS
  • Rejestracja: dni
  • Ostatnio: dni
0

Rozwiązanie z klonowaniem kursora działa i jest bardzo proste w implementacji .
@Marius.Maximus dzięki za inspirację :)

Kopiuj
procedure datasetUserAction(aDataset: TFDmemtable; aAction: TDatasetNotifyEvent);
var
  tmpTable: TFDmemtable;
begin
  tmpTable := TFDmemtable.create(nil);
  try

    // klonowanie kursora
    tmpTable.CloneCursor(aDataset, true, false);

    // wykonanie akcji na sklonowanym kursorze 
    aAction(tmpTable);

  finally
    tmpTable.Free;
  end;
end;

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.