Een Eigen Gridje Is zo Eenvoudig nog Niet…

Een Eigen Gridje Is zo Eenvoudig nog Niet…

Een klant van mij vroeg me wat schermen van hun programma op te poetsen door de grid-schermen te voorzien van de mogelijkheid om op kolommen te sorteren. Geen rare gedachte, aangezien dat in de wereld die Windows heet nou eenmaal heel gebruikelijk is. Denk aan Outlook, Excel, etc. Maar omdat de schermen met gridjes opgebouwd waren uit ListViews was deze ombouw nogal een tijdrovende klus. Dus werd besloten om de ListViews, waar veel programmeerwerk in zat, te vervangen door een commerciële grid-component.

De DevExpress-grid

Een paar jaar geleden was ik al eens in de weer geweest met de grid-component van DevExpress, en na het downloaden en bekijken van de demo werd besloten dat deze het zou worden. Ik wist nog van de oude versie (3) dat de grid omgeven werd door een werkelijk enorme hoeveelheid properties om de werking en het uiterlijk aan te passen, dus werd meteen besloten om er een eigen (sub)class van te maken, zodat niet elke ontwikkelaar in het team zich hoefde te bemoeien met de eigenschappen en de uiterlijkheden van de grid.

Na installatie nestelde de (enorme) hoeveelheid componenten waaruit de grid bestaat zich prima in de IDE van Rad Studio 2007. Even een nieuwe Win32-app gemaakt, en een grid op het scherm gekwakt, en het volgende staat voor je neus:

Grappig. Het ziet ernaar uit dat de grid uit een samenstelling van componenten bestaat. Even met F12 aan de andere kant kijken bevestigt dit:

type
  TForm1 = class(TForm)
    cxGrid1DBTableView1: TcxGridDBTableView;
    cxGrid1Level1: TcxGridLevel;
    cxGrid1: TcxGrid;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

Om te bepalen wat we uiteindelijk met de grid willen bereiken zullen we eerst deze versie maar eens tot leven brengen en kijken wat-ie kan. Er staat vast wel ergens een NorthWind.mdb op de schijf, dus maar even een TAdoConnection , TAdoQuery, en een TDatasource op het form gezet, ConnectionString gemaakt, en de query gevuld met ‘select * from customers’ . En alles met elkaar verbonden, en op connected en active gezet… Da’s gek. Normaal zie je met een TDbGrid dan meteen je data in beeld. Het is ook even zoeken naar het Datasource-property van het cxGrid. Het blijkt een property te zijn van cxGridDbTableView1.Datacontroller. Achteraf ook logisch, aangezien de grid ook ‘unbound’ moet kunnen functioneren. Maar nog steeds geen kolommen in beeld. Hoewel de scroll-bar van de grid me laat zien dat er wel beweging in is gekomen:

Blijkbaar moet altijd de inhoud van de grid met de hand worden bepaald. Dat is overigens niet zo moeilijk; een dubbelklik op de grid of een klik op ‘Customize’ opent een editor waarin met een knop ‘Retrieve fields’ alle velden uit de dataset kunnen worden opgepikt. Tijd om het oude TDbGrid van het scherm te halen om het cxGrid de ruimte te geven. En even runnen om te zien wat die allemaal standaard in huis heeft, en wat we daarvan willen gaan gebruiken.

Groeperen

Het meest opvallende aan de grid is een paneeltje boven de grid waarin staat ‘Drag a column here to group by that column’. Proberen dus. Het veld (kolom) ‘Country’ leent zich hier wel voor, want daar kun je wel een paar gebruikers blij mee maken, denk ik.

Blijkbaar weten ze bij DevExpress ook wel dat hun grid een scala aan mogelijkheden heeft

Als je, zoals je in Outlook gewend bent, op een kolomkop klikt, zie je meteen dat er op de betreffende kolom gesorteerd wordt. Da’s mooi. Net zoals het omgekeerd sorteren, en middels de shift-toets het sorteren op meerdere kolommen. De opgelegde sortering is ook keurig zichtbaar met pijltjes in de kolomkoppen. Alleen jammer dat bij sorteren op meerdere kolommen niet duidelijk is welke kolom nou als eerste (of tweede) is gebruikt.

Als je de muis over de kolomkoppen beweegt, zie je een kleine button verschijnen (anders dan die van het sorteren). Erop klikken geeft een lijst met waardes die zich in de onderliggende kolom bevinden, en waarop je dus kunt filteren. Ook erg handig:

De ‘custom’ optie geeft een dialoog waarin een samengesteld filter kan worden gebouwd. Ook erg leuk, maar misschien wat too much voor de gemiddelde gebruiker.

Later maar eens kijken of dit eraf gehaald kan worden. Net zoals een paneeltje dat zichtbaar wordt onder de grid, op het moment dat een filter actief is. Erg leuk, maar zonde van de schermruimte. Wel jammer dat, zonder dit paneeltje, niet echt duidelijk is dat er een filter op een bepaalde kolom actief is.

Al met al ziet na een paar klikken het scherm er al indrukwekkend uit:

Nu met een echte database …

Toch eens even kijken. We bouwen allemaal programma’s voor klanten die iets meer moeten kunnen dan het weergeven van een testbestandje of een cd-verzameling. Laten we dus hetzelfde traject maar eens doen met een volwassen database, met een kleine 2 miljoen records in één van de tabellen. Zo gezegd, zo gedaan, even wat IBX componenten op het scherm, en de Interbase-database eronder gezet. En dan eens kijken of het nog steeds lekker loopt:

Aaaarrrrgggghhhh!. En het ging zo lekker. Tijd om de help er maar eens bij te pakken. En die help is omvangrijk. Blijkbaar weten ze bij DevExpress ook wel dat hun grid een scala aan mogelijkheden heeft, want het help-bestand heeft een omvang die evenredig is aan het aantal mogelijkheden van de grid. Net zoals het aantal samples dat wordt meegeleverd. En daarmee houdt het niet op, hun website heeft een support-centre waar het uitstekend zoeken is naar eerdere vragen of problemen die andere gebruikers daar eerder hebben neergelegd. Gelukkig maar, want het blijkt dat het niet allemaal even simpel is wat de makers hebben bedacht.

Even een korte uitleg over de opbouw van de grid: Zoals uit het eerste probeersel bleek, bestaat de grid uit (tenminste) 3 componenten: een Grid, een Level, en een View. De grid is niet meer dan een omhulsel met wat (weinig) algemene zaken. Het Level is bedoeld om binnen een Grid een ander Grid te kunnen weergeven. Een soort parent-child relatie. En de View is er om de weergave binnen dat Level te bepalen. Er kan worden gekoppeld aan data (bound of unbound), en ingesteld worden hoe de records weergegeven worden, maar behalve het platte ‘grid’–model zijn er nog andere (Cards) weergavemogelijkheden.

Even testen: en ja, dit werkt … alleen gebeurt er niks

Na enig zoeken in de help en voorbeelden blijkt dat de grid standaard probeert om alle records naar binnen te slobberen, om zodoende de mogelijkheid tot sorteren, groeperen en filteren te bieden. Sorteren heeft namelijk geen zin als je maar een fractie van de onderliggende data in huis hebt. Net als filteren. Het klinkt erg aanlokkelijk om het cxGrid al deze zaken automatisch te laten verzorgen, maar als programmeur wil je toch proberen te voorkomen dat bij wijze van spreken de complete harde schijf van een server over het netwerk wordt getrokken.

Het geheim zit in de property GridMode van de DatamodeController, die zelf weer een property is van de DataController. Als je deze property wijzigt, ziet de code van je form er als volgt uit:

object cxGrid1DBTableView1: TcxGridDBTableView
NavigatorButtons.ConfirmDelete = False
DataController.DataModeController.GridMode = True
DataController.DataSource = DataSource1

Dus zo moeilijk is dat niet aan te passen. Tijd om het spul weer eens te runnen. En inderdaad staat het scherm er in tienden van seconden, zoals we van een grid (en Delphi) gewend zijn. En het klikken op een kolomkop kan nog steeds … alleen gebeurt er niks. Idem voor het filteren. En het groeperen op een kolom-kop heeft tot resultaat dat de hele tabel weer opgehaald wordt, en dus eindigt met een out-of-memory boodschap. Dat laatste willen we zeker niet laten gebeuren, en het sorteren en filteren moet gewoon lekker op de database server gebeuren. Misschien biedt subclassen een oplossing.

Subclassing

We beginnen met een gewone subclass van de grid:

TSdnGrid = class(TcxGrid)
private
  { Private declarations }
protected
  { Protected declarations }
public
  { Public declarations }
  constructor Create(AOwner: TComponent); override;
  destructor  Destroy; override;
published
  { Published stuff }
end;

procedure Register;

implementation

{ Register }

procedure Register;
begin
  RegisterComponents('SdnStuff',[TSdnGrid]);
end;

constructor TSdnGrid.create(AOwner: TComponent);
begin
  inherited create(AOwner);
end;

destructor TSdnGrid.Destroy;
begin
  inherited destroy;
end;

end.

Volgens Bartjens zou alles prima kloppen, dus slaan we de unit op onder de naam SdnGridUnit.pas. Hierna maken we (File-New-Package – Delphi for Win32) een package dat we meteen opslaan met de naam SdnPack. Met de rechtermuis op SdnPack.bpl (in de project manager) kiezen we ‘add’ en voegen zo de unit toe met de class van hierboven. Daarna via rechtsklik kiezen voor ‘build’, gevold door ‘install’ en we zien:

Helaas gemakkelijk komen we hier niet mee weg. Als we ons SDN-grid op een form zetten, staan alle properties nog keurig zoals bij het standaard cxGrid. En na wat aanmodderen bij de Create van de class TSdnGrid blijkt dat het hier niet mogelijk is om te gaan sleutelen in de eigenschappen van het Level of de View die bij de grid hoort. En wel om de heel eenvoudige reden dat deze beide steunpilaren bij de Create van de grid nog niet bestaan. De conclusie is dan ook dat we meteen het Level en de View ook tot een eigen subclass maken. Na wat aanmodderen kom ik erachter dat deze subclasses niet meteen als standaard Level en View door het SDN-grid worden geaccepteerd. Je moet je eigen Level en View aan de grid kenbaar maken door ze als default Level en View te benoemen. De hele class ziet er dan zo uit:

TSdnLevel = class(TcxGridLevel)
TSdnRootLevel = class(TcxGridRootLevel)
TSdnView = class(TcxGridDBTableView)
TSdnTableViewInfo = class(TcxGridTableViewInfo)
TSdnRowsViewInfo = class(TcxGridRowsViewInfo)
TSdnGrid = class(TcxGrid)

Al met al is het een hele lap. En het enige dat ik hiermee heb bereikt is dat ik een ‘volledig’ grid heb (met View en Level) die klaar is om van onze eigen standaard kenmerken en gedrag te worden voorzien.

Het wordt dan tijd om te zoeken naar de plek om de grid (of eigenlijk, de view) te dwingen standaard in GridMode=true te gaan. We maken daarom een override van de create van ons eigen TSdnView:

constructor TSdnView.create(AOwner: TComponent);
begin
  inherited create(AOwner);
  if not assigned(Datacontroller) then exit;
  if not assigned(Datacontroller.DataModeController)
    then exit;
  Datacontroller.DataModeController.GridMode := true;
end;

Let wel op dat het op deze manier een property is die altijd(!) wordt gezet, ook als je hem in design time zelf op false op zet. Maar in dit geval is het wel wat ik wil, dus kan ik ermee leven. De grid draait nu in ieder geval prima op een grote dataset. Het begin is er.

Kolommen maken

Nu maar verder gaan met het wensenlijstje waaraan mijn grid moet voldoen. Om te beginnen met het maken van de kolommen op het moment dat er runtime geen kolommen te bekennen zijn. En natuurlijk het opslaan (en weer ophalen) van de kolommen op het moment dat iemand de kolommen wijzigt.

Op zoek dus naar zoiets als een ‘OnDatasourceActiveChanged’ event. Het blijkt niet eenvoudig. Het komt erop neer dat we nog meer van de TcxBlaBla classes moeten subclassen om zodoende een ActiveChanged event tot onze beschikking te hebben. Die vinden we terug in de DataProvider, en die is weer een onderdeel van de DbDataController. Dus we gooien nog twee subclasses in de strijd:

TSdnDataController = class(TcxGridDbDataController)
protected
  function GetDataProviderClass:
    TcxCustomDataProviderClass; override;
public
  { Public declarations }
  constructor Create(Aowner: TComponent); override;
end;

TSdnDBDataProvider = class(TcxDbDataProvider)
protected
  procedure ActiveChanged(AActive: Boolean); override;
end;

constructor TSdnDataController.create(Owner:TComponent);
begin
  inherited create(Owner);
end;

function TSdnDataController.GetDataProviderClass: TcxCustomDataProviderClass;
begin
  Result := TSdnDBDataProvider;
end;

procedure  TSdnDBDataProvider.
  ActiveChanged(AActive: Boolean);
begin
  inherited ActiveChanged(AActive);
end;

De initialisation wordt uitgebreid met:

RegisterClass(TSdnDataController);

Nou, we hebben nog vrijwel geen functionaliteit toegevoegd, maar wel 8 classes de wereld in geholpen. Mocht iemand opmerken dat het geheel wel redelijk ingewikkeld in elkaar zit, dan ben ik het daar grondig mee eens! Maar goed, we waren gebleven bij het maken van kolommen, en het opslaan en ophalen van instellingen. Nu we een eigen subclass hebben met die TSdnDbDataProvider kunnen we die een OnActiveChanged event geven waaraan we, vanuit onze eigen View-class, een procedure kunnen plakken. Dit doen we vanuit de View, aangezien op dit niveau alles bekend is qua kolommen, en omdat de View zelf wat procedures in zich heeft om de kolommen op te slaan dan wel op te halen.

De TSdnDbDataProvider wordt uitgebreid met:

TSdnDBDataProvider = class(TcxDbDataProvider)
private
  { Private declarations }
  FOnActiveChanged: TNotifyEvent;
protected
  { protected declarations }
  procedure ActiveChanged(AActive: Boolean); override;
public
  { Public declarations }
  property OnActiveChanged: TNotifyEvent read
    FOnActiveChanged write FOnActiveChanged;
end;

De ActiveChanged override die we hebben gemaakt wordt nu:

procedure TSdnDBDataProvider.
  ActiveChanged(AActive: Boolean);
begin
  inherited ActiveChanged(AActive);
  if assigned(OnActiveChanged) then
    OnActiveChanged(self);
end;

Tenslotte maken we in onze TSdnView class een eigen procedure ActiveChanged, en in de Create van de TSdnView plakken we het zaakje aan elkaar:

constructor TSdnView.create(AOwner: TComponent);
begin
  inherited create(AOwner);
  if not assigned(Datacontroller) then exit;
  if not assigned(Datacontroller.DataModeController)
    then exit;
  Datacontroller.DataModeController.GridMode := true;

  if assigned(DataController.Provider) then
    TSdnDbDataProvider(DataController.Provider).
  OnActiveChanged := ActiveChanged;
end;

procedure TSdnView.ActiveChanged(sender: TObject);
var
  sStorageName: string;
begin
  // staat van data wijzigt: kolommen ophalen/opslaan
  sStorageName := IncludeTrailingPathDelimiter(
    'Sdn\Grids\'+self.name);
  if DataController.Provider.DataSet.Active then
  begin
    if ColumnCount < 1 then
      DataController.CreateAllItems(false);
    Self.RestoreFromRegistry(sStorageName, True, True,
      [gsoUseFilter,gsoUseSummary],'');
  end
  else begin
    if ColumnCount > 0 then
      self.StoreToRegistry(sStorageName,true,
        [gsoUseFilter,gsoUseSummary],'');
  end;
end;

Zo. Het eerste stukje toegevoegde waarde zit erop. Het enige manco dat hier nog in zit is dat het vermoedelijk zo is dat, bij het sluiten van het form, de dataset of datasource eerder wordt afgekoppeld dan dat de dataset gesloten wordt. Dus om het netjes te doen moet ook nog zoiets in de Destroy van de TSdnView worden afgevangen. Dit beschouw ik maar even als behorend tot de afsluitende taakjes.

Als we het TSdnGrid weer op een Form zetten met de eerder genoemde grote Interbase tabel, en het zaakje runnen, dan werkte het allemaal prima. Bij het openen en sluiten van de dataset (ibQuery) worden keurig de instellingen opgehaald en weggeschreven.

Als programmeur wil je toch proberen te voorkomen dat de complete harde schijf van een server over het netwerk wordt getrokken

Sorteren van kolommen

Dus kunnen we door met het volgende punt: het sorteren als op een kolomkop wordt geklikt. Het ligt voor de hand dat dit in de SQL gebeurt. De enige voorwaarde is dan dat er altijd een TQuery (of afgeleide) wordt gebruikt bij het opvragen van data. In mijn geval is dat ook zo, dus vooruit maar met de geit. Even wat spitten in de vele voorbeeldjes leert dat er een OnSortingChanged event bestaat binnen de Datacontroller van de View. Aangezien het meeste werk tot nu toe wel binnen de View wordt gedaan, maken we daarom maar een procedure SdnSortingChanged, die we in de Create aan het OnSortingChanged event van de Datacontroller plakken. Verder moet er nog een optie worden ‘aangezet’ : OptionsCustomize.ColumnSorting := True. Dit geeft de volgende toevoeging aan de code:

procedure  TSdnView.SdnSortingChanged(Sender: TObject);

  procedure ApplySortToQuery(AQuery: TIbQuery;
            ASortArray: array of string);
  var
    I: Integer;
    ASortString, ASqlString: string;
  begin
    ASortString := '';
    for I := 0 to High(ASortArray) do
      ASortString := ASortString + ASortArray[I];
    ASQLString := Uppercase(AQuery.Sql.text);
    Delete(ASortString, Length(ASortString)-1, 2);
    if pos('ORDER BY', ASQLString )>0 then
      ASQLString := copy(ASQLString,1,pos('ORDER BY',
        ASQLString )-1);
    try
      AQuery.DisableControls;
      AQuery.Close;
      if ASortString <> '' then
        ASortString := 'order by ' + ASortString;
      AQuery.Sql.Text := ASQLString + ASortString;
    finally
      AQuery.Open;
      AQuery.EnableControls;
    end;
  end;

var
  I: Integer;
  AOrder, AFieldName: string;
  ASortArray: array of string;
begin
  try
    Screen.Cursor := crHourGlass;

    BeginUpdate;
    SetLength(ASortArray, SortedItemCount);
    for I := 0 to SortedItemCount - 1 do
    begin
      AFieldName := TcxGridDBColumn(
        SortedItems[I]).DataBinding.FieldName;
      if SortedItems[I].SortOrder = soAscending then
        AOrder := ' ASC, '
      else
        AOrder := ' DESC, ';
      ASortArray[SortedItems[I].SortIndex] :=
    AFieldName + AOrder;
    end;
    if Length(ASortArray) = 0 then
      SetLength(ASortArray, 1);
    ApplySortToQuery(  TIbQuery(DataController.DataSet),
      ASortArray);
  finally
    EndUpdate;
    Screen.Cursor := crDefault;
  end;
end;

Je ziet dat het eerste deel van de code uit de SortedItems list de SQL genereert die in de query kan worden gebruikt, en dat deze in ApplySortToQuery aan de SQL van de query wordt vastgeplakt. Let op dat er gemakshalve even niet wordt gelet op een sortering die in design-time aan de query wordt opgelegd, maar het is niet al te moeilijk om deze uitbreiding te verzinnen. Bij het testen (runnen) van deze uitbreiding blijkt dat er nog een cadeautje bij zit: op het moment dat de instellingen (in de SdnActiveChanged) worden opgehaald, wordt blijkbaar meteen de sortering gecheckt, en indien er kolommen worden opgeslagen waarop wordt gesorteerd, wordt deze sortering keurig netjes weer toegepast op het moment dat de grid weer actief wordt. Lekker makkelijk.

Filteren

Tot slot staat dan het filteren op mijn wensenlijstje. Een heel stuk terug hadden we al gezien dat bij een klik op de filter-button, de grid een lijstje produceert met de (unieke) waardes die in de onderliggende kolom kunnen voorkomen. Alleen in de GridMode die wij gebruiken werkt dit dus niet automatisch. Dat willen we ook niet, aangezien dit zou betekenen dat wederom de hele tabel in het geheugen moet worden gelezen. Zoals het nu is, gebeurt er helemaal niets, en wordt bij het klikken op de filter-button een bijna leeg lijstje getoond:

Het plan is om de tweede, derde, en vierde optie helemaal niet te tonen (ik wil mijn gebruikers niet gaan uitleggen wat blanks zijn). Verder moeten we dus m.b.v. SQL de unieke waardes gaan zoeken. Terug naar de help en de voorbeelde, en het blijkt gelukkig niet al te lastig. Binnen de View moet je de ‘OptionsCustomize.ColumnFiltering := True’ aanzetten. Dat kan dus prima in de Create erbij worden gezet. En er bestaat in de View.DataController.Filter property een ‘OnGetValueList’ event, dat afgevuurd wordt op het moment dat op de filter-button wordt geklikt. Dit event kan er dan als volgt uitzien:

procedure TSdnView.SdnFilterGetValueList(
  Sender: TcxFilterCriteria; AItemIndex: Integer;
  AValueList: TcxDataFilterValueList);

  function GetTableNameFromSql(AQuery: TIbQuery):
    string;
  var
    I: Integer;
    ASqlString: string;
  begin
    result := '';
    ASqlString := uppercase(AQuery.Sql.Text);
    if pos('FROM' ,ASqlString)>0 then
    begin
      Result :=trim(copy(ASqlString,
        pos('FROM',ASqlString)+4, length(ASqlString)));
      i := 1;
      while ((copy(Result,i,1)<>' ') ) do  inc(i);
      Result := trim(copy(Result,1,i-1));
    end;
  end;

  var
  AColumn: TcxGridDBColumn;
  AValue: Variant;
  ASqlText: String;
begin
  AColumn := Columns[AItemIndex];
  try
    Screen.Cursor := crHourGlass;
    if not assigned(FQryTemp) then
    begin
      FQryTemp := TIbQuery.create(nil);
      FQryTemp.Database := TIbQuery(
        self.DataController.DataSource.DataSet).
        Database;
      FQryTemp.Transaction  := TIbQuery(
        self.DataController.DataSource.DataSet).
          Transaction;
    end;
    FQryTemp.SQL.Clear;
    ASqlText := 'Select DISTINCT ' +
      AColumn.DataBinding.FieldName + ' From ' +
        GetTableNameFromSql(TIbQuery(
          DataController.DataSet));
    FQryTemp.SQL.Add( ASqlText );
    FQryTemp.Open;
    FQryTemp.First;
    while not FQryTemp.Eof do
    begin
      AValue := FQryTemp.Fields[0].Value;
      if VarIsNull(AValue) then Exit;
      AValueList.Add(fviValue, FQryTemp.Fields[0].Value,
        AValue, False);
      FQryTemp.Next;
    end;
    FQryTemp.Close;
  finally
    Screen.Cursor := crDefault;
  end;
end;

Kort gezegd haalt dit stukje code alle waardes uit de tabel van het veld op wiens kolom wordt geklikt. Deze waardes worden dan in de ValueList gezet die wordt getoond op het moment dat op de filterbutton wordt geklikt. Even testen: en ja, dit werkt. Alleen gebeurt er niks met de onderliggende data op het moment dat we een waarde selecteren. Wat er wel gebeurt, is dat er het eerder genoemde paneel te voorschijn komt waarin de filter-expressie wordt getoond. En dat wilden we niet. Dit pakken we aan door in de Create van onze View het property Filterbox.visible op fvNever te zetten. Maar daarmee werkt het filter nog steeds niet. Na even zoeken blijkt dat de code binnen cxGrid gebruik probeert te maken van de normale ‘filter’-mogelijkheid die binnen een TDataset beschikbaar is. En dat willen we nou juist niet, aangezien dan alle records uit de onderliggende tabel weer naar binnen gehaald moeten worden. Dit gaan we voorkomen door (wederom in de Create van onze View) de  property Datacontroller.Filter.AutoDatasetFilter op False te zetten. Daarnaast gebruiken we het Datacontroller.Filter.Onchanged event om het filter toe te passen op de onderliggende dataset:

procedure TSdnView.SdnFilterChanged(Sender: TObject);
var
  s: string;
begin
  DataController.Filter.OnChanged := nil;
TSdnDbDataProvider(DataController.Provider).
  OnActiveChanged
  := Nil;

  TIbQuery(Datacontroller.DataSource.DataSet).close;
  s := uppercase(TIbQuery(
   Datacontroller.DataSource.DataSet).Sql.Text);
  if pos('WHERE',s) > 0 then
    s := Copy(s,1, pos('WHERE',s)-1);
  if Datacontroller.Filter.FilterText<>'' then
    s := s + ' where '+Datacontroller.Filter.FilterText;

  TIBQuery(Datacontroller.DataSource.DataSet).Sql.Text
   :=s;
  TIbQuery(Datacontroller.DataSource.DataSet).Open;

  DataController.Filter.OnChanged := SdnFilterChanged;
  TSdnDbDataProvider(DataController.Provider).
   OnActiveChanged := SdnActiveChanged;
end;

Binnen de functie wordt het OnActiveChanged even afgekoppeld (en aan het eind weer aangekoppeld) om te voorkomen dat er onnodig instellingen worden opgeslagen.

De Create van onze eigen View ziet er uiteindelijk zo uit:

constructor TSdnView.create(AOwner: TComponent);
begin
  inherited create(AOwner);
  if not assigned(Datacontroller) then exit;
  if not assigned(Datacontroller.DataModeController)
    then  exit;
  Datacontroller.DataModeController.GridMode := true;

  // Voor het opslaan en ophalen van de instellingen
  if assigned(DataController.Provider) then
    TSdnDbDataProvider(DataController.Provider).
      OnActiveChanged := SdnActiveChanged;

  //voor middels SQL sorteren bij klikken op kolomkoppen
  DataController.OnSortingChanged := SdnSortingChanged;
  OptionsCustomize.ColumnSorting := True;

  // Voor het ophalen van de filter-waardes
  OptionsCustomize.ColumnFiltering := True;
  DataController.Filter.OnGetValueList :=
    SdnFilterGetValueList;

  // filter-paneel niet zichtbaar
  Filterbox.visible := fvNever ;
  // geen dataset-filtering
  Datacontroller.Filter.AutoDatasetFilter := false;
  Datacontroller.Filter.OnChanged := SdnFilterChanged;

end;

Als we dit testen blijkt dat we met het opslaan en ophalen van de instellingen nog een cadeautje krijgen: ook de filter-instellingen worden door de StoreToRegistry procedure keurig meegenomen, en andersom ook weer keurig toegepast. Lekker handig, en lekker consequent. Het enige nadeel dat ik hier zie is dat de filter-buttons alleen zichtbaar zijn op het moment dat de muis over de kolomkoppen heen gaat. En op het moment dat een filter (een extra stukje SQL) wordt opgeslagen, is het wel zo prettig voor de gebruiker dat het feit dat er een filter op een kolom actief is duidelijk zichtbaar is. Dus een laatste keer nog maar de helpfile in en de support afgestruind.

Tijdens dit zoeken krijgen we meteen mee hoe we de filter-items die we niet willen hebben (de zgn. Blanks en Non-Blanks) eraf moeten halen:

for I := AValueList.Count-1 downto 0 do
begin
  if AValueList.items[i].Kind in
   [fviCustom, fviBlanks, fviNonBlanks] then
    AValueList.Delete(i);
end;

Dit stukje wordt dus meteen maar even meegenomen aan het begin van onze SdnFilterGetValueList. Opgeruimd staat netjes.

Het andere bleek echter iets lastiger dan het zetten van een property. Het kwam erop neer dat we nog eens drie classes die in het cxGrid gebruikt worden, in een eigen klasse moesten vertalen, zodat we de GetAlwaysVisible van de ColumnHeaderFilterButtonViewInfo konden manipuleren.

TSdnColumnHeaderFilterButtonViewInfo =
  class(TcxGridColumnHeaderFilterButtonViewInfo)
protected
  function GetAlwaysVisible: Boolean; override;
end;

TSdnColumnHeaderViewInfo = class(
  TcxGridColumnHeaderViewInfo)
protected
  procedure GetAreaViewInfoClasses(AProc:
    TcxGridClassEnumeratorProc); override;
end;

TSdnHeaderViewInfo = class(TcxGridHeaderViewInfo)
  function GetItemClass:
  TcxGridColumnHeaderViewInfoClass; override;
end;

function TSdnColumnHeaderFilterButtonViewInfo.
  GetAlwaysVisible: Boolean;
begin
  Result := inherited GetAlwaysVisible or
    (GridView.DataController.Filter.FindItemByItemLink(
       Column) <> nil);
end;

procedure TSdnColumnHeaderViewInfo.
  GetAreaViewInfoClasses(
  AProc: TcxGridClassEnumeratorProc);
begin
  if CanHorzSize then AProc(
    TcxGridColumnHeaderHorzSizingEdgeViewInfo);
  if CanFilter then AProc(
    TSdnColumnHeaderFilterButtonViewInfo);
  if CanSort then AProc(
    TcxGridColumnHeaderSortingMarkViewInfo);
  if HasGlyph then AProc(
    TcxGridColumnHeaderGlyphViewInfo);
end;

function TSdnHeaderViewInfo.GetItemClass:
 TcxGridColumnHeaderViewInfoClass;
begin
Result := TSdnColumnHeaderViewInfo;
end;

Conclusie

Het is zo een lang verhaal geworden. Maar het levert uiteindelijk een grid op die onder alle omstandigheden overeind blijft. Daaruit blijkt dat, wat je er ook aan wilt veranderen, alles uiteindelijk mogelijk is. Al die Win32-apps, waar de gebruikers uiteindelijk nog jaren mee te maken gaan krijgen, kunnen hiermee een zo groot mogelijk gebruiksgemak bieden. En snelheid.

Gemakshalve heb ik het onderdeel ‘groeperen’ maar even buiten beschouwing gelaten. Net zoals het vertalen van teksten die je met gebruik van deze grid tegenkomt. Gelukkig bleek onderweg dat dit erg makkelijk te doen was. En neem maar van mij aan dat, als je toevallig een data-dictionary gebruikt, er een wereld van mogelijkheden voor je open gaat.

De sources die bij dit artikel horen kun je downloaden via Kalken_SdnGrid_SRC.zip.

Commentaar van anderen:
ChristianLouboutin op 17-8-2010 om 5:13
Christian Louboutin Shoes, Christian Louboutin, Christian Louboutin Shoes, Wedding Shoes, Wedding Shoes, Louboutin Shoes, Christian Louboutin Discount copies of the mirror, so the angel of the practical activities of customers who activities. Christian Louboutin Evening, Manolo Blahnik Shoes, Christian, Louboutin, Christian Louboutin Sale, Louboutin Sale, Cheap Christian Louboutin Can you from your shoes, you give yourself into chargeless travel and bear your lifetime. Christian Louboutin Boots, Christian Louboutin Pumps, Christian Louboutin Sandals, Christian Louboutin Flats, Christian Louboutin Wedges, Christian Louboutin Sandals Do you agree to accept and hear the cases angled heel shoes forward or abstract, tear, buttons, and destruction, and the ability to reduce greenhouse gas emissions, it is exactly like daydreaming and shoes. Yves Saint Laurent Shoes, Christian Louboutin Boots, Manolo Blahnik Shoes, Yves Saint Laurent Boots, Miu Miu Shoes, Christian Dior Shoes this is an extra brand and shoes. However, you will never accept the amorous Louboutin christians through brand. Christian Louboutin Flats, Christian, Herve Leger V Neck Dress, Herve Leger Bandage Dress, Herve Leger Dress, Herve Leger V Neck Dress You can visit in your configuration of the life of their shoes were forgiven a brace, air brief you accept defeat them later anniversary, they are careful bag dust, shoe box that you have to go.
asdasd op 19-8-2010 om 16:01
i would certainly have gone for the hot pink one.. hihih :cloud9: i did not know cooler pad existed. i’ve never seen one Wooden Flash Driver. guess i won’t find that in mauritius though.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
darcy op 1-9-2010 om 7:03
dial case and bracelet The different types of cartier colt watches casual and sports wear to funky fun fashion a lange sohne fake u boat watches is the standout feature of this lovely watch It patek philippe submariner watches aluminum These are durable and rust resistant omega watches automatic chronographs TAG Heuer managed to replica watches you are a watch fan It is a valuable one with a buy watches excellence in advancing technology Tag Heuer luxury watch watches are unique in that almost every aspect of luxury watches The custom double injected polyurethane band has a cartier watches In 1974 at 51 Anne Klein succumbed to cancer gucci excellence and precision is its long time lv watch karat rose gold together with the hand sewn montblanc watches cartier declaration watches their beginnings as jewellers Cartier has parmigiani watches various groups according to their attributes so a lange sohne tool which is always in great demand thanks to montblanc watches replica watch hours of power reserve along with 100 m water replica omega watches watch of this kind also is a stylish addition to swiss watch rise up against the ranks of fierce competition cartier movement Thus the First Class presented bvlgarl watches is highly distinctive both in terms of hublot watches Rousseau launched the Expos Skeleton dial watch movado lithium content alloy containing aluminum copper tag watches Its training features include the recording of breitling watches source for replica watcheshigh quality watches mens watches impressive Ceramic watches are symbol of swiss watch water resistant to only 30 meters These fabulous breitling for bentley flying watches tag heuer commerce while taking further action to enhance.
replica Rolex op 1-9-2010 om 11:22
igner handbags not e jerseY cheap igner handbags not e pittsbuRgh steelers 34 rashard mendenhall white jersey-3341
Geef feedback:

CAPTCHA image
Vul de bovenstaande code hieronder in
Verzend Commentaar