Wednesday, May 20, 2020

Create and Customize Buttons With the DBNavigator

Ok, the DBNavigator does its job of navigating data and managing records. Unfortunately, my customers want more user-friendly experience, like custom button graphics and captions, ... This inquiry came from a Delphi developer searching for a way to enhance the power of the DBNavigator component.   The DBNavigator is a great component—it provides a VCR-like interface for navigating data and managing records in database applications. Record navigation is provided by the First, Next, Prior, and Last buttons. Record management is provided by the Edit, Post, Cancel, Delete, Insert, and Refresh buttons. In one component Delphi provides everything you need, to operate on your data. However, as the author of the e-mail inquiry also stated, the DBNavigator lacks some features like custom glyphs, button captions, and others. A More Powerful DBNavigator Many Delphi components have useful properties and methods that are marked invisible (protected) to a Delphi developer. Hopefully, to access such protected members of a component, a simple technique called the protected hack can be used. First, youll add a caption to every DBNavigator button, then youll add custom graphics, and finally, youll OnMouseUp-enable each button.   From the boring DBNavigator to either of: Standard graphics and custom captionsOnly captionsCustom graphics and custom captions Lets Rock n Roll The DBNavigator has a protected Buttons property. This member is an array of TNavButton, a descendant of TSpeedButton.   Since each button in this protected property inherits from TSpeedButton, if you get our hands on it, youll be able to work with standard TSpeedButton properties like: Caption (a string that identifies the control to the user), Glyph (the bitmap that appears on the button), Layout (determines where the image or text appears on the button)... From the DBCtrls unit (where DBNavigator is defined) you read that the protected Buttons property is declared as: Buttons: array[TNavigateBtn] of TNavButton; Where TNavButton inherits from TSpeedButton and TNavigateBtn is an enumeration, defined as : TNavigateBtn (nbFirst, nbPrior, nbNext, nbLast, nbInsert, nbDelete, nbEdit, nbPost, nbCancel, nbRefresh); Note that TNavigateBtn holds 10 values, each identifying different button on a TDBNavigator object. Now, lets see how to hack a DBNavigator: Enhanced DBNavigator​ First, set up a simple data editing Delphi form by placing at least a DBNavigator, a DBGrid, a DataSoure and a Dataset object of your choice (ADO, BDE, dbExpres, ...). Make sure all components are connected. Second, hack a DBNavigator by defining an inherited dummy class, above the Form declaration, like: type THackDBNavigator class(TDBNavigator); type TForm1 class(TForm) ... Next, to be able to display custom captions and graphics on each DBNavigator button, youll need to set up some glyphs. You can use the TImageList component and assign 10 pictures (.bmp or .ico), each representing an action of a particular button of a DBNavigator. Third, in the OnCreate event for the Form1, add a call like: procedure TForm1.FormCreate(Sender: TObject); SetupHackedNavigator(DBNavigator1, ImageList1);end; Make sure you add the declaration of this procedure in the private part of the form declaration, like: type TForm1 class(TForm) ... privateprocedure SetupHackedNavigator(const Navigator : TDBNavigator; const Glyphs : TImageList); ... Fourth, add the SetupHackedNavigator procedure. The SetupHackedNavigator procedure adds custom graphics to each button and assigns a custom caption to each button. uses Buttons; //!!! dont forgetprocedure TForm1.SetupHackedNavigator (const Navigator : TDBNavigator; const Glyphs : TImageList);const Captions : array[TNavigateBtn] of string (Initial, Previous, Later, Final, Add, Erase, Correct, Send, Withdraw, Revive);(* Captions : array[TNavigateBtn] of string (First, Prior, Next, Last, Insert, Delete, Edit, Post, Cancel, Refresh); in Croatia (localized): Captions : array[TNavigateBtn] of string (Prvi, Prethodni, Slijedeci, Zadnji, Dodaj, Obrisi, Promjeni, Spremi, Odustani, Osvjezi);*)var btn : TNavigateBtn;beginfor btn : Low(TNavigateBtn) to High(TNavigateBtn) dowith THackDBNavigator(Navigator).Buttons[btn] dobegin//from the Captions const array Caption : Captions[btn]; //the number of images in the Glyph property NumGlyphs : 1; // Remove the old glyph. Glyph : nil; // Assign the custom one Glyphs.GetBitmap(Integer(btn),Glyph); // gylph above text Layout : blGlyphTop; // explained later OnMouseUp : HackNavMouseUp; end;end; (*SetupHackedNav igator*) Ok, lets explain. You iterate through all the buttons in the DBNavigator. Recall that each button is accessible from the protected Buttons array property—therefore the need for the THackDBNavigator class. Since the type of the Buttons array is TNavigateBtn, you go from the first (using the  Low  function) button to the last (using the  High  function) one. For each button, you simply remove the old glyph, assign the new one (from the Glyphs parameter), add the caption from the Captions array and mark the layout of the glyph. Note that you can control which buttons are displayed by a DBNavigator (not the hacked one) through its VisibleButtons property. Another property whose default value you may want to change is Hints—use it to supply Help Hints of your choosing for the individual navigator button. You can control the display of the Hints by editing the ShowHints property. Thats it. This is why youve picked Delphi! Gimme More! Why stop here? You know that when you click the nbNext button the datasets current position is advanced to the next record. What if you want to move, lets say, 5 records ahead if the user is holding the CTRL key while pressing the button? How about that?   The standard DBNavigator does not have the OnMouseUp event—the one that carries the Shift parameter of the TShiftState—enabling you to test for the state of the Alt, Ctrl, and Shift keys. The DBNavigator only provides the OnClick event for you to handle.   However, the THackDBNavigator can simply expose the OnMouseUp event and enable you to see the state of the control keys and even the position of the cursor above the particular button when clicked! Ctrl Click : 5 Rows Ahead To expose the OnMouseUp you simply assign your custom event handling procedure to the OnMouseUp event for the button of the hacked DBNavigator. This exactly is already done in the SetupHackedNavigator procedure:OnMouseUp : HackNavMouseUp; Now, the HackNavMouseUp procedure could look like: procedure TForm1.HackNavMouseUp (Sender:TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);const MoveBy : integer 5;beginif NOT (Sender is TNavButton) then Exit; case TNavButton(Sender).Index of nbPrior: if (ssCtrl in Shift) then TDBNavigator(TNavButton(Sender).Parent). DataSource.DataSet.MoveBy(-MoveBy); nbNext: if (ssCtrl in Shift) then TDBNavigator(TNavButton(Sender).Parent). DataSource.DataSet.MoveBy(MoveBy); end; end;(*HackNavMouseUp*) Note that you need to add the signature of the HackNavMouseUp procedure inside the private part of the form declaration (near the declaration of the SetupHackedNavigator procedure): type TForm1 class(TForm) ... privateprocedure SetupHackedNavigator(const Navigator : TDBNavigator; const Glyphs : TImageList); procedure HackNavMouseUp(Sender:TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); ... Ok, lets explain, one more time. The HackNavMouseUp procedure handles the OnMouseUp event for each DBNavigator button. If the user is holding the CTRL key while clicking the nbNext button, the current record for the linked dataset is moved MoveBy (defined as constant with the value of 5) records ahead. What? Overcomplicated? Yep. You do not need to mess with all this if you only need to check the state of the control keys when the button was clicked. Heres how to do the same in the ordinary OnClick event of the ordinary DBNavigator: procedure TForm1.DBNavigator1Click(Sender: TObject; Button: TNavigateBtn); function CtrlDown : Boolean; var State : TKeyboardState; begin GetKeyboardState(State); Result : ((State[vk_Control] And 128) 0); end;const MoveBy : integer 5;begincase Button of nbPrior: if CtrlDown then DBNavigator1.DataSource.DataSet.MoveBy(-MoveBy); nbNext: if CtrlDown then DBNavigator1.DataSource.DataSet.MoveBy(MoveBy); end; //caseend;(*DBNavigator2Click*) Thats All Folks And finally, the project is done.  Or you can keep going.  Heres a scenario/task/idea for you:   Lets say you want only one button to replace the nbFirst, nbPrevious, nbNext, and nbLast buttons. You can use the X, and Y parameters inside the HackNavMouseUp procedure to find the position of the cursor when the button was released. Now, to this one button (to rule them all) you can attach a picture that has 4 areas, each area is suppose to mimic one of the buttons you are replacing ... got the point?

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.