Implement a more effective solution for hiding and unhiding columns in ListColumns.

Pros:
- ShowDBProperties doesn't reset column layout
- Sort direction is remembered automatically
- Reading columnlist from registry is done once in a TMDIChild, not on each ShowDBProperties
- Hiding/unhiding columns doesn't call ShowDBProperties
- No more hassling with what is called a "default column" or not. popupDBGrid is usable like in Windows Explorer.
- Column layout is stored global, not per session.
Cons:
- Column layout from old settings logic needed to be discarded

Side effect:
- Fixes a potential AV in FormatNumber(str) with empty strings.
- Created an overloaded FormatByteNumber() which can take a string as input
This commit is contained in:
Ansgar Becker
2007-08-30 23:49:38 +00:00
parent 2dee4b326f
commit 8ab0b95440
4 changed files with 248 additions and 164 deletions

View File

@ -361,14 +361,14 @@ object MDIChild: TMDIChild
Height = 203 Height = 203
Align = alClient Align = alClient
EditDelay = 500 EditDelay = 500
Header.AutoSizeIndex = 6 Header.AutoSizeIndex = -1
Header.Font.Charset = DEFAULT_CHARSET Header.Font.Charset = DEFAULT_CHARSET
Header.Font.Color = clWindowText Header.Font.Color = clWindowText
Header.Font.Height = -11 Header.Font.Height = -11
Header.Font.Name = 'Tahoma' Header.Font.Name = 'Tahoma'
Header.Font.Style = [] Header.Font.Style = []
Header.Height = 20 Header.Height = 20
Header.Options = [hoAutoResize, hoColumnResize, hoDblClickResize, hoHotTrack, hoShowSortGlyphs, hoVisible] Header.Options = [hoColumnResize, hoDblClickResize, hoHotTrack, hoShowSortGlyphs, hoVisible]
Header.PopupMenu = popupDbGridHeader Header.PopupMenu = popupDbGridHeader
Header.SortColumn = 0 Header.SortColumn = 0
Images = MainForm.ImageList1 Images = MainForm.ImageList1
@ -432,8 +432,78 @@ object MDIChild: TMDIChild
item item
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible] Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 6 Position = 6
Width = 10 Width = 100
WideText = 'Comment' WideText = 'Comment'
end
item
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 7
WideText = 'Version'
end
item
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 8
Width = 70
WideText = 'Row format'
end
item
Alignment = taRightJustify
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 9
Width = 70
WideText = 'Avg row length'
end
item
Alignment = taRightJustify
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 10
Width = 70
WideText = 'Max data length'
end
item
Alignment = taRightJustify
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 11
Width = 70
WideText = 'Index length'
end
item
Alignment = taRightJustify
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 12
Width = 70
WideText = 'Data free'
end
item
Alignment = taRightJustify
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 13
Width = 90
WideText = 'Auto increment'
end
item
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 14
Width = 120
WideText = 'Check time'
end
item
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 15
Width = 70
WideText = 'Collation'
end
item
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 16
Width = 70
WideText = 'Checksum'
end
item
Options = [coAllowClick, coEnabled, coParentBidiMode, coParentColor, coResizable, coShowDropMark, coVisible]
Position = 17
Width = 70
WideText = 'Create options'
end> end>
end end
object pnlDatabaseToolbar: TPanel object pnlDatabaseToolbar: TPanel
@ -2189,7 +2259,6 @@ object MDIChild: TMDIChild
object popupDbGridHeader: TPopupMenu object popupDbGridHeader: TPopupMenu
AutoHotkeys = maManual AutoHotkeys = maManual
AutoLineReduction = maManual AutoLineReduction = maManual
Images = MainForm.ImageList1
Left = 72 Left = 72
Top = 80 Top = 80
object DefaultColumnLayout1: TMenuItem object DefaultColumnLayout1: TMenuItem

View File

@ -523,6 +523,7 @@ type
UserQueryFired : Boolean; UserQueryFired : Boolean;
CachedTableLists : TStringList; CachedTableLists : TStringList;
QueryHelpersSelectedItems : Array[0..3] of Integer; QueryHelpersSelectedItems : Array[0..3] of Integer;
ListTablesColumnNames : TStringList;
function GetQueryRunning: Boolean; function GetQueryRunning: Boolean;
procedure SetQueryRunning(running: Boolean); procedure SetQueryRunning(running: Boolean);
@ -533,6 +534,7 @@ type
function RunThreadedQuery(AQuery : String) : TMysqlQuery; function RunThreadedQuery(AQuery : String) : TMysqlQuery;
procedure DisplayRowCountStats(ds: TDataSet); procedure DisplayRowCountStats(ds: TDataSet);
procedure insertFunction(Sender: TObject); procedure insertFunction(Sender: TObject);
procedure SetupListTablesHeader;
public public
ActualDatabase : String; ActualDatabase : String;
@ -1951,14 +1953,10 @@ end;
{ Show tables and their properties on the tabsheet "Database" } { Show tables and their properties on the tabsheet "Database" }
procedure TMDIChild.ShowDBProperties(Sender: TObject); procedure TMDIChild.ShowDBProperties(Sender: TObject);
var var
i,j,k : Integer; i : Integer;
bytes : Extended; bytes : Extended;
menuitem : TMenuItem;
TablelistColumns: TStringList;
column : TVirtualTreeColumn;
ds : TDataSet; ds : TDataSet;
OldSortColumn : Integer; ListCaptions : TStringList;
OldSortDirection: TSortDirection;
begin begin
// DB-Properties // DB-Properties
Screen.Cursor := crHourGlass; Screen.Cursor := crHourGlass;
@ -1974,146 +1972,107 @@ begin
ds := FetchActiveDbTableList; ds := FetchActiveDbTableList;
// Generate items for popupDbGridHeader
for i:=popupDbGridHeader.Items.Count-1 downto 2 do
popupDbGridHeader.Items.Delete( i );
with TRegistry.Create do
begin
openkey( REGPATH + '\Servers\' + FConn.Description, true );
if ValueExists( 'TablelistDefaultColumns' ) then
popupDbGridHeader.Items[0].Checked := ReadBool( 'TablelistDefaultColumns' );
if ValueExists( 'TablelistColumns' ) then
TablelistColumns := Explode( ',', ReadString( 'TablelistColumns' ) )
else
TablelistColumns := TStringList.Create;
free;
end;
for i:=0 to ds.FieldCount-1 do
begin
menuitem := TMenuItem.Create( self );
menuitem.Caption := ds.Fields[i].Fieldname;
menuitem.Tag := 2;
menuitem.OnClick := MenuTablelistColumnsClick;
if i=0 then
begin
menuitem.Enabled := false; // tablename should always be kept
menuitem.Checked := true;
end
else if TablelistColumns.Count > 0 then
begin
menuitem.Checked := TablelistColumns.IndexOf( menuitem.Caption ) > -1;
end;
popupDbGridHeader.Items.Add( menuitem );
end;
// Remember sorting options and restore them later
OldSortColumn := ListTables.Header.SortColumn;
OldSortDirection := ListTables.Header.SortDirection;
ListTables.BeginUpdate; ListTables.BeginUpdate;
ListTables.Clear; ListTables.Clear;
ListTables.Header.Columns.BeginUpdate;
ListTables.Header.Columns.Clear;
column := ListTables.Header.Columns.Add;
column.Text := 'Table';
column.Width := 120;
if popupDbGridHeader.Items[0].Checked then
begin // Default columns - initialize column headers
column := ListTables.Header.Columns.Add;
column.Text := 'Records';
column.Alignment := taRightJustify;
column.Width := 80;
column := ListTables.Header.Columns.Add; // (Un)hides (un)selected column names in list
column.Text := 'Size'; SetupListTablesHeader;
column.Alignment := taRightJustify;
column.Width := 70;
column := ListTables.Header.Columns.Add;
column.Text := 'Created';
column.Width := 120;
column := ListTables.Header.Columns.Add;
column.Text := 'Updated';
column.Width := 120;
column := ListTables.Header.Columns.Add;
column.Text := 'Engine';
column.Width := 70;
column := ListTables.Header.Columns.Add;
column.Text := 'Comment';
column.Width := 50;
end;
for i:=0 to TablelistColumns.Count-1 do
begin
column := ListTables.Header.Columns.Add;
column.Text := TablelistColumns[i];
column.Width := 50;
column.MinWidth := 50;
end;
// Ensure AutoResize does not cause an AV
ListTables.Header.SortColumn := OldSortColumn;
ListTables.Header.SortDirection := OldSortDirection;
ListTables.Header.Columns.EndUpdate;
SetLength(VTRowDataListTables, ds.RecordCount); SetLength(VTRowDataListTables, ds.RecordCount);
for i := 1 to ds.RecordCount do for i := 1 to ds.RecordCount do
begin begin
VTRowDataListTables[i-1].Captions := TStringList.Create; listcaptions := TStringList.Create;
VTRowDataListTables[i-1].ImageIndex := 39;
// Table // Table
VTRowDataListTables[i-1].Captions.Add( ds.Fields[0].AsString ); ListCaptions.Add( ds.Fields[0].AsString );
if (mysql_version >= 32300) and popupDbGridHeader.Items[0].Checked then if mysql_version >= 32300 then
begin // Default columns begin // Default columns
// Records // Records
VTRowDataListTables[i-1].Captions.Add( FormatNumber( ds.FieldByName('Rows').AsFloat ) ); ListCaptions.Add( FormatNumber( ds.FieldByName('Rows').AsFloat ) );
// Size: Data_length + Index_length // Size: Data_length + Index_length
bytes := ds.FieldByName('Data_length').AsFloat + ds.FieldByName('Index_length').AsFloat; bytes := ds.FieldByName('Data_length').AsFloat + ds.FieldByName('Index_length').AsFloat;
VTRowDataListTables[i-1].Captions.Add( FormatNumber( bytes / 1024 + 1 ) + ' KB'); ListCaptions.Add( FormatNumber( bytes / 1024 + 1 ) + ' KB');
// Created: // Created:
if not ds.FieldByName('Create_time').IsNull then if not ds.FieldByName('Create_time').IsNull then
VTRowDataListTables[i-1].Captions.Add( ds.FieldByName('Create_time').AsString ) ListCaptions.Add( ds.FieldByName('Create_time').AsString )
else else
VTRowDataListTables[i-1].Captions.Add('N/A'); ListCaptions.Add('N/A');
// Updated: // Updated:
if not ds.FieldByName('Update_time').IsNull then if not ds.FieldByName('Update_time').IsNull then
VTRowDataListTables[i-1].Captions.Add( ds.FieldByName('Update_time').AsString ) ListCaptions.Add( ds.FieldByName('Update_time').AsString )
else else
VTRowDataListTables[i-1].Captions.Add('N/A'); ListCaptions.Add('N/A');
// Type // Type
if ds.FindField('Type')<>nil then if ds.FindField('Type')<>nil then
VTRowDataListTables[i-1].Captions.Add( ds.FieldByName('Type').AsString ) ListCaptions.Add( ds.FieldByName('Type').AsString )
else if ds.FindField('Engine')<>nil then else if ds.FindField('Engine')<>nil then
VTRowDataListTables[i-1].Captions.Add( ds.FieldByName('Engine').AsString ) ListCaptions.Add( ds.FieldByName('Engine').AsString )
else else
VTRowDataListTables[i-1].Captions.Add(''); ListCaptions.Add('');
// Comment // Comment
VTRowDataListTables[i-1].Captions.Add( ds.FieldByName('Comment').AsString ); ListCaptions.Add( ds.FieldByName('Comment').AsString );
end;
for j:=0 to TablelistColumns.Count-1 do // Add content from other columns which are not visible in ListTables by default
begin
for k:=0 to ds.FieldCount-1 do if ds.FindField('Version')<>nil then
begin ListCaptions.Add( ds.FieldByName('Version').AsString )
if TablelistColumns[j] = ds.Fields[k].FieldName then else
begin ListCaptions.Add('');
if ds.Fields[k].DataType in [ftInteger, ftSmallint, ftWord, ftFloat, ftWord ] then
begin if ds.FindField('Row_format')<>nil then
// Number ListCaptions.Add( ds.FieldByName('Row_format').AsString )
// TODO: doesn't match any column else
ListTables.Header.Columns[ListTables.Header.Columns.Count-1].Alignment := taRightJustify; ListCaptions.Add('');
VTRowDataListTables[i-1].Captions.Add( FormatNumber( ds.Fields[k].AsFloat ) );
end if ds.FindField('Avg_row_length')<>nil then
else ListCaptions.Add( FormatByteNumber(ds.FieldByName('Avg_row_length').AsString) )
// String else
VTRowDataListTables[i-1].Captions.Add( ds.Fields[k].AsString ); ListCaptions.Add('');
break;
end; if ds.FindField('Max_data_length')<>nil then
end; ListCaptions.Add( FormatByteNumber(ds.FieldByName('Max_data_length').AsString) )
else
ListCaptions.Add('');
if ds.FindField('Index_length')<>nil then
ListCaptions.Add( FormatByteNumber(ds.FieldByName('Index_length').AsString) )
else
ListCaptions.Add('');
if ds.FindField('Data_free')<>nil then
ListCaptions.Add( FormatByteNumber(ds.FieldByName('Data_free').AsString) )
else
ListCaptions.Add('');
if ds.FindField('Auto_increment')<>nil then
ListCaptions.Add( FormatNumber(ds.FieldByName('Auto_increment').AsString) )
else
ListCaptions.Add('');
if ds.FindField('Check_time')<>nil then
ListCaptions.Add( ds.FieldByName('Check_time').AsString )
else
ListCaptions.Add('');
if ds.FindField('Collation')<>nil then
ListCaptions.Add( ds.FieldByName('Collation').AsString )
else
ListCaptions.Add('');
if ds.FindField('Checksum')<>nil then
ListCaptions.Add( ds.FieldByName('Checksum').AsString )
else
ListCaptions.Add('');
if ds.FindField('Create_options')<>nil then
ListCaptions.Add( ds.FieldByName('Create_options').AsString )
else
ListCaptions.Add('');
end; end;
VTRowDataListTables[i-1].ImageIndex := 39;
VTRowDataListTables[i-1].Captions := ListCaptions;
ds.Next; ds.Next;
end; end;
mainform.showstatus(ActualDatabase + ': ' + IntToStr(ds.RecordCount) +' table(s)'); mainform.showstatus(ActualDatabase + ': ' + IntToStr(ds.RecordCount) +' table(s)');
@ -5509,44 +5468,6 @@ end;
// Click on first menu-item in context-menu of tablelist-columns
procedure TMDIChild.MenuTablelistColumnsClick(Sender: TObject);
var
menuitem : TMenuItem;
TablelistColumnsList : TStringList;
begin
menuitem := (Sender as TMenuItem);
with TRegistry.Create do
try
openkey( REGPATH + '\Servers\' + FConn.Description, true );
case menuitem.Tag of
1 : // Toggle default-columns
WriteBool( 'TablelistDefaultColumns', not menuitem.Checked );
2 :
begin
if ValueExists( 'TablelistColumns' ) then
TablelistColumnsList := Explode( ',', ReadString( 'TablelistColumns' ) )
else
TablelistColumnsList := TStringList.Create;
if TablelistColumnsList.IndexOf( menuitem.Caption ) > -1 then
TablelistColumnsList.Delete( TablelistColumnsList.IndexOf( menuitem.Caption ) )
else
TablelistColumnsList.Add( menuitem.Caption );
WriteString( 'TablelistColumns', implodestr( ',', TablelistColumnsList ) );
end;
end;
free;
except
free;
MessageDlg( 'Error while writing to registry.', mtError, [mbOK], 0 );
exit;
end;
menuitem.Checked := not menuitem.Checked;
ShowDBProperties( self );
end;
function TMDIChild.mask(str: String) : String; function TMDIChild.mask(str: String) : String;
begin begin
result := maskSql(mysql_version, str); result := maskSql(mysql_version, str);
@ -6136,6 +6057,85 @@ begin
end; end;
{**
Click on popupDBGridHeader
}
procedure TMDIChild.MenuTablelistColumnsClick(Sender: TObject);
var
menuitem : TMenuItem;
reg : TRegistry;
begin
menuitem := (Sender as TMenuItem);
reg := TRegistry.Create;
reg.OpenKey( REGPATH, true );
if ListTablesColumnNames.IndexOf( menuitem.Caption ) > -1 then
ListTablesColumnNames.Delete( ListTablesColumnNames.IndexOf( menuitem.Caption ) )
else
ListTablesColumnNames.Add( menuitem.Caption );
// Store list of columns in registry
reg.WriteString( REGNAME_LISTTABLESCOLUMNNAMES, ListTablesColumnNames.DelimitedText );
SetupListTablesHeader;
end;
{**
(Un)hides (un)selected columns in ListTables
}
procedure TMDIChild.SetupListTablesHeader;
var
reg : TRegistry;
i : Integer;
menuitem : TMenuItem;
begin
if ListTablesColumnNames = nil then
begin
// First time we read the columns list from registry into this global variable
ListTablesColumnNames := TStringList.Create;
reg := TRegistry.Create;
reg.openkey( REGPATH, true );
if reg.ValueExists( REGNAME_LISTTABLESCOLUMNNAMES ) then
begin
ListTablesColumnNames.DelimitedText := reg.ReadString( REGNAME_LISTTABLESCOLUMNNAMES );
// Ensure first column (Table) is always visible
if ListTablesColumnNames.IndexOf(ListTables.Header.Columns[0].Text) = -1 then
ListTablesColumnNames.Add( ListTables.Header.Columns[0].Text );
end
else begin
// If not set, by default make the first 7 columns visible
for i:=0 to ListTables.Header.Columns.Count-1 do
begin
ListTablesColumnNames.Add(ListTables.Header.Columns[i].Text);
if i >= 6 then
break;
end;
end;
reg.Free;
end;
// All columns in ListTables are created at designtime. Only (un)hide them here
// Plus generate menuitems for popupDBgridColumns
popupDBGridHeader.Items.Clear;
for i:=0 to ListTables.Header.Columns.Count-1 do
begin
menuitem := TMenuItem.Create( popupDBGridHeader );
menuitem.Caption := ListTables.Header.Columns[i].Text;
menuitem.OnClick := MenuTablelistColumnsClick;
// Disable hiding first column
menuitem.Enabled := i>0;
popupDbGridHeader.Items.Add( menuitem );
if (i=0) or (ListTablesColumnNames.IndexOf( ListTables.Header.Columns[i].Text ) > -1) then
begin
ListTables.Header.Columns[i].Options := ListTables.Header.Columns[i].Options + [coVisible];
menuitem.Checked := True;
end
else begin
ListTables.Header.Columns[i].Options := ListTables.Header.Columns[i].Options - [coVisible];
menuitem.Checked := False;
end;
end;
end;
end. end.

View File

@ -38,6 +38,7 @@ const
// Registry name for storing list of displayed columns // Registry name for storing list of displayed columns
REGNAME_DISPLAYEDCOLUMNS = 'DisplayedColumns'; REGNAME_DISPLAYEDCOLUMNS = 'DisplayedColumns';
REGNAME_SORTDISPLAYEDCOLUMNS = 'DisplayedColumnsSorted'; REGNAME_SORTDISPLAYEDCOLUMNS = 'DisplayedColumnsSorted';
REGNAME_LISTTABLESCOLUMNNAMES = 'ListTablesColumnNames';
// how much memory we're aiming to use for the // how much memory we're aiming to use for the
// data grid and it's automatic limit function // data grid and it's automatic limit function

View File

@ -81,7 +81,8 @@ type
function GetFieldValue( Field: TField ): String; function GetFieldValue( Field: TField ): String;
function LastPos( substr: WideString; str: WideString): Integer; function LastPos( substr: WideString; str: WideString): Integer;
function ConvertServerVersion( Version: Integer ): String; function ConvertServerVersion( Version: Integer ): String;
function FormatByteNumber( Bytes: Int64; Decimals: Byte = 1 ): String; function FormatByteNumber( Bytes: Int64; Decimals: Byte = 1 ): String; Overload;
function FormatByteNumber( Bytes: String; Decimals: Byte = 1 ): String; Overload;
function FormatTimeNumber( Seconds: Cardinal ): String; function FormatTimeNumber( Seconds: Cardinal ): String;
function TColorToHex( Color : TColor ): string; function TColorToHex( Color : TColor ): string;
function GetVTCaptions( VT: TVirtualStringTree; OnlySelected: Boolean = False; Column: Integer = 0 ): TStringList; function GetVTCaptions( VT: TVirtualStringTree; OnlySelected: Boolean = False; Column: Integer = 0 ): TStringList;
@ -1587,7 +1588,10 @@ end;
} }
function FormatNumber( str: String ): String; Overload; function FormatNumber( str: String ): String; Overload;
begin begin
result := FormatNumber( StrToFloat( str ) ); if str <> '' then
result := FormatNumber( StrToFloat( str ) )
else
result := FormatNumber( 0 );
end; end;
@ -1950,7 +1954,7 @@ end;
@param Int64 Number of Bytes @param Int64 Number of Bytes
@param Byte Decimals to display when bytes is bigger than 1M @param Byte Decimals to display when bytes is bigger than 1M
} }
function FormatByteNumber( Bytes: Int64; Decimals: Byte = 1 ): String; function FormatByteNumber( Bytes: Int64; Decimals: Byte = 1 ): String; Overload;
const const
KB = 1024; KB = 1024;
begin begin
@ -1965,6 +1969,16 @@ begin
end; end;
{**
An overloaded function of the previous one which can
take a string as input
}
function FormatByteNumber( Bytes: String; Decimals: Byte = 1 ): String; Overload;
begin
Result := FormatByteNumber( MakeInt(Bytes), Decimals );
end;
{** {**
Format a number of seconds to a human readable time format Format a number of seconds to a human readable time format
@param Cardinal Number of seconds @param Cardinal Number of seconds