DBGrid - wiersze fsBold, kolumny kolorowe.

0

Mam potrzebę aby DBGrid wyświetlał mi "nie przeczytane" wiersze literami fsBold a oprócz tego wybrane komórki w kolorach. "Nie przeczytane" oznacza że użytkownik nie edytował jeszcze tego rekordu, inna procedura. Fakt przeczytania (edycji rekordu) zapisuję lokalnie w pliku *.ini.
Napisałem taki kod:

procedure TForm19.dbgZleceniaDrawColumnCell(Sender: TObject; const Rect: TRect;
  DataCol: Integer; Column: TColumn; State: TGridDrawState);
......
if ((ibqZlecenia.FieldByName('termin_potw').AsDateTime < Date()) and (Column.FieldName = 'TERMIN_POTW') and (ibqZlecenia.FieldByName('status').AsInteger = 1)) then 
           begin
           dbgZlecenia.Canvas.Font.Color := clWebTomato;
           dbgZlecenia.Canvas.Font.Style := [fsBold];
           end;
if  {(Column.Field.FieldName = 'ID')} (ibqZlecenia.FieldByName('id').AsInteger>0) then 
          begin
          IDb:=ibqZlecenia.FieldByName('id').AsString);     //odczatane ID z bazy
          R1b:=ibqZlecenia.FieldByName('read_1').AsString;   //odczatane Read_1 z bazy
          T3I:= OdczytINIString('tech3_zlec.ini','Zlecenia',IDb);  // odczytuję z pliku *.ini czy rekord był już czytany czy nie ( jeśli tak to wartość IDb>1, jeśli nie to IDb=0). Uprzednio odczytuję z bazy pole 'Read_1', a poniżej go porównuję aby ustawić fsBold
          end;
if (R1b<>T3I) then dbgZlecenia.Canvas.Font.Style:= [fsBold];

dbgZlecenia.DefaultDrawColumnCell(Rect, DataCol, Column, State);

Kod oznaczony { } to jest moje drugie podejście do tego zagadnienia (wtedy go odznaczam, a wyłączam warunek następujący zaraz po nim). Różnica polega na tym, że pierwszy kod działa, lecz klikanie myszą na komórki kolumny ID powoduje dziwne zachowanie DBGrida, tzn. cały wiersz nie zawsze wyświetla się jako fsBold lecz jakoś dziwnie jakby z opóźnieniem, tzn. ustawia Bold dla następnym klikniętym wierszu zamiast na wierszu właśnie kliknietej komórki. Natomiast klikanie po wszystkich pozostałych kolumnach (oprócz ID) działa prawidłowo tak jak chcę.

Jeśli natomiast odblokuję kod zaznaczony { } to mam wszystko dokładnie tak jak chcę, ale DBGrid działa wyraźnie wolniej, widać, że komputer ma dużo więcej do liczenia niż w pierwszym przypadku. Można powiedzieć, że go nieźle "przymula".

Nie mam pomysłu jak to poprawić. Czy ktoś pomoże?

Jarek

0

A możesz zweryfikować czas poszczególnych operacji aby zdiagnozować przyczynę opóźnienia między pierwszym, a drugim przypadkiem? Ciężko coś powiedzieć bez konkretnego zestawu danych ale czysto teorytycznie warunek:

Column.Field.FieldName = 'ID'

powoduje, że odczyt z INI jest dla każdego rekordu w gridzie bez wyjątku natomiast ten warunek ibqZlecenia.FieldByName('id').AsInteger>0

 może powodować odczyt tylko dla niektórych rekordów. Oczywiście jeśli ID jest dla każdego rekordu to nie ma wyjścia i trzeba analizować czas np za pomocą OutputDebugString z czasem poszczególnych wywołań.
0

Czasu za bardzo nie porównam, ale postaram się opisać.
ad.1 Przewijając kółkiem myszy okno DBGrida, wszystko następuje natychmiast, jak pusty grid, każdy przeskok kółka powoduje przeskok DBGrida do następnej linii. Jeśli od szybko pokręcisz kółkiem to wiersze przewijają się natychmiast. Ale jest jeśli przewijam po kolumnie ID to mam ten dziwny efekt, jakby spóźnionego Bold-a o 1 przeskok. Przykład: mam wyświetlonego DBGrida, wiersz nr 5 jest BOLD, pozostałe nie. Przewijam z góry na dół od wiersza 1, gdy wchodzę na wiersz nr 5 to Bold znika, przechodzę na wiersz 6, Bold na 5 się pojawia, ale pojawia się też na 6, a nie ma być. Przechodzę na 7, Bold pojawia się na 5 i znika na 6. Jeśli jest więcej wierszy z Boldem, to jest spore zamieszanie.
ad.2 Nie ma tego problemu z Boldem, ale za to jest wszystko spowolnione. Przewijając myszą szybko z góry na dół np. o 8 wierszy, jest opóźnienie. Mysz się już zatrzyma, a kursor w DGBridzie dobiegnie dopiero po 1 sek. Każdy zauważy że coś jest nie tak.

Wnioskuję, że DBGrid ma więcej liczenia w 2-gim przypadku.
Dążyłem do czegoś takiego ,żeby Grid odczytywał plik INI tylko 1 raz na każdy wiersz, a wynika mi z tego, że odczytuje go dodatkowo dla każdej komórki danego wiersza. Dodam, że mam z 60 kolumn w tabeli. To by tłumaczyło wzrost czasu na liczenie.

Nie mam pomysłu jak osiągnąć efekt o który mi chodzi.

Nasuwa mi się pytanie, jak DBGrid wyświetla pola na ekran, wierszami czy kolumnami ?

0

OK, wybrnąłem z sytuacji w końcu. Doszedłem, że procedura odczytu (jak się okazało wielokrotnego, dla każdej komórki grid-a) z pliku INI powoduje opóźnienie. Zastąpiłem ją odczytem ze StringList-a, który tworzę jednorazowo poprzez wczytanie z pliku INI tuż przed malowaniem dbGrid-a. Odczyt ze StringList-a jest szybki i nie wprowadza żadnych zauważalnych opóźnień.
Dzięki temu mam dbGrid-a w którym "nie czytane" wiersze wyświetlają się Boldem. Aplikacja działa w sieci LAN więc na każdym komputerze użytkownik ma inaczej oznaczone "nie czytane" wiersze. Jak w programie pocztowym.

Pozdrawiam

0

@jarek265 ze swojego doświadczenia radziłbym Ci na kompletną zmianę tego rozwiązania. Jest trochę średnie rozwiązanie. A co będzie jak user przesiądzie się na inny komputer chwilowo i na jednym komputerze odznaczy jako przeczytaną, na drugim będzie musiał też odznaczać? Przy kilku wiadomościach może to i jest ok, ale co jak tych wiadomości będzie 100? Średnio by mi się podobało klikanie i odznaczanie tego drugi raz. Znacznie lepiej takie dane trzymać w głównej bazie danych. Wtedy mógłbyś sobie złączyć w zapytaniu SQL tabele i mieć od razu pole "Przeczytane" razem z danymi i tego problemu byś nie miał. Wszystko by działało szybko i bezboleśnie.

A jeśli faktycznie by tak miało być, że ten sam użytkownik ma inne ustawienia na innym komputerze to też żaden problem. Wystarczyło by zapamiętać na user+pc ustawienia i już.

0

Rozwiązań jest zwykle kilka. Myślałem o tym również, ale chciałem to zrobić wydaje mi się prościej. W twoim przypadku musiał bym trochę rozbudować bazę, zapytania i odpowiednie warunki pilnujące aby reszta programu poprawnie działała. Tworzę, nie program pocztowy, lecz program do wyświetlania zleceń produkcyjnych. W bazie jest ok 3000 zleceń, rocznie przybywa ok.1000. Jest ok 10 użytkowników sieci i wszyscy pracują na tych samych zleceniach. Nie ma, jak w programie pocztowym, prywatnych danych. Jeśli którykolwiek z nich wprowadzi jakąś zmianę w zleceniu to pozostałym ma się to pokazywać jako "nie czytane". Zrobiłem to w dość prosty sposób: w głównej bazie w polu o nazwie Read_1 po dokonaniu edycji przez kogokolwiek zapisywana jest losowa liczba Integer. Na komputerach natomiast porównywana ona jest z liczbą zapamiętaną w pliku INI dla danego zlecenia. Jeśli Read_1 jest różne od Liczby_INI to Text ma być Bold. Po odczytaniu rekordu zapisujemy tą liczbę do pliku INI przez co następnym razem rekord nie jest wyświetlany jako Bold (chyba że znowu ktoś wprowadzi zmianę).
Poza tym przenosiny na nowy lub kolegi komputer występują bardzo rzadko. Zawsze można przenieść również plik INI. Poza tym zlecenia produkcyjne idą dość szybko, wyświetlanych jest około 20 ostatnich, reszta to historia. Jeśli kogoś nie będzie przez tydzień to praktycznie i tak większość zleceń pokaże mu się jako "nie czytane". Więc nawet utrata pliku INI nie jest groźna.

0

Tak, to prawda. Moja propozycja spowoduje rozrost bazy, ale przy takich ilościach danych jak piszesz to nie będzie problemów. Ale patrząc na opis, to ja bym to zrobił jeszcze inaczej. Każdy rekord może mieć datę ostatniej edycji z dokładnością do sekundy. Dodatkowo jedna tabela z czasem ostatniego odczytu przez użytkownika danego rekordu. Całość powinna być relatywnie prosta i mniej magiczna niż losowanie intów którego jakoś nie czuję.

Ale moje rozwiązanie to w sumie jedna z kilku możliwości. Jednak dla mnie wygodniej by było trzymać wszystkie dane w tej samej bazie danych. Bo czytanie pliku ini jak sam zobaczyłeś jest bardzo wolne.

0

Tak, na różne sposoby można to osiągnąć. Mnie dodatkowo zależy aby wymiana informacji z bazą była możliwie najmniejsza, gdyż korzystam czasami z mojego programu będąc na wyjazdach przez internet przez komórkę. Gdy zasięg jest tylko GPRS to wtedy wyraźnie widać wydłużony czas zapytania do bazy. Kiedyś optymalizowałem pod tym kątem program.

Twoja wersja dodaje 1 kolumnę w tabeli głównej oraz 1 tabelę dodatkowo. Moja wersja dodaje 1 kolumnę oraz 1 mały plik, który ma w tej chwili 11kb. Czas zapytania w obu przypadkach będzie taki sam. Ale zwróć uwagę na stopień skomplikowania. Mój jest prostszy. Zdarza mi się czasami po roku lub dwóch wrócić do jakiejś tam procedury, więc czas na rozszyfrowanie na nowo co poprzednio miałem na myśli jest też ważny.

Ale prawda jest jeszcze taka, że nie chce mi się przerabiać tego co już jest :)

0
jarek265 napisał(a):

Mnie dodatkowo zależy aby wymiana informacji z bazą była możliwie najmniejsza, gdyż korzystam czasami z mojego programu będąc na wyjazdach przez internet przez komórkę. Gdy zasięg jest tylko GPRS to wtedy wyraźnie widać wydłużony czas zapytania do bazy. Kiedyś optymalizowałem pod tym kątem program.
W przypadku GPRS'u sprawa jest nieco inna. Duże znaczenie ma nie tylko wielkość danych, ale i również ping. Przy pingu jaki oferuje GPRS (500ms i więcej) trudno o jakąkolwiek komfortową pracę w takim systemie. A jedna kolumna więcej to przecież dość mało zwiększy rozmiar przesyłanych danych. A wystawianie bazy przez internet jest delikatnie mówiąc średnim rozwiązaniem.

jarek265 napisał(a):

Twoja wersja dodaje 1 kolumnę w tabeli głównej oraz 1 tabelę dodatkowo. Moja wersja dodaje 1 kolumnę oraz 1 mały plik, który ma w tej chwili 11kb. Czas zapytania w obu przypadkach będzie taki sam. Ale zwróć uwagę na stopień skomplikowania. Mój jest prostszy. Zdarza mi się czasami po roku lub dwóch wrócić do jakiejś tam procedury, więc czas na rozszyfrowanie na nowo co poprzednio miałem na myśli jest też ważny.
No nie bardzo. Jeśli nie chcesz dawać skomplikowanych zapytań wystarczy skorelowane podzapytanie które zwróci Ci datę ostatniego odczytu:

zlecenia:
id                int
.
.
.

ostatnia_edycja   date

zlecenia_odczyty:
id                int
uzytkownik        int
zlecenie          int
ostatni_odczyt    date

Zaś same zapytanie można zrobić następująco:

select
  A.*
  (select B.ostatni_odczyt from zlecenia_odczyty B where B.zlecenie = A.id and B.uzytkownik = 100) as ostatni_odczyt
from
  zlecenia A
order by
  A.id

Ewentualnie jakiegoś joina można dać. Nic tu zamotanego nie ma, a będzie czytelniejsze i od razu masz wszystkie dane bez kombinowania z dodatkowymi odczytami. Co ważniejsze takie rozwiązanie będzie o wiele szybsze niż każdorazowe przeszukiwanie StringList.

jarek265 napisał(a):

Ale prawda jest jeszcze taka, że nie chce mi się przerabiać tego co już jest :)
W takim razie to co innego ;)

0

OK, zrobię tak. Przekonał mnie kolega.

Mam jeszcze drugie pytanie dot. bazy FireBird.
Mam w bazie procedurę updatującą dane, która zwraca mi jakieś tam wyniki. Dla tej procedury mam zdefiniowany wyzwalacz AU. Pytanie brzmi: W którym momencie baza wyśle mi wyniki? Zaraz po zakończeniu działania procedury czy dopiero po zakończeniu działania wyzwalacza AU?

Do czego mi potrzebna ta informacja? Zamierzam w wyzwalaczu "porządkować/przeliczać/obrabiać" dane, ale jest to operacja czasochłonna, trwa ok 6-12 sekund. Zatem zależy mi, aby procedura w której robię update danego rekordu dawała mi najpierw wynik, a potem w tle niech sobie baza pracuje. Czy w ten sposób osiągnę cel?

0

Nie jestem pewien jak to dokładnie działa w FireBird ale w Oracle, PostgreSQL i MSSQL to co dzieje się w triggerze wykonuje się zaraz po update czyli:

procedure nazwa
begin
  ... jakiś kod
  update // zaraz  za nim trigger AfterUpdate
  ... dalszy kod procedury
  return // i dopiero w tym momencie masz wynik procedury
end;

Tak jak wspomniałem pewności nie mam ale dla przykładu w Oracle jeśli na triggerze AU robisz np select z tej samej tabeli to ci on zmutuje i nic się nie wykona (trzeba użyć autonomicznej transakcji) natomiast w MSSQL takich problemów nie zaobserwowałem.

0

Tego się obawiałem, że zanim procedura zwróci mi wynik to trigger musi zrobić swoje. Rozumiem, że wywołanie drugiej procedury w trakcie pierwszej również da taki sam efekt, czyli po zakończeniu 2-giej zakończy się dopiero 1-sza. Czy jest jakaś możliwość aby po zakończeniu 1-szej procedury i zwórceinu wyników wywołać jakąś inną procedurę (ale nie za pomocą wywołania jej z programu lecz automatycznie) ?

0
jarek265 napisał(a):

Tego się obawiałem, że zanim procedura zwróci mi wynik to trigger musi zrobić swoje. Rozumiem, że wywołanie drugiej procedury w trakcie pierwszej również da taki sam efekt, czyli po zakończeniu 2-giej zakończy się dopiero 1-sza. Czy jest jakaś możliwość aby po zakończeniu 1-szej procedury i zwórceinu wyników wywołać jakąś inną procedurę (ale nie za pomocą wywołania jej z programu lecz automatycznie) ?
Z tego co kojarzę to takie coś niestety nie jest możliwe. Chyba żeby procedurę wywoływać z programu. Ale tego chcesz uniknąć.

Ale zastanawia mnie co innego. Dlaczego Twoja procedura tak długo się wykonuje? Aż tak dużo danych? Może wystarczy dołożyć jakiś indeks i będzie o wiele szybciej. Warto przeanalizować to. Ile masz rekordów? Bo 12s to naprawdę bardzo dużo. Ja takie wyniki mam jak zadaję zapytanie które ma ponad 500 000 odczytów. Więc albo masz dużo danych, albo wyzwalacz jest nieefektywny.

0

Oto procedura, która wykonuje się 5-6s a jest to tylko zakres 2 lat:

CREATE PROCEDURE CALENDAR_HRS_AVG 
as
declare variable moja_data date;
begin
  for select data1 from calendar where data1 between '01-1-2014' and '31-12-2015' into :moja_data    /*pętla analizuje wszystkie dni z okresu 2 lat ( około 720 rekordów)*/
  do begin   
     update calendar set
     sum_hours = (select sum(hours) from forma 
                 where (status>0) and (suma_of is not null) and (data_rozpoczecia is not null) 
                 and (data_rozpoczecia <= :moja_data) and (termin_potw >= :moja_data))
     where data1 = :moja_data;
  end
end

CREATE TABLE CALENDAR (
    ID             INTEGER NOT NULL,
    DATA1          DATE NOT NULL,
    HOURS           INTEGER,
    ......
);
CREATE TABLE FORMA (
    ID                  INTEGER,
    DATA_ROZPOCZECIA    DATE,
    TERMIN_POTW         DATE,
    STATUS              INTEGER DEFAULT -1,
    SUMA_OF             INTEGER,    
    ....
);

i gdyby nie sum(hours) to wykonała by się w ułamek sekundy. Sumowanie znacznie wydłuża czas wykonania, sprawdziłem. Sum(hours) w mojej bazie sumuje około 20-30 rekordów z tabeli Forma

0

Cóż, jakoś wydaje mi się to skandalicznie wolno. Po pierwsze może warto założyć indeks na pole data_rozpoczecia z tabeli forma A ile rekordów ma tabela Forma?

Po drugie 720 małych zapytań wykona się o wiele wolniej niż 1 duże. Spróbowałbym napisać ten update tak aby nie było pętli. Coś na wzór:

UPDATE
  calendar a
SET
  a.sum_hours = (SELECT SUM(hours) FROM forma b
                 WHERE (status>0) AND (suma_of IS NOT NULL) AND (data_rozpoczecia IS NOT NULL) 
                 AND (data_rozpoczecia <= a.data1) AND (termin_potw >= a.data1))
WHERE
   a.data1 BETWEEN '01-1-2014' AND '31-12-2015'

Pisane z palca bez sprawdzenia, ale ja osobiście tak bym to próbował zrobić. Ale musisz sprawdzić czy moje zapytanie działa poprawnie. Bo ja nie testowałem. Warto mieć realne dane.

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