The finished result:
Okay, so to start off I design the UI with a single TPanel (called in this case pnlProductMaster) that is in the right location (on another TPanel which is the grid parent). Onto the pnlProductMaster panel I put the other elements that I want. This could be a set of more panels to make cells, or anything you want. In the example case, I have an image, a title label, some explanation text and a button. Make the template panel invisible so that it won’t appear to the user ever.
Now, the implementation to make this template into a grid of this is perhaps currently a little crude. I have considered making things more generic, but then I’d lose control over the fine details, and I like to put a lot of polish into my UI. Thus instead of making a generic framework that allows me to access Row.Label.Caption, I rather have a Row.TitleLabel.Caption. First, I define a class to hold the information about my panel components:
TProductPanel = class private m_xProductPanel: TPanel; m_xTitleLabel : TLabel; m_xHeaderLabel : TLabel; m_xLogo : TImage; m_xSelect : TButton;
m_bSelected : Boolean; m_szProductJSON : String; m_xOptionsList : TStringList; procedure SetSelected(bValue : Boolean);
public constructor Create(xMainPanel : TPanel); destructor Destroy; override;
procedure SetOptions(xOptionsList : TStringList); property MainPanel: TPanel read m_xProductPanel write m_xProductPanel; property TitleLabel : TLabel read m_xTitleLabel write m_xTitleLabel; property HeaderLabel : TLabel read m_xHeaderLabel write m_xHeaderLabel; property Logo : TImage read m_xLogo write m_xLogo; property Select : TButton read m_xSelect write m_xSelect;
property ProductJSON : String read m_szProductJSON write m_szProductJSON; property OptionsList : TStringList read m_xOptionsList;
property Selected : Boolean read m_bSelected write SetSelected;
Yes, sorry, I use a personal version of “hungarian notation” and “m_” means a member variable, and “x” means an object. You can make it to fit your style.
Then I have a procedure to create a panel to represent an item in the data. The parameters are I hope obvious, one specifying the index, and the other passing in data to be stored against the row. This helps later when the user selects it, as the data is ready and waiting.
procedure TfrmShop.CreatePanelFor(nProductLoop : Integer; xOptionsList : TStringList); var xPanel : TProductPanel; begin xPanel := TProductPanel.Create(DuplicatePanel(pnlProductMaster, pnlProductList)); xPanel.MainPanel.Tag := nProductLoop; xPanel.TitleLabel := DuplicateLabel(lblProductName, xPanel.MainPanel); xPanel.HeaderLabel := DuplicateLabel(lblProductHeader, xPanel.MainPanel); xPanel.Logo := DuplicateImage(imgProductLogo, xPanel.MainPanel); xPanel.Select := DuplicateButton(btnProductSelect, xPanel.MainPanel); xPanel.Select.Tag := nProductLoop; xPanel.HeaderLabel.Tag := nProductLoop; xPanel.Logo.Tag := nProductLoop; xPanel.TitleLabel.Tag := nProductLoop; xPanel.MainPanel.Top := (nProductLoop * (xPanel.MainPanel.Height - 1)) - 1; xPanel.SetOptions(xOptionsList); m_xProductDisplay.Add(xPanel); end;
The panel object is stored in a TObjectList called m_xProductDisplay. The DuplicatePanel, DuplicateLabel etc are pretty similar, and are generic functions to replicate a specific panel. In this way, a copy of the template panel is built up.
function DuplicatePanel(xReferencePanel : TPanel; xParentPanel : TPanel) : TPanel; begin Result := TPanel.Create(xParentPanel); // xReferencePanel); Result.Name := 'AutoPanel' + IntToStr(g_nAppendName); Inc(g_nAppendName); CopyPanelDetails(Result, xReferencePanel); end; procedure CopyPanelDetails(xTargetPanel : TPanel; xReferencePanel : TPanel); begin xTargetPanel.ShowBorder := xReferencePanel.ShowBorder; xTargetPanel.ShowCaption := xReferencePanel.ShowCaption; xTargetPanel.ShowCloseButton := xReferencePanel.ShowCloseButton; xTargetPanel.ShowShadow := xReferencePanel.ShowShadow; xTargetPanel.Hint := xReferencePanel.Hint; xTargetPanel.Width := xReferencePanel.Width; xTargetPanel.Height := xReferencePanel.Height; xTargetPanel.Color := xReferencePanel.Color; xTargetPanel.Left := xReferencePanel.Left; xTargetPanel.Top := xReferencePanel.Top; xTargetPanel.OnClick := xReferencePanel.OnClick; xTargetPanel.OnMouseEnter := xReferencePanel.OnMouseEnter; xTargetPanel.OnMouseLeave := xReferencePanel.OnMouseLeave; xTargetPanel.OnMouseMove := xReferencePanel.OnMouseMove; xTargetPanel.OnMouseDown := xReferencePanel.OnMouseDown; xTargetPanel.OnMouseUp := xReferencePanel.OnMouseUp; end;
The important part here is that the important properties of the panel, or other component, are all copied. For the panel, I don’t copy the captions as they are likely to not be duplicated. For labels, I have a parameter to determine if the label caption should be duplicated too. I have at points sat trying to work out why my new code isn’t working, and of course I’d not copied the appropriate property. The mouse events in particular which are used to highlight the panels as the user mouses over. Note that the tag of the items was set in the creation of the panel, so all items on the panel know which row they are representing, and all of them have the MouseEnter and MouseLeave set.
procedure TfrmShop.pnlProductMasterMouseEnter(Sender: TObject); begin TProductPanel(m_xProductDisplay[TComponent(Sender).Tag]).Selected := True; end; procedure TProductPanel.SetSelected(bValue : Boolean); begin m_bSelected := bValue; if m_bSelected then begin MainPanel.Color := clMintCream; end else begin MainPanel.Color := clWhite; end; end;
In some applications, I’ve not only changed the background colour, but also shown and enabled things like buttons or links that allow activity on the grid item. Not having it visible for all items stops the UI being overwhelmed with a repeat that isn’t useful unless you are over the item.
So, now you just have to fill the grid with data.
procedure TfrmShop.GetProductInfoComplete(nResult : Integer; szProductJSON: String); var nLoop : Integer; xList : TStringList; begin try lblPreparing.Visible := false; m_xProductDisplay.Clear; nLoop := 0; xList := TStringList.Create; try json_ReadStringListPaired(szProductJSON, 'p' + IntToStr(nLoop), xList); while xList.Count > 0 do begin CreatePanelFor(nLoop, xList); Inc(nLoop); json_ReadStringListPaired(szProductJSON, 'p' + IntToStr(nLoop), xList); end; pnlSignIn.Top := ((nLoop) * (pnlProductMaster.Height - 1)) - 1; pnlSignIn.Visible := not frmWelcome.LoginActive; finally xList.Free; end; except on errInfo : EError do begin Report('GetProductInfoComplete Error ' + errInfo.Message); end; end; end;
I hope that this gives some explanation of how it is possible to make nice looking and interactive grids in WebBuilder.
Please note that I have no link to Elevate Software other than being a happy user of their products. This is provided to the world unsolicited, and as-is. If you wish to discuss WebBuilder, I hang out on the relevant newsgroups.