1. Wstawianie kolumny/wiersza
2. Kasowanie kolumny/wiersza
3. Wykrywanie zdarzenia zmiany rozmiaru kolumny/wiersza
Założenia
Dostępne w sieci przykłady nie spełniły moich oczekiwań - skupiają się głównie na pisaniu nowego komponentu udostępniającego jedne z powyższych funkcjonalności. Ja nie lubię specjalnie zaśmiecać palety komponentów, stąd też staram się ograniczać i używać tego co mam.
Poniższe rozwiązania nie wymagają instalowania / tworzenia komponentu a jedynie kilku zmian w kodzie modułu z formą.
Przygotowania
Zanim przystąpimy do omawiania powyższych podpunktów, musimy zastosować technikę zwaną "podrabianiem klasy" (ang. Class Spoofing) - bardzo rzadko stosowana, acz bardzo użyteczna tachnika pozwalająca nam uzyskać dostęp do pól i metod z sekcji protected. Standardowa notacja kodu używana w Delphi nie przewiduje specjalnych oznaczeń, jednak ja konsekwentnie używam przedrostka CS. Należy zadeklarować następujący typ który posłuży nam w dalszej części artykułu:
type // class spoofing
TCS_StringGrid = class(TStringGrid);
1. Wstawianie kolumny/wiersza
W razie potrzeby należy samodzielnie dodać kod zabezpieczający przed wyjściem z zakresu parametrów AColumn i ARow.
procedure TfMain.InsertColumn(AStringGrid: TStringGrid; AColumn: integer);
Begin
with TCS_StringGrid(AStringGrid) do
begin
ColCount := ColCount + 1;
MoveColumn(ColCount - 1, AColumn);
Cols[AColumn].Clear;
end;
end;
procedure TfMain.InsertRow(AStringGrid: TStringGrid; ARow: integer);
begin
with TCS_StringGrid(AStringGrid) do
begin
RowCount := RowCount + 1;
MoveRow(RowCount - 1, ARow);
Rows[ARow].Clear;
end;
end;
2. Kasowanie kolumny/wiersza
procedure TfMain.DeleteColumn(AStringGrid: TStringGrid; AColumn: integer);
begin
with TCS_StringGrid(AStringGrid) do
begin
if ColCount - FixedCols = 1 then
Exit;
DeleteColumn(AColumn);
end;
end;
procedure TfMain.DeleteRow(AStringGrid: TStringGrid; ARow: integer);
begin
with TCS_StringGrid(AStringGrid) do
begin
if RowCount - FixedRows = 1 then
Exit;
DeleteRow(ARow);
end;
end;
Powyższe metody mogą zostać zaimplementowane w klasie formy, lub można w ramach treningu stworzyć klasę pomocniczą dla TStringGrida.
WAŻNE
Jeśli implementowaliśmy zdarzenie przesuwania kolumn/wierszy OnRowMove/OnColumnMoved, to powyższe implementacje mogą być dość problematyczne, ponieważ zostanie wywołany event przesunięcia, pomimo, że takowe "logicznie" nie nastąpiło. Należy się przed tym zabezpieczyć poprzez napisanie metod zawieszających i odwieszających te zdarzenia, oraz wrappery na funkcje dodające/kasujące wiersze/kolumny.
procedure TfMain.LockColRowMoveEvent;
begin
StringGrid1.OnRowMoved := nil;
StringGrid1.OnColumnMoved := nil;
end;
procedure TfMain.UnlockColRowMoveEvent;
begin
StringGrid1.OnRowMoved := StringGridRowMoved;
StringGrid1.OnColumnMoved := StringGridColumnMoved;
end;
a tu mamy wrappery na metody dodawania/kasowania, z których należy korzystać zamiast z InsertColumn, InsertRow, DeleteRow i DeleteColumn w przypadku wystąpienia zdarzeń przesuwania kolumn i wierszy dla danego StringGrida:
procedure TfMain.AddCol(ACol: integer);
begin
LockColRowMoveEvent;
InsertColumn(StringGrid1, ACol);
UnlockColRowMoveEvent;
end;
procedure TfMain.AddRow(ARow: integer);
begin
LockColRowMoveEvent;
InsertRow(StringGrid1, ARow);
UnlockColRowMoveEvent;
end;
procedure TfMain.DelCol(ACol: integer);
begin
LockColRowMoveEvent;
DeleteColumn(StringGrid1, ACol);
UnlockColRowMoveEvent;
end;
procedure TfMain.DelRow(ARow: integer);
begin
LockColRowMoveEvent;
DeleteRow(StringGrid1, ARow);
UnlockColRowMoveEvent;
end;
3. Wykrywanie zdarzenia zmiany rozmiaru kolumny/wiersza
Dla czego? Najodpowiedniejszym wyjaśnieniem jest synchronizacja z innym StringGridem np. znajdującym się nad/pod lub obok.
W przykładzie zostanie pokazana technika wykrywania zdarzenia dla kolumn, w razie potrzeby można dopisać analogiczne metody dla wierszy.
Pierwszym krokiem na drodze do wykrycia zdarzenia jest stworzenie klasy pomocniczej, której metody są używane w dalszym kodzie:
type
TStringGridHelper = class helper for TStringGrid
public
function RightCol: integer;
function BottomRow: integer;
end;
{...}
function TStringGridHelper.BottomRow: integer;
begin
Result := TopRow + VisibleRowCount - 1
end;
function TStringGridHelper.RightCol: integer;
begin
Result := LeftCol + VisibleColCount - 1;
end;
Następnym krokiem jest zadeklarowanie nowej klasy
TStringGridColResize = class(TStringGrid)
protected
procedure ColWidthsChanged; override;
end;
której implementacja wygląda następująco (kiedy chcemy synchronizować z innym StringGridem):
procedure TStringGridColResize.ColWidthsChanged;
procedure AlignColSize(ASource, ADest: TStringGrid);
var
i, RightCol: LongInt;
begin
if ASource.ColCount - ASource.FixedCols <> ADest.ColCount -
ADest.FixedCols then
Exit;
RightCol := ASource.RightCol + 1;
if RightCol = ASource.ColCount then
Dec(RightCol);
for i := ASource.LeftCol to RightCol do
ADest.ColWidths[i + (ADest.FixedCols - ASource.FixedCols)] :=
ASource.ColWidths[i];
end;
begin
if Self = fMain.StringGrid1 then
AlignColSize(fMain.StringGrid1, fMain.StringGrid2)
else if Self = fMain.StringGrid2 then
AlignColSize(fMain.StringGrid2, fMain.StringGrid1);
inherited;
end;
Jak widać nie wykrywamy która konkretnie kolumna podlega zmianie rozmiaru, a jedynie wykrywamy zdarzenie bez danych precyzujących, jak co i o ile.
Można oczywiście wykryć której kolumny rozmiar został zmieniony, poprzez przechowywanie rozmiarów kolumn w osobnej tablicy i porównanie ich z nowymi rozmiarami.
Technika jest wydajna nawet w przypadku dużej liczby kolumn, gdyż sprawdzamy rozmiary kolumn jedynie tych widocznych.
Teraz w zdarzeniu stworzenia formy należy podmienić tablicę VMT dla StringGridów, które mają być podatne na wykrycie zdarzenia zmiany rozmiarów kolumn.
procedure TfMain.FormCreate(Sender: TObject);
begin
// patch dla VMT - potrzebne zdarzenie ColResize
PPointer(StringGrid1)^ := TStringGridColResize;
PPointer(StringGrid2)^ := TStringGridColResize;
end;
Podsumowanie
Zaprezentowane techniki mogą się wydać dość uciążliwe jednak do większości zastosowań są bardzo użyteczne i nie sprawią problemów i co ważniejsze nie trzeba tworzyć/instalować dodatkowych komponentów.
W przypadku intensywnej eksploracji warto się pokusić o stworzenie własnego komponentu :).
[ŚCIĄGNIJ PRZYKŁADOWY PROGRAM]
Pozdrawiam Maciej "HNB" Izak