I wish that the TRzDBGrid would let me put a button in some, but not all, column headers. Each button would either be blank or display an arrow to indicate that the grid is sorted on that column. I also wish the header would underline a character that follows an ampersand. Any chance that my wish will come true?

Yes that would be a neat solution. In the meantime you could do what we do in our own applications - that is, when the user clicks on a column heading to sort it, we change the column title and add a '(+)' in front to indicate that it's sorted acending. If they click again, to sort decending, we change the prefix to '(-)'.

We allow sorting of all columns and also support multi-select so the user can sort by, for example, column 'A' within column 'B'. This flexibility is important because the grids columns are user definable (heading and width) from all fields in the table being shown. As well as the 'click' on the column we also have a popup menu (right click) that lists key sort fields and also enables the user to clear the sorting.

If you want to restrict sorting to certain columns why don't you show the possible fields in 'bold' or 'italic'?

Streatley wrote:If you want to restrict sorting to certain columns why don't you show the possible fields in 'bold' or 'italic'?

Because I'd rather not make up user interface conventions that users won't understand without reading the manual. I expect my users to read the manual to understand how to use my apps, but I think I owe it to them to make the user interface logical and clear. Buttons and arrows have reasonably clear meanings. Bold and italic headers do not.

I sometimes ask people what the use of bold means in a math book. I've yet to meet a non-mathematician who knows the right answer. On the other hand, I don't recall any math book ever explicitly explaining what bold means. Sometimes your users will understand your conventions and sometimes they won't.

// The BetterDBGrid has optional buttons in the column titles. To get a button// in a column, put an ampersand in the column Title.Caption property. This also// underlines the letter after the ampersand in the title. Clicking the button// or pressing the accelerator key will sort the data set on the column, if you// add appropriate code to the Sort_Data_Set method. Only one column can be// sorted on at a time.

// If the grid is disabled, the row indicator is hidden. You can save the sort// column, column widths, and column order to a TRzRegIniFile using the SaveGrid// procedure and Restore method. If the dgRowSelect option is selected, then the// cursor keys are adjusted to be more natural. Ctrl+Del no longer is passed to// the grid; you can use the OnKeyDown event to handle it yourself.

//------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.DrawCell.//------------------------------------------------------------------------------

//------------------------------------------------------------------------------// Start of procedures used by Calculate_Button_Rectangle.//------------------------------------------------------------------------------

//------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.DrawButton.//------------------------------------------------------------------------------

// Draw the specified cell. If the cell is a title cell and the column isn't// expandable, then do a better job. If the grid is not enabled, then don't draw// the row indicator. Otherwise, just call the inherited method.

//------------------------------------------------------------------------------// Start of procedures used by SaveGrid.//------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Save.//------------------------------------------------------------------------------

//------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Restore.//------------------------------------------------------------------------------

//------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.KeyDown.//------------------------------------------------------------------------------

//------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.MouseDown.//------------------------------------------------------------------------------

function TBetterDBGrid.AcquireFocus: boolean;

// Acquire the focus, if possible. Return whether the focus was acquired. This// is identical to the private TDBGrid.AcquireFocus method.

begin

result := true;

if FAcquireFocus and self.CanFocus and not ( csDesigning in ComponentState ) then begin self.SetFocus; result := self.Focused or ( InplaceEditor <> nil ) and InplaceEditor.Focused; end;

// If the specified point is in the button in the title of the specified column,// save the field name of the column in the dbgrid object's field. Save the// button rectangle in the dbgrid object's field. Make the button be redrawn in// the down state.

else if Clicked_Button_Down then self.Invalidate_Clicked_Button_Rectangle; end;

end; // TBetterDBGrid.MouseUp.

//------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.CMDialogChar.//------------------------------------------------------------------------------

// This method will be called if the user types an accelerator key. Accelerator// keys are Alt plus a character or, if the control with focus doesn't want// characters, just the character without Alt. Check whether the key is the// accelerator for any of the column titles. If so, sort on the column.

// The TBetterDBGrid has optional buttons in the column titles. To get a button in a column title, put an ampersand in the column// Title.Caption property. This also underlines the letter after the ampersand in the title. Clicking the button or pressing the// accelerator key will sort the data set on the column, assuming you change to the appropriate index in the OnSort event. Only one// column can be sorted on at a time.

// I don't know what happens if the grid is editing when you try to change indexes, so I've set it so the sorting only happens if// the grid is not editing.

// If the grid is disabled, the row indicator is hidden. You can save the sort column, column widths, and column order to a// TRzRegIniFile using the SaveGrid procedure and Restore method. If the dgRowSelect option is selected, then the cursor keys are// adjusted to be more natural. Ctrl+Delete is no longer passed to the grid; you can use the OnKeyDown event to handle it yourself.

// The TBetterNavigator has a BeforeClick event that lets you abort the navigate action.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.DrawCell.//---------------------------------------------------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by Calculate_Button_Rectangle.//---------------------------------------------------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.DrawButton.//---------------------------------------------------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Draw_Title_Caption.//---------------------------------------------------------------------------------------------------------------------------------

// Draw the specified string on the specified canvas in the specified rectangle with the specified alignment and using the// specified padding. But, if there isn't room to draw the string, then draw it left justified with no padding.

// Draw the specified cell. If the cell is a title cell and the column isn't expandable, then draw the title cell, and if the title// has an accelerator character, underline the accelerator character and include a button in the title. If the grid is not enabled,// then don't draw the row indicator. Otherwise, just call the inherited method.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by SaveGrid.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Save.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by Write_Ini_File_String.//---------------------------------------------------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Restore.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Sort_Data_Set.//---------------------------------------------------------------------------------------------------------------------------------

procedure TBetterDBGrid.DoSort( const FieldName: string );

// Call the OnSort event. A descendant could override this method to do the sorting without having to use the OnSort event.

// Sort the data set on the specified field. If the data set is not a TableInfo, then you must use the OnSort event to do the// actual sorting. Changing the index causes the grid to scroll all the way to the left. I don't know why it does this. I scroll it// back by saving and restoring LeftCol.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.KeyDown.//---------------------------------------------------------------------------------------------------------------------------------

// Adjust the key handling of the grid so it is more natural. If we handle the key, clear the key so the grid won't do the normal// processing. The Windows messages are scroll-bar messages. If the mouse is being clicked, clear the mouse click and eat the key.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.MouseDown.//---------------------------------------------------------------------------------------------------------------------------------

function TBetterDBGrid.AcquireFocus: boolean;

// Acquire the focus, if possible. Return whether the focus was acquired. This is identical to the private TDBGrid.AcquireFocus// method.

begin

result := true;

if FAcquireFocus and self.CanFocus and not ( csDesigning in ComponentState ) then begin self.SetFocus; result := self.Focused or ( InplaceEditor <> nil ) and InplaceEditor.Focused; end;

// If the specified point is in the button in the title of the specified column, save the field name of the column in the dbgrid// object's field. Save the button rectangle in the dbgrid object's field. Make the button be redrawn in the down state.

// If a title button was clicked, record the column field name and the clicked-button rectangle. Otherwise, call the inheried// method.

var Cell: TGridCoord;

begin

Cell := MouseCoord( X, Y ); Clicked_Column_Field_Name := '';

if self.AcquireFocus and not ( csDesigning in ComponentState ) and not EditorMode and ( Button = mbLeft ) and not ( ssDouble in Shift ) and ( Cell.X >= IndicatorOffset ) and ( dgTitles in Options ) and ( Cell.Y = TitleRow ) then

// Call the inherited method. If a title button was clicked, sort the data set on the clicked column. We clear// Clicked_Column_Field_Name before calling the methods that may redraw the title buttons because its value is checked by the// DrawButton method.

else if Clicked_Button_Down then self.Invalidate_Clicked_Button_Rectangle; end;

inherited;

end; // TBetterDBGrid.MouseUp.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.CMDialogChar.//---------------------------------------------------------------------------------------------------------------------------------

// This method will be called if the user types an accelerator key. Accelerator keys are Alt plus a character or, if the control// with focus doesn't want characters, just the character without Alt. Check whether the key is the accelerator for any of the// column titles. If so, sort on the column. Do not set focus to the grid.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.HandleAccelerator.//---------------------------------------------------------------------------------------------------------------------------------

// If the specified key is the accelerator key for a column title, sort on the column. This method may be called in another// control's OnKeyPress event if you want the user to be able to use the grid's accelerator key without pressing Alt, but Windows// thinks the other control wants the key, e.g., TRzNumericEdit.

// Call the BeforeClick event. If the BeforeClick event sets OK to false, then don't call the inherited method. Raize's navigator// doesn't let you say you don't want to navigate (since the NavBtn parameter has to have some value). This lets you abort a// navigate action, if you want to.

// The TBetterDBGrid has optional buttons in the column titles. To get a button in a column title, put an ampersand in the column// Title.Caption property. This also underlines the letter after the ampersand in the title. Clicking the button or pressing the// accelerator key will sort the data set on the column, assuming that you change to the appropriate index in the OnSort event.// Only one column can be sorted on at a time.

// For the TDBGrid (and TRzDBGrid), if dgTitleClick is in TDBGrid.Options, then the title changes color and looks depressed when// the user clicks it. And, if dgTitleHotTrack is in TDBGrid.Options, then the title cells hot track (althogh only seems to take// effect if also have dgTitleClick). The purpose of these options is similar to that of having title buttons, but it doesn't make// much sense to do both (and it would be complicated to support both at the same time). So, if you set either of these options,// then you won't get the buttons in the titles or the underlined letters. In the TDBGrid, if you don't have dgTitleClick, then the// OnTitleClick event won't be called. However, the TBetterDBGrid will call the event when a title button is clicked or the title// accelerator key is pressed.

// I don't know what happens if the grid is editing when you try to change indexes, so I've set it so that the sorting only happens// if the grid is not editing.

// Additional differences from TDBGrid/TRzDBGrid: If the grid is disabled, the row indicator is hidden. You can save the sort// column, column widths, and column order to a TRzRegIniFile using the SaveGrid procedure and Restore method. If the dgRowSelect// option is selected, then the cursor keys are adjusted to be more natural. Ctrl+Delete is no longer passed to the grid; you can// use the OnKeyDown event to handle it yourself.

// The TBetterNavigator has a BeforeClick event that lets you abort the navigate action.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.DrawCell.//---------------------------------------------------------------------------------------------------------------------------------

function TBetterDBGrid.Title_Buttons_Active: boolean;

// Return true if the grid's options are compatible with title buttons. We don't know what should happen, or if our modifications// will work, if IsRightToLeft is true, so don't display the title buttons in that case.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Hide_Indicator_If_Not_Enabled.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Draw_Background_For_Raw_Cell.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Raw_To_Data_Row.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.TitleOffset.//---------------------------------------------------------------------------------------------------------------------------------

// Return the title offset, i.e., the number of rows of titles. This should be the same as the private FTitleOffset. I don't know// if the check on ComponentState is necessary, but TCustomDBGrid.InternalLayout does it.

// Return the data row number corresponding to the specified raw row number. The raw row numbers number all rows (both title and// data) starting at zero. The data row numbers have the first data row as row number zero.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.DrawButton.//---------------------------------------------------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Draw_Title_Caption.//---------------------------------------------------------------------------------------------------------------------------------

// Draw the specified string on the specified canvas in the specified rectangle with the specified alignment and using the// specified padding. But, if there isn't room to draw the string, then draw it left justified with no padding. The background// should already be drawn; we use a brush style of bsClear.

// Draw the specified cell. If the cell is a title cell and the column isn't expandable, then draw the title cell, and if the title// has an accelerator character, underline the accelerator character and include a button in the title. If the grid is not enabled,// then don't draw the row indicator. Otherwise, just call the inherited method. When reading the Delphi code, it helps to remember// that gdFixed will be in AState for the title row and the DrawCells procedure in TRzDBGrid.Paint paints over the raised-inner// cell border.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by SaveGrid.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Save.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by Write_Ini_File_String.//---------------------------------------------------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Restore.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Sort_Data_Set.//---------------------------------------------------------------------------------------------------------------------------------

procedure TBetterDBGrid.DoSort( const FieldName: string );

// Call the OnSort event. A descendant could override this method to do the sorting without having to use the OnSort event.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.KeyDown.//---------------------------------------------------------------------------------------------------------------------------------

// Adjust the key handling of the grid so it is more natural. If we handle the key, clear the key so the grid won't do the normal// processing. The Windows messages are scroll-bar messages. If the mouse is being clicked, clear the mouse click and eat the key.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.MouseDown.//---------------------------------------------------------------------------------------------------------------------------------

function TBetterDBGrid.AcquireFocus: boolean;

// Acquire the focus, if possible. Return whether the focus was acquired. This is identical to the private TDBGrid.AcquireFocus// method.

begin

result := true;

if FAcquireFocus and self.CanFocus and not ( csDesigning in ComponentState ) then begin self.SetFocus; result := self.Focused or ( InplaceEditor <> nil ) and InplaceEditor.Focused; end;

// If the specified point is in the button in the title of the specified column, save the field name of the column in the dbgrid// object's field. Save the button rectangle in the dbgrid object's field. Make the button be redrawn in the down state.

// If a title button was clicked, record the column field name and the clicked-button rectangle. Otherwise, call the inheried// method. Note that Depress_Button_If_Point_In_Title_Button may set ClickedColumn.

var Cell: TGridCoord;

begin

Cell := MouseCoord( X, Y ); ClickedColumn := nil;

if self.Title_Buttons_Active and self.AcquireFocus and not ( csDesigning in ComponentState ) and not EditorMode and ( Button = mbLeft ) and not ( ssDouble in Shift ) and ( Cell.X >= IndicatorOffset ) and ( Cell.Y = TitleRow ) then

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.MouseUp.//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.Sort_Data_And_Call_Title_Click_Event.//---------------------------------------------------------------------------------------------------------------------------------

// If there is an OnTitleClick event, call it. The TDBGrid.TitleClick method only calls the event if dgTitleClick is in Options,// but we want to call it if the user clicks one of our buttons or uses the title accelerator key.

// If a title button was clicked, sort the data set on the clicked column. Call the inherited method. We clear ClickedColumn before// calling the methods that may redraw the title buttons because its value is checked by the DrawButton method.

else if Clicked_Button_Down then self.Invalidate_Clicked_Button_Rectangle; end;

inherited;

end; // TBetterDBGrid.MouseUp.

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.CMDialogChar.//---------------------------------------------------------------------------------------------------------------------------------

// This method will be called if the user types an accelerator key. Accelerator keys are Alt plus a character or, if the control// with focus doesn't want characters, just the character without Alt. Check whether the key is the accelerator for any of the// column titles. If so, sort on the column and call the OnTitleClick event. Do not set focus to the grid (so the user can sort the// grid while another control retains the focus).

//---------------------------------------------------------------------------------------------------------------------------------// Start of procedures used by TBetterDBGrid.HandleAccelerator.//---------------------------------------------------------------------------------------------------------------------------------

function Key_Code_For_Character( const Key: char ): word;

// Return the virtual key code for the specified character. Since we are converting to a key code, the character should be ASCII.

// If the specified key is the accelerator key for a column title, sort on the column and call the OnTitleClick event. This method// may be called in another control's OnKeyPress event if you want the user to be able to use the grid's accelerator key without// pressing Alt, but Windows thinks the other control wants the key, e.g., TRzNumericEdit.

// Call the BeforeClick event. If the BeforeClick event sets OK to false, then don't call the inherited method. Raize's navigator// doesn't let you say you don't want to navigate (since the NavBtn parameter has to have some value). This lets you abort a// navigate action, if you want to.