unit TableDialog;
interface
uses
Windows, Messages, SysUtils, Types, UITypes, Classes, Forms, Dialogs,
Controls, Graphics, StdCtrls, ComCtrls;
type
TTableDialog = class
strict private
type TFormData = class(TComponent)
public
ListView: TListView;
IconKind: PWideChar;
Icon: HICON;
LIWSD: Boolean;
Title: string;
end;
class function Scale(X: Integer): Integer; static;
class procedure FormShow(Sender: TObject);
class procedure FormDestroy(Sender: TObject);
class procedure FormPaint(Sender: TObject);
class procedure FormKeyPress(Sender: TObject; var Key: Char);
class procedure LVToClipboard(AListView: TListView);
class function GetTitleWidth(const AFormData: TFormData): Integer; static;
class procedure SetupTitleFont(ACanvas: TCanvas); static;
public
class function ShowTable(AOwner: TCustomForm; const ACaption, ATitle: string;
const ANames, AValues: array of string;
const AButtonTypes: array of TModalResult;
const AButtonLabels: array of string;
ADefaultButton, ACancelButton: TModalResult;
ADialogType: TMsgDlgType;
const AWidth: Integer = 0; const AHeight: Integer = 0): TModalResult; overload;
class function ShowTable(AOwner: TCustomForm; const ACaption, ATitle: string;
const ANames, AValues: array of string;
ADialogType: TMsgDlgType;
const AWidth: Integer = 0; const AHeight: Integer = 0): TModalResult; overload;
class function ShowTable(AOwner: TCustomForm; const ACaption: string;
const ANames, AValues: array of string;
ADialogType: TMsgDlgType;
const AWidth: Integer = 0; const AHeight: Integer = 0): TModalResult; overload;
end;
implementation
uses
Math, Clipbrd, CommCtrl;
class procedure TTableDialog.FormShow(Sender: TObject);
var
FormData: TFormData;
ComCtl: HMODULE;
LoadIconWithScaleDown: function(hinst: HINST; pszName: LPCWSTR; cx: Integer;
cy: Integer; var phico: HICON): HResult; stdcall;
begin
if not (Sender is TForm) then
Exit;
if not (TObject(TForm(Sender).Tag) is TFormData) then
Exit;
TForm(Sender).OnShow := nil;
FormData := TFormData(TForm(Sender).Tag);
if FormData.IconKind = nil then
Exit;
ComCtl := LoadLibrary('ComCtl32.dll');
if ComCtl <> 0 then
begin
try
LoadIconWithScaleDown := GetProcAddress(ComCtl, 'LoadIconWithScaleDown');
if Assigned(LoadIconWithScaleDown) then
FormData.LIWSD := Succeeded(LoadIconWithScaleDown(0, FormData.IconKind,
Scale(32), Scale(32), FormData.Icon));
finally
FreeLibrary(ComCtl);
end;
end;
if not FormData.LIWSD then
FormData.Icon := LoadIcon(0, FormData.IconKind);
end;
class function TTableDialog.GetTitleWidth(const AFormData: TFormData): Integer;
var
bm: TBitmap;
begin
if AFormData.Title.IsEmpty then
Exit(0);
bm := TBitmap.Create;
try
SetupTitleFont(bm.Canvas);
Result := bm.Canvas.TextWidth(AFormData.Title)
finally
bm.Free;
end;
end;
class procedure TTableDialog.FormDestroy(Sender: TObject);
var
FormData: TFormData;
begin
if not (Sender is TForm) then
Exit;
if not (TObject(TForm(Sender).Tag) is TFormData) then
Exit;
FormData := TFormData(TForm(Sender).Tag);
if (FormData.Icon <> 0) and FormData.LIWSD then
DestroyIcon(FormData.Icon);
end;
class procedure TTableDialog.FormKeyPress(Sender: TObject; var Key: Char);
var
FormData: TFormData;
begin
if not (Sender is TForm) then
Exit;
if not (TObject(TForm(Sender).Tag) is TFormData) then
Exit;
FormData := TFormData(TForm(Sender).Tag);
case Key of
^C:
LVToClipboard(FormData.ListView);
end;
end;
class procedure TTableDialog.FormPaint(Sender: TObject);
var
FormData: TFormData;
Frm: TForm;
Y: Integer;
R: TRect;
S: string;
begin
if not (Sender is TForm) then
Exit;
if not (TObject(TForm(Sender).Tag) is TFormData) then
Exit;
Frm := TForm(Sender);
FormData := TFormData(TForm(Sender).Tag);
Y := Frm.ClientHeight - Scale(25 + 8 + 8);
Frm.Canvas.Brush.Color := clWhite;
Frm.Canvas.FillRect(Rect(0, 0, Frm.ClientWidth, Y));
Frm.Canvas.Pen.Color := $00DFDFDF;
Frm.Canvas.MoveTo(0, Y);
Frm.Canvas.LineTo(Frm.ClientWidth, Y);
if FormData.Icon <> 0 then
begin
DrawIconEx(Frm.Canvas.Handle, Scale(8), Scale(8), FormData.Icon,
Scale(32), Scale(32), 0, 0, DI_NORMAL);
R.Left := R.Left + Scale(32 + 8);
end;
S := FormData.Title;
if not s.IsEmpty then
begin
R := Rect(
Scale(IfThen(FormData.Icon <> 0, 8 + 32 + 8, 8)),
Scale(8),
Frm.ClientWidth - Scale(8),
Scale(8 + 32)
);
SetupTitleFont(Frm.Canvas);
Frm.Canvas.TextRect(R, S, [tfSingleLine, tfLeft, tfTop, tfEndEllipsis]);
end;
end;
class procedure TTableDialog.LVToClipboard(AListView: TListView);
function GetRow(AIndex: Integer): string;
begin
if InRange(AIndex, 0, AListView.Items.Count - 1) and (AListView.Items[AIndex].SubItems.Count = 1) then
Result := AListView.Items[AIndex].Caption + #9 + AListView.Items[AIndex].SubItems[0]
else
Result := '';
end;
var
S: string;
i: Integer;
begin
if AListView = nil then
Exit;
S := GetRow(0);
for i := 1 to AListView.Items.Count - 1 do
S := S + sLineBreak + GetRow(i);
Clipboard.AsText := S;
end;
class function TTableDialog.Scale(X: Integer): Integer;
begin
Result := MulDiv(X, Screen.PixelsPerInch, 96);
end;
class procedure TTableDialog.SetupTitleFont(ACanvas: TCanvas);
begin
if Screen.Fonts.IndexOf('Segoe UI') <> -1 then
ACanvas.Font.Name := 'Segoe UI'
else if Screen.Fonts.IndexOf('Tahoma') <> -1 then
ACanvas.Font.Name := 'Tahoma';
ACanvas.Font.Size := 12;
ACanvas.Font.Color := $00993300;
end;
class function TTableDialog.ShowTable(AOwner: TCustomForm; const ACaption: string;
const ANames, AValues: array of string; ADialogType: TMsgDlgType;
const AWidth, AHeight: Integer): TModalResult;
begin
Result := ShowTable(
AOwner,
ACaption,
'',
ANames,
AValues,
[mrOk],
['OK'],
mrOk,
mrOk,
ADialogType,
AWidth,
AHeight);
end;
class function TTableDialog.ShowTable(AOwner: TCustomForm; const ACaption,
ATitle: string; const ANames, AValues: array of string;
ADialogType: TMsgDlgType; const AWidth, AHeight: Integer): TModalResult;
begin
Result := ShowTable(
AOwner,
ACaption,
ATitle,
ANames,
AValues,
[mrOk],
['OK'],
mrOk,
mrOk,
ADialogType,
AWidth,
AHeight);
end;
class function TTableDialog.ShowTable(AOwner: TCustomForm;
const ACaption, ATitle: string;
const ANames, AValues: array of string;
const AButtonTypes: array of TModalResult;
const AButtonLabels: array of string;
ADefaultButton, ACancelButton: TModalResult;
ADialogType: TMsgDlgType;
const AWidth, AHeight: Integer): TModalResult;
const
Sounds: array[TMsgDlgType] of Integer =
(MB_ICONWARNING, MB_ICONERROR, MB_ICONINFORMATION, MB_ICONQUESTION, 0);
Icons: array[TMsgDlgType] of MakeIntResource =
(IDI_WARNING, IDI_ERROR, IDI_INFORMATION, IDI_QUESTION, nil);
var
dlg: TForm;
FormData: TFormData;
lv: TListView;
btn: TButton;
i: Integer;
snd: Integer;
W0, W1, W01Min, W01Max, MinW, H, RSpace: Integer;
begin
if Length(ANames) <> Length(AValues) then
raise Exception.Create('The lengths of the columns don''t match.');
if Length(AButtonTypes) <> Length(AButtonLabels) then
raise Exception.Create('The lengths of the button arrays don''t match.');
dlg := TForm.Create(AOwner);
try
dlg.BorderStyle := bsDialog;
dlg.Caption := ACaption;
if AWidth <> 0 then
dlg.Width := Scale(AWidth)
else
dlg.Width := Scale(640);
if AHeight <> 0 then
dlg.Height := Scale(AHeight)
else
dlg.Height := Scale(480);
dlg.Position := poOwnerFormCenter;
dlg.Scaled := False;
dlg.Font.Name := 'Segoe UI';
dlg.Font.Size := 9;
FormData := TFormData.Create(dlg);
dlg.Tag := NativeInt(FormData);
TFormData(dlg.Tag).IconKind := Icons[ADialogType];
TFormData(dlg.Tag).Title := ATitle;
dlg.OnShow := FormShow;
dlg.OnDestroy := FormDestroy;
dlg.OnPaint := FormPaint;
dlg.OnKeyPress := FormKeyPress;
dlg.KeyPreview := True;
for i := 0 to High(AButtonTypes) do
begin
btn := TButton.Create(dlg);
btn.Parent := dlg;
btn.Caption := AButtonLabels[i];
btn.Default := AButtonTypes[i] = ADefaultButton;
btn.Cancel := AButtonTypes[i] = ACancelButton;
btn.ModalResult := AButtonTypes[i];
btn.Width := Scale(75);
btn.Height := Scale(25);
btn.Left := dlg.ClientWidth - (btn.Width + Scale(8)) * (Length(AButtonTypes) - i);
btn.Top := dlg.ClientHeight - btn.Height - Scale(8);
btn.Anchors := [akRight, akBottom];
end;
lv := TListView.Create(dlg);
TFormData(dlg.Tag).ListView := lv;
lv.Parent := dlg;
lv.DoubleBuffered := True;
lv.ReadOnly := True;
lv.BorderStyle := bsNone;
lv.Left := Scale(8) + IfThen(Icons[ADialogType] <> nil, Scale(32 + 8));
lv.Width := dlg.ClientWidth - Scale(16) - IfThen(Icons[ADialogType] <> nil, Scale(32 + 8));
if ATitle.IsEmpty then
begin
lv.Top := Scale(8);
lv.Height := dlg.ClientHeight - Scale(16 + 8 + 4) - Scale(25);
end
else
begin
lv.Top := Scale(8 + 32 + 8);
lv.Height := dlg.ClientHeight - Scale(8 + 32 + 8 + 8 + 4 + 8) - Scale(25);
end;
lv.Anchors := [akLeft, akTop, akRight, akBottom];
lv.ViewStyle := vsReport;
lv.RowSelect := True;
lv.ShowColumnHeaders := False;
RSpace := GetSystemMetricsForWindow(SM_CXVSCROLL, dlg.Handle) + scale(2);
with lv.Columns.Add do
begin
Caption := 'Name';
Width := Min(Scale(150), lv.ClientWidth div 2);
end;
with lv.Columns.Add do
begin
Caption := 'Value';
Width := lv.ClientWidth - lv.Columns[0].Width - RSpace;
end;
W0 := 0;
W1 := 0;
lv.Items.BeginUpdate;
try
for i := 0 to High(ANames) do
with lv.Items.Add do
begin
Caption := ANames[i];
SubItems.Add(AValues[i]);
W0 := Max(W0, ListView_GetStringWidth(lv.Handle, PChar('XXI' + ANames[i])));
W1 := Max(W1, ListView_GetStringWidth(lv.Handle, PChar('XXI' + AValues[i])));
end;
finally
lv.Items.EndUpdate;
end;
if lv.Items.Count > 0 then
H := lv.Items[0].DisplayRect(drBounds).Height
else
H := 0;
if AWidth = 0 then
begin
if ATitle.IsEmpty then
MinW := Scale(300)
else
MinW := Min(Scale(8) + IfThen(Icons[ADialogType] <> nil, Scale(32 + 8)) + GetTitleWidth(FormData) + Scale(16), Scale(800));
MinW := Max(MinW, Scale(300));
MinW := Max(MinW, Scale(75) * Length(AButtonTypes) + Scale(8) * (Length(AButtonTypes) + 1));
W01Min := Min(W0, W1);
W01Max := Max(W0, W1);
if W01Min < W01Max div 5 then
W01Min := W01Max div 5;
dlg.ClientWidth := EnsureRange(
dlg.ClientWidth - lv.Width + W01Max + W01Min + RSpace,
MinW,
Scale(800)
);
end;
if AHeight = 0 then
dlg.ClientHeight := EnsureRange(
dlg.ClientHeight - lv.Height + Succ(lv.Items.Count) * H,
Scale(100),
Scale(600)
);
if (Round(1.2 * W0) < (lv.ClientWidth - RSpace) div 2) and (Round(1.2 * W0) + W1 < lv.ClientWidth - RSpace) then
W0 := Round(1.2 * W0);
if (W0 > (lv.ClientWidth - RSpace) div 2) and (W1 > (lv.ClientWidth - RSpace) div 2) then
W0 := (lv.ClientWidth - RSpace) div 2;
if (W0 > 2 * (lv.ClientWidth - RSpace) div 3) and (W1 > (lv.ClientWidth - RSpace) div 3) then
W0 := (lv.ClientWidth - RSpace) div 2;
lv.Columns[0].Width :=
EnsureRange(
W0,
lv.ClientWidth div 6,
3 * lv.ClientWidth div 4
);
lv.Columns[1].Width := lv.ClientWidth - lv.Columns[0].Width - RSpace;
snd := Sounds[ADialogType];
if snd <> 0 then
MessageBeep(snd);
for var CtlIdx := 0 to dlg.ControlCount - 1 do
if (dlg.Controls[CtlIdx] is TButton) and TButton(dlg.Controls[CtlIdx]).Default then
begin
dlg.ActiveControl := TButton(dlg.Controls[CtlIdx]);
Break;
end;
Result := dlg.ShowModal;
finally
dlg.Free;
end;
end;
end.