How to change TDBGrld row colour when a field value is changed - delphi

(Minimal RepEx at the end)
I have a DBGrid (actually a TSMDBgrid) populated by a query.
One field, payment_made, is Boolean and displays in the grid as a checkbox either ticked or unticked. depending upon the field value. This is the only field that can be edited. Initially all rows are unticked
I'm using the often published DrawColumnCell() code below to change the whole row colour to blue if the Boolean field on that row gets changed to checked (true) and back to white if it subsequently gets unchecked (false).
I want all rows that are checked to remain blue.
The code I use is
procedure TFrmBulkSubdPaymentRecord.SMDBGrid1DrawColumnCell(Sender: TObject;
const Rect: TRect;
DataCol: Integer;
Column: TColumn;
State: TGridDrawState);
//see https://www.thoughtco.com/change-coloring-in-tdbgrid-component-4077252
begin
if MyQuery1.FieldByName('payment_made').AsBoolean = true then
SMDBGrid1.Canvas.Brush.Color := $00ffff99//pale blue
else
SMDBGrid1.Canvas.Brush.Color := clwhite;
SMDBGrid1.DefaultDrawColumnCell (Rect, DataCol, Column, State);
end;
When starting with a white grid, if I check the payment_made box in ,say, row 3 the whole row goes blue.
If I check another row, say row 6 then that whole row row goes blue.
If I then uncheck row 3 the whole of that row goes white again
But, if I then check row 3 again, this time just the payment_made cell goes blue, not the whole row
It looks like could be something to do with the OnDrawColumnCell() not being called for every cell.
Am I doing this the right way or have I missed something?
I've looked at this post and several other related posts but it appears I am doing it right (although obviously not)
No other processing is done when the field is clicked as it's all done afterwards in a loop that deals with each checked row in turn.
Incidentally, I use
if MyQuery1.FieldByName('payment_made').AsBoolean = true then
simply to make the code more readable. I realise that the = true is unnecessary and I can live with the extra few microseconds this might add.
Minimal Reproducible example (written in Delphi 2009 and using TSMDBgrid from SM components (although problem seem to exist with the TDGGrid as well)
The .dfm
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 553
ClientWidth = 640
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 344
Top = 56
Width = 61
Height = 13
Caption = '<--- TDBGrid'
end
object Label2: TLabel
Left = 344
Top = 165
Width = 75
Height = 13
Caption = '<--- TSMDBGrid'
end
object DBGrid1: TDBGrid
Left = 8
Top = 16
Width = 321
Height = 97
TabOrder = 0
TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clWindowText
TitleFont.Height = -11
TitleFont.Name = 'Tahoma'
TitleFont.Style = []
end
object SMDBGrid1: TSMDBGrid
Left = 8
Top = 137
Width = 321
Height = 104
Options = [dgEditing, dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgConfirmDelete, dgCancelOnExit]
TabOrder = 1
TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clWindowText
TitleFont.Height = -11
TitleFont.Name = 'Tahoma'
TitleFont.Style = []
Flat = False
BandsFont.Charset = DEFAULT_CHARSET
BandsFont.Color = clWindowText
BandsFont.Height = -11
BandsFont.Name = 'Tahoma'
BandsFont.Style = []
Groupings = <>
GridStyle.Style = gsCustom
GridStyle.OddColor = clWindow
GridStyle.EvenColor = clWindow
TitleHeight.PixelCount = 24
FooterColor = clBtnFace
ExOptions = [eoBooleanAsCheckBox, eoENTERlikeTAB, eoKeepSelection, eoStandardPopup, eoBLOBEditor, eoTitleWordWrap, eoFilterAutoApply]
RegistryKey = 'Software\Scalabium'
RegistrySection = 'SMDBGrid'
WidthOfIndicator = 11
DefaultRowHeight = 24
ScrollBars = ssHorizontal
end
object btnSetTrue: TButton
Left = 480
Top = 111
Width = 75
Height = 25
Caption = 'btnSetTrue'
TabOrder = 2
OnClick = btnSetTrueClick
end
object btnSetFalse: TButton
Left = 480
Top = 160
Width = 75
Height = 25
Caption = 'btnSetFalse'
TabOrder = 3
OnClick = btnSetFalseClick
end
object Memo1: TMemo
Left = 8
Top = 304
Width = 609
Height = 241
Lines.Strings = (
'Top grid is TDBGrid, botton grid is TSMDBGrid'
'Both use DataSource1 as their datasource'
'Both have the same code for their OnDBGrid1DrawColumnCell'
'When the field '#39'payment_made'#39' is TRUE the whole row should be bl' +
'ue, when it is false the whole rowq should be white'
''
'Correct operation'
'=========='
''
'Change the value of the boolean field using the buttons. '
'This operates correctly, the whole row in each grid changes to b' +
'lue or whire correctly. '
'problem evident.'
''
'To reproduce the problem'
'==============='
''
'Change the boolean field from true to false or vice versa using ' +
'either the check box in SMDBGrid or by typing true '
'or false in the DBGrid.'
'Sometimes only one cell on the row changes to the correct colour' +
', leaving other cells on the row the wrong colour.')
TabOrder = 4
end
object ClientDataSet1: TClientDataSet
Aggregates = <>
Params = <>
Left = 376
Top = 240
end
object DataSource1: TDataSource
Left = 448
Top = 240
end
end
The .pas
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, SMDBGrid, Grids, DBGrids, DB, DBClient;
type
TForm1 = class(TForm)
ClientDataSet1: TClientDataSet;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
SMDBGrid1: TSMDBGrid;
btnSetTrue: TButton;
btnSetFalse: TButton;
Memo1: TMemo;
Label1: TLabel;
Label2: TLabel;
procedure FormShow(Sender: TObject);
procedure DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
procedure SMDBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
procedure btnSetTrueClick(Sender: TObject);
procedure btnSetFalseClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnSetFalseClick(Sender: TObject);
begin
ClientDataSet1.FieldByName('payment_made').AsBoolean := false;
SMDBGrid1.Refresh ;
DBGrid1.refresh
end;
procedure TForm1.btnSetTrueClick(Sender: TObject);
begin
ClientDataSet1.FieldByName('payment_made').AsBoolean := true;
SMDBGrid1.Refresh ;
DBGrid1.refresh
end;
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
if ClientDataSet1.FieldByName('payment_made').AsBoolean = true then
DBGrid1.Canvas.Brush.Color := $00ffff99//pale blue
else
DBGrid1.Canvas.Brush.Color := clwhite;
DBGrid1.DefaultDrawColumnCell (Rect, DataCol, Column, State);
end;
procedure TForm1.FormShow(Sender: TObject);
begin
//define dataset fields
ClientDataSet1.FieldDefs.Add('payment_made', ftBoolean);
ClientDataSet1.FieldDefs.Add('second_column', ftString, 10);
ClientDataSet1.CreateDataSet;
ClientDataSet1.LogChanges := False;
//put in one row of data
ClientDataSet1.Append; // insertfirst row
ClientDataSet1.FieldByName('payment_made').AsBoolean := false;
ClientDataSet1.FieldByName('second_column').AsString := 'row one';
ClientDataSet1.Post;
//leave it in editing mode (although this doesn't seem to make any difference)
ClientDataSet1.Edit;
//set option for SMDBgrid to display booleans as a checkbox
SMDBGrid1.ExOptions := SMDBGrid1.ExOptions + [eoBooleanAsCheckBox] ;
//link components together
DataSource1.DataSet := ClientDataSet1;
SMDBGrid1.DataSource := DataSource1;
DBGrid1.DataSource := DataSource1;
//point the grids to their respective OnDrawColumnCell routines
DBGrid1.OnDrawColumnCell := DBGrid1DrawColumnCell ;
SMDBGrid1.OnDrawColumnCell := SMDBGrid1DrawColumnCell ;
end;
procedure TForm1.SMDBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
if ClientDataSet1.FieldByName('payment_made').AsBoolean = true then
SMDBGrid1.Canvas.Brush.Color := $00ffff99//pale blue
else
SMDBGrid1.Canvas.Brush.Color := clwhite;
SMDBGrid1.DefaultDrawColumnCell (Rect, DataCol, Column, State);
end;
end.

Related

How to simulate ROWSELECT when selecting a ListItem in an OwnerDrawn TListView.OnDrawItem event handler?

In a 32-bit VCL Application in Windows 10 in Delphi 11 Alexandria, I select a ListItem in the OwnerDrawn TListView.OnDrawItem event handler and I want the ENTIRE UNINTERRUPTED row to be selected. Unfortunately, not the entire row gets selected, but only the caption-text portion of the row gets selected:
This is what I need to achieve:
This is the code of the form-unit:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls;
type
TForm1 = class(TForm)
ListView1: TListView;
Edit1: TEdit;
procedure ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect; State: TOwnerDrawState);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
//uses
//CodeSiteLogging,
//Generics.Collections,
//System.StrUtils,
//Vcl.Themes;
{$R *.dfm}
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect; State: TOwnerDrawState);
const
Alignments: array[TAlignment] of TTextFormats = (tfLeft, tfRight, tfCenter);
procedure SetCanvasColors(const aBrushColor, aFontColor: TColor);
begin
(Sender as TListView).Canvas.Brush.Color := aBrushColor;
(Sender as TListView).Canvas.Font.Color := aFontColor;
end;
begin
if not Assigned(Item) then EXIT;
var SelectionColor := clYellow;
if Edit1.Text = '' then
begin
/// Draw normal Item Columns:
var LV := Sender as TListView;
LV.Canvas.Brush.Style := bsSolid;
LV.Canvas.FillRect(Rect);
var x1 := 0;
var x2 := 0;
var RR := Rect;
var SS: string;
LV.Canvas.Brush.Style := bsClear;
for var i := 0 to 1 do
begin
Inc(x2, LV.Columns[i].Width);
RR.Left := x1;
RR.Right := x2;
if i = 0 then
SS := Item.Caption
else
begin
SS := Item.SubItems[i - 1];
end;
SS := #32 + SS;
if ([odSelected, odHotLight] * State <> []) then
SetCanvasColors(SelectionColor, clWindowText)
else
SetCanvasColors(clWindow, clWindowText);
LV.Canvas.TextRect(RR, SS, [tfSingleLine, Alignments[LV.Columns[i].Alignment], tfVerticalCenter]);
x1 := x2;
end;
end;
// code removed that is not relevant for this question...
end;
end.
And this is the code of the form DFM file:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 191
ClientWidth = 545
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Segoe UI'
Font.Style = []
Position = poScreenCenter
PixelsPerInch = 96
TextHeight = 17
object ListView1: TListView
Tag = -1
Left = 0
Top = 25
Width = 545
Height = 166
Align = alClient
Columns = <
item
AutoSize = True
end
item
Width = 100
end>
Items.ItemData = {
05CA0100000400000000000000FFFFFFFFFFFFFFFF01000000FFFFFFFF000000
001654006F006D00200068006100720076006500730074006500640020003300
20006100700070006C00650073000566007200750069007400E09FD791000000
00FFFFFFFFFFFFFFFF01000000FFFFFFFF00000000194A006500720072007900
200069006E0068006500720069007400650064002000350020006F0072006100
6E006700650073000566007200750069007400D0BFD79100000000FFFFFFFFFF
FFFFFF01000000FFFFFFFF000000002454006800650020006200610062007900
2000680061007300200065006100740065006E00200073006F006D0065002000
7300740072006100770062006500720072006900650073000566007200750069
00740068D2D79100000000FFFFFFFFFFFFFFFF01000000FFFFFFFF000000003D
530061006C006C0079002000770061006E0074007300200074006F0020006200
61006B006500200061002000630061006B006500200077006900740068002000
660069007600650020006100700070006C0065007300200061006E0064002000
7400680072006500650020006F00720061006E0067006500730004630061006B
00650060F0D791FFFFFFFFFFFFFFFF}
OwnerDraw = True
ReadOnly = True
RowSelect = True
TabOrder = 0
ViewStyle = vsReport
OnDrawItem = ListView1DrawItem
end
object Edit1: TEdit
AlignWithMargins = True
Left = 33
Top = 0
Width = 479
Height = 25
Margins.Left = 33
Margins.Top = 0
Margins.Right = 33
Margins.Bottom = 0
Align = alTop
TabOrder = 1
Visible = False
end
end
The issue seems to be that you partly think about declarative programming, when in fact Delphi is entirely imperative.
If you want the background to be a single, blue rectangle, you have to write a code of line that draws a single, blue rectangle.
Since you want this to be the background, on top of which the text should be printed, you need to put this line before the text-drawing commands.
Here's a simple example:
Create a new VCL app and add a TListView to the main form. As always, set DoubleBuffered to True. In this case, I set Align = alClient, in which case you are aesthetically obliged to also set Border = bsNone.
Add columns and data.
Then, to make it owner drawn, set OwnerDraw = True.
Then add the following OnDrawItem handler:
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
Rect: TRect; State: TOwnerDrawState);
begin
if Sender <> ListView1 then
Exit;
// Draw the background
if odSelected in State then
begin
ListView1.Canvas.Brush.Color := clHighlight;
ListView1.Canvas.Font.Color := clHighlightText;
end
else
begin
ListView1.Canvas.Brush.Color := clWindow;
ListView1.Canvas.Font.Color := clWindowtext;
end;
ListView1.Canvas.FillRect(Rect);
// Draw each column
var x := 0;
for var i := 0 to ListView1.Columns.Count - 1 do
begin
var S := '';
if i = 0 then
S := Item.Caption
else
S := Item.SubItems[i - 1];
S := #32 + S; // padding happens to equal width of a single space
var W := ListView1.Columns[i].Width;
var R := TRect.Create(x, Rect.Top, x + W, Rect.Bottom);
ListView1.Canvas.TextRect(R, S, [tfSingleLine, tfVerticalCenter, tfEndEllipsis]);
Inc(x, W);
end;
end;
Result:
Please note that this simple example has a serious bug, since it doesn't support a non-zero position of the horizontal scroll bar. This can be fixed very easily, almost trivially. (How?)
In addition, in a real scenario, you would also implement the focus rectangle and the mouse hover effect.

Why do hidden forms and controls stop painting correctly after switching themes twice?

I hate the title of this question. Anyway:
If you call TForm.Show with a custom theme (Windows10 Dark in this case), then close that form, then change the theme to the system Windows theme, then change back to the Windows10 Dark theme, and finally call TForm.Show on that form again, the border renders incorrectly and certain controls do not render properly, like a TComboBox.
I have a test project below, and a "fix" of sorts. But I do not like my fix and the reason for this question is that I do not really understand what is happening here that causes the form to render incorrectly only if it was hidden while the theme changed, and only if the theme is changed away from, and then back to, Windows10 Dark.
My fix is to track the theme change. If the condition I describe above occurs, I intercept the CM_SHOWINGCHANGED message, ignore it, then force the window to be recreated and then process the inherited CM_SHOWINGCHANGED the next time around. It is a very brittle fix and obviously not the way to go, so I am hoping someone can show me what is actually happening so I can fix it "for real."
Incidentally, I have submitted this as a bug to Embarcadero already. https://quality.embarcadero.com/browse/RSP-33977
Here is the test code. You'll need to add Windows10 Dark to the application's styles, obviously.
unit Unit22;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Unit23, Vcl.Themes;
type
TForm22 = class(TForm)
Panel1: TPanel;
ComboBox1: TComboBox;
RadioGroup1: TRadioGroup;
ButtonShow: TButton;
Memo1: TMemo;
procedure ButtonShowClick(Sender: TObject);
procedure RadioGroup1Click(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{ Private declarations }
FAllowChange: Boolean;
public
{ Public declarations }
end;
var
Form22: TForm22;
implementation
{$R *.dfm}
procedure TForm22.ButtonShowClick(Sender: TObject);
begin
Form23.Show;
end;
procedure TForm22.FormShow(Sender: TObject);
begin
if StyleServices.Name = 'Windows10 Dark' then
RadioGroup1.ItemIndex := 1
else
RadioGroup1.ItemIndex := 0;
FAllowChange := True;
end;
procedure TForm22.RadioGroup1Click(Sender: TObject);
begin
if not FAllowChange then
exit;
if RadioGroup1.ItemIndex = 0 then
TStyleManager.SetStyle('Windows');
if RadioGroup1.ItemIndex = 1 then
TStyleManager.SetStyle('Windows10 Dark');
end;
end.
Unit 22 DPR:
object Form22: TForm22
Left = 0
Top = 0
ActiveControl = Memo1
Caption = 'Form22'
ClientHeight = 305
ClientWidth = 511
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 511
Height = 305
Align = alClient
BevelEdges = []
BevelOuter = bvNone
Caption = 'Panel1'
ShowCaption = False
TabOrder = 0
object ComboBox1: TComboBox
Left = 16
Top = 8
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 0
TabOrder = 0
Text = 'one'
Items.Strings = (
'one'
'two'
'three')
end
object RadioGroup1: TRadioGroup
Left = 16
Top = 48
Width = 185
Height = 105
Caption = 'RadioGroup1'
Items.Strings = (
'windows'
'dark')
TabOrder = 1
OnClick = RadioGroup1Click
end
object ButtonShow: TButton
Left = 16
Top = 159
Width = 75
Height = 25
Caption = 'ButtonShow'
TabOrder = 2
OnClick = ButtonShowClick
end
object Memo1: TMemo
Left = 207
Top = 8
Width = 274
Height = 281
Lines.Strings = (
'Always start in dark.'
''
'Steps to reproduce:'
'1.'#9'Click ButtonShow.'
'2.'#9'Close the window that opened.'
'3.'#9'Click Windows (change to system them).'
'4.'#9'Click Dark (change back to dark VCL style).'
'5.'#9'Click ButtonShow again. The controls are '
'not properly painted. Combobox text is black and form '
'is wrong until resize.'
''
'Hacky fix:'
'1.'#9'Click ButtonShow.'
'2.'#9'Check the '#8220'Fix'#8221' button in the window that '
'opened, then close it.'
'3.'#9'Click Windows (change to system)'
'4.'#9'Click Dark (change back to vcl dark)'
'5.'#9'Click ButtonShow. See comments in source.'
'')
ReadOnly = True
TabOrder = 3
end
end
end
Unit23:
unit Unit23;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Themes;
type
TForm23 = class(TForm)
Panel1: TPanel;
ComboBox1: TComboBox;
ComboBox2: TComboBox;
ComboBox3: TComboBox;
Button1: TButton;
CheckBox1: TCheckBox;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
FFixing: Boolean;
FNeedFix: String;
FShowedStyle: String;
protected
procedure DoShow; override;
public
{ Public declarations }
procedure CMStyleChanged(var Message: TMessage); message CM_STYLECHANGED;
procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
end;
var
Form23: TForm23;
implementation
{$R *.dfm}
procedure TForm23.Button1Click(Sender: TObject);
begin
PostMessage(Handle, CM_RECREATEWND, 0, 0);
end;
procedure TForm23.CMShowingChanged(var Message: TMessage);
var
DoFix: Boolean;
begin
if not Showing then
inherited
else
begin
// if the theme changed away from dark, then back to dark, while we were
// not visible, then we need to force the window to be recreated again
// before showing.
// This is a really bad hack but basically I am just preventing the
// normal response to CMShowingChanged and then setting up a message
// queue that will recreate the window and then process the CM_SHOWINGCHANGED
// message again. This will probably break the universe but it appears to work
// in this test.
FShowedStyle := StyleServices.Name;
Panel1.Caption := FShowedStyle;
DoFix := not FFixing and (FNeedFix <> '') and (FNeedFix = FShowedStyle);
FNeedFix := '';
if DoFix and CheckBox1.Checked then
begin
FFixing := True;
// SendMessage(Handle, WM_SETREDRAW, Winapi.Windows.WPARAM(LongBool(False)), 0);
PostMessage(Handle, CM_RECREATEWND, 0, 0);
// PostMessage(Handle, CM_SHOWINGCHANGED, Message.WParam, Message.LParam);
// do not allow inherited.
end else
begin
FFixing := False;
inherited;
end;
end;
end;
procedure TForm23.CMStyleChanged(var Message: TMessage);
begin
FNeedFix := FShowedStyle;
inherited;
end;
procedure TForm23.DoShow;
var
DoFix: Boolean;
begin
inherited;
exit;
end;
end.
Unit23 DPR:
object Form23: TForm23
Left = 0
Top = 0
Caption = 'Form23'
ClientHeight = 253
ClientWidth = 360
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 360
Height = 253
Align = alClient
Alignment = taRightJustify
BevelEdges = []
BevelOuter = bvNone
Caption = 'Panel1'
TabOrder = 0
object ComboBox1: TComboBox
Left = 16
Top = 32
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 0
TabOrder = 0
Text = 'one'
Items.Strings = (
'one'
'two'
'three')
end
object ComboBox2: TComboBox
Left = 16
Top = 59
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 1
TabOrder = 1
Text = 'two'
Items.Strings = (
'one'
'two'
'three')
end
object ComboBox3: TComboBox
Left = 16
Top = 86
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 2
TabOrder = 2
Text = 'three'
Items.Strings = (
'one'
'two'
'three')
end
object Button1: TButton
Left = 16
Top = 136
Width = 75
Height = 25
Caption = 'RecreateWnd'
TabOrder = 3
OnClick = Button1Click
end
object CheckBox1: TCheckBox
Left = 16
Top = 167
Width = 273
Height = 17
Caption = 'Fix with CM_SHOWINGCHANGED hack'
TabOrder = 4
end
end
end

Vertically drawn text on TCanvas not visible when drawn from initial FormResize

I'm using the DrawTextRotatedB function from Josef Švejk's excellent answer to the question How to draw text in a canvas vertical + horizontal with Delphi 10.2
to draw text vertically on a TPanel (full code below, Win 32 program).
This is done in a message handler (message WM_DRAWTEXT). The PostMessage call is in the FormResize (which in the real program does a lot more).
Issue:
FormResize is called from FormShow, all relevant code is executed, but the vertical text does not show.
If I then resize the form, the same code gets executed again and it is visible.
How can this be, and how to fix it?
Structure view of form components (drawing on PnlLeftLeft):
Full test code below. Note that this logs to a text file specified by the cLogFileName constant at the top. This log contains (initial resize + one subsequent resize).
FormShow start
FormShow end
FormResize start
PostMessage left sent
FormResize end
RedrawMessage left: test text
(X,Y): (51,284)
RedrawMessage ends
FormResize start
PostMessage left sent
FormResize end
RedrawMessage left: test text
(X,Y): (51,285)
RedrawMessage ends
uFrmTest.Pas
unit uFrmTest;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Imaging.pngimage, Vcl.ExtCtrls, System.UITypes;
const
WM_DRAWTEXT = WM_USER + 100;
cLogFileName = 'd:\temp\log.lst';
type
TFrmTest = class(TForm)
PnlClient: TPanel;
PnlLeft: TPanel;
PnlRight: TPanel;
PnlLeftLeft: TPanel;
procedure FormShow(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormResize(Sender: TObject);
private
FTextFile: TextFile; // Debugging do cLogFileName
procedure RedrawMessage(var Msg: TMessage); message WM_DRAWTEXT;
public
end;
var
FrmTest: TFrmTest;
implementation
{$R *.dfm}
procedure DrawTextRotated(ACanvas: TCanvas; Angle, X, Y: Integer; AText: String);
// DrawTextRotatedB from https://stackoverflow.com/a/52923681/512728
var
Escapement: Integer;
LogFont: TLogFont;
NewFontHandle: HFONT;
OldFontHandle: HFONT;
begin
if not Assigned(ACanvas) then
Exit;
// Get handle of font and prepare escapement
GetObject(ACanvas.Font.Handle, SizeOf(LogFont), #LogFont);
while Angle > 360 do Angle := Angle - 360;
while Angle < -360 do Angle := Angle + 360;
Escapement := Angle * 10;
// We must initialise all fields of the record structure
LogFont.lfWidth := 0;
LogFont.lfHeight := ACanvas.Font.Height;
LogFont.lfEscapement := Escapement;
LogFont.lfOrientation := 0;
if fsBold in ACanvas.Font.Style then
LogFont.lfWeight := FW_BOLD
else
LogFont.lfWeight := FW_NORMAL;
LogFont.lfItalic := Byte(fsItalic in ACanvas.Font.Style);
LogFont.lfUnderline := Byte(fsUnderline in ACanvas.Font.Style);
LogFont.lfStrikeOut := Byte(fsStrikeOut in ACanvas.Font.Style);
LogFont.lfCharSet := ACanvas.Font.Charset;
LogFont.lfOutPrecision := OUT_DEFAULT_PRECIS;
LogFont.lfClipPrecision := CLIP_DEFAULT_PRECIS;
LogFont.lfQuality := DEFAULT_QUALITY;
LogFont.lfPitchAndFamily := DEFAULT_PITCH;
StrPCopy(LogFont.lfFaceName, ACanvas.Font.Name);
// Create new font with rotation
NewFontHandle := CreateFontIndirect(LogFont);
try
// Select the new font into the canvas
OldFontHandle := SelectObject(ACanvas.Handle, NewFontHandle);
try
// Output result
ACanvas.Brush.Style := VCL.Graphics.bsClear;
try
ACanvas.TextOut(X, Y, AText);
finally
ACanvas.Brush.Style := VCL.Graphics.bsSolid;
end;
finally
// Restore font handle
NewFontHandle := SelectObject(ACanvas.Handle, OldFontHandle);
end;
finally
// Delete the deselected font object
DeleteObject(NewFontHandle);
end;
end;
procedure TFrmTest.FormCreate(Sender: TObject);
begin
AssignFile(FTextFile,cLogFileName);
Rewrite(FTextFile);
end;
procedure TFrmTest.FormDestroy(Sender: TObject);
begin
CloseFile(FTextFile);
end;
procedure TFrmTest.FormResize(Sender: TObject);
begin
WriteLn(FTextFile,'FormResize start');
PostMessage(Handle,WM_DRAWTEXT,0,0);
WriteLn(FTextFile,'PostMessage left sent');
WriteLn(FTextFile,'FormResize end');
end;
procedure TFrmTest.FormShow(Sender: TObject);
begin
WriteLn(FTextFile,'FormShow start');
WriteLn(FTextFile,'FormShow end');
end;
type
THackPanel = class(TPanel);
procedure TFrmTest.RedrawMessage(var Msg: TMessage);
const cLeftVertText = 'test text';
var lHorDrawOffset, lVertDrawOffset: Integer;
begin
WriteLn(FTextFile,'RedrawMessage left: ' + cLeftVertText);
THackPanel(PnlLeftLeft).Canvas.Font := PnlLeftLeft.Font;
lVertDrawOffset := (PnlLeftLeft.Height - THackPanel(PnlLeftLeft).Canvas.TextHeight(cLeftVertText)) DIV 2;
lHorDrawOffset := (PnlLeftLeft.Width - THackPanel(PnlLeftLeft).Canvas.TextWidth(cLeftVertText)) DIV 2;
DrawTextRotated(THackPanel(PnlLeftLeft).Canvas , 90, lHorDrawOffset, lVertDrawOffset, cLeftVertText);
WriteLn(FTextFile,Format('(X,Y): (%d,%d)',[lHorDrawOffset,lVertDrawOffset]));
WriteLn(FTextFile,'RedrawMessage ends');
WriteLn(FTextFile,'');
end;
end.
uFrmTest.dfm
object FrmTest: TFrmTest
Left = 0
Top = 0
Caption = 'FrmTest'
ClientHeight = 592
ClientWidth = 905
Color = clWhite
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
OnResize = FormResize
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
object PnlClient: TPanel
Left = 0
Top = 0
Width = 905
Height = 592
Align = alClient
BevelOuter = bvNone
ParentColor = True
TabOrder = 0
ExplicitLeft = 88
ExplicitTop = 8
object PnlLeft: TPanel
AlignWithMargins = True
Left = 20
Top = 10
Width = 367
Height = 582
Margins.Left = 20
Margins.Top = 10
Margins.Right = 20
Margins.Bottom = 0
Align = alLeft
BevelOuter = bvNone
ParentColor = True
TabOrder = 0
object PnlLeftLeft: TPanel
Tag = -20
Left = 0
Top = 0
Width = 145
Height = 582
Align = alLeft
BevelOuter = bvNone
ParentColor = True
TabOrder = 0
end
end
object PnlRight: TPanel
AlignWithMargins = True
Left = 427
Top = 10
Width = 458
Height = 582
Margins.Left = 20
Margins.Top = 10
Margins.Right = 20
Margins.Bottom = 0
Align = alClient
BevelOuter = bvNone
TabOrder = 1
end
end
end

Tchart change labels [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I need a little help on changing the labels shown on a tchart
I use this code to populate the tchart:
With Series1 do
begin
clear;
Add (v[1], 'abcdef', clRed);
Add (v[2], 'aaaaaaaaaaaaaaaaaaaaaaaa', clBlue);
Add (v[3], 'bbbbbbbbbbbbbb', clGreen);
end;
I get this chart:
image of chart
Please help me on changing the labels above the rectangles from the text shown in the image to some other variables via code; also please tell me what can i do to show long texts on the x axis and how to break it on multiple lines
First, I'll assume that your "v" variable was an array of integer; My example it's named "BarValue". The form has a TChart with a 2D Bar Series added.
When you construct the labels, you'll want to replace the spaces and word-breaks with the "TeeLineSeparator" and then maybe rescan them and insert in a "TeeLineSeparator" into much longer words to force them to line-break. (In my example I just broke the long "aaaaaaaaa..." label into words randomly.)
This example worked in the Delphi 10.2 Tokyo and Delphi 2007.
type
TForm1 = class(TForm)
Chart1: TChart;
Series1: TBarSeries;
procedure FormCreate(Sender: TObject);
procedure Series1GetMarkText(Sender: TChartSeries; ValueIndex: Integer; var MarkText: string);
private
BarMark : array[1..3] of string;
BarValue : array[1..3] of integer;
BarLabel : array[1..3] of string;
BarColor : array[1..3] of TColor;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i : integer;
begin
Chart1.Legend.Visible := false;
// This maybe needed on earlier versions of TChart (e.g. Delphi 2007-)
// to make room for multi-line labels
//Chart1.Axes.Bottom.LabelSize := 32;
Series1.OnGetMarkText := Series1GetMarkText;
BarValue[1] := 100;
BarValue[2] := 200;
BarValue[3] := 300;
BarLabel[1] := 'abcdefg';
BarLabel[2] := 'aaaa'+TeeLineSeparator+'aaaaaaaaa'+TeeLineSeparator+'aaaaaaaaaaa';
BarLabel[3] := 'bbbbbbbbbbbbbb';
BarColor[1] := clRed;
BarColor[2] := clBlue;
BarColor[3] := clGreen;
BarMark[1] := 'ok1';
BarMark[2] := 'ok2';
BarMark[3] := 'ok3';
Series1.Clear;
for i := 1 to 3 do
Series1.Add (BarValue[i], BarLabel[i], BarColor[i]);
end;
procedure TForm1.Series1GetMarkText(Sender: TChartSeries; ValueIndex: Integer; var MarkText: string);
begin
MarkText := BarMark[ValueIndex+1];
end;
The DFM:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 374
ClientWidth = 702
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Chart1: TChart
Left = 204
Top = 40
Width = 400
Height = 250
Title.Text.Strings = (
'TChart')
View3D = False
TabOrder = 0
DefaultCanvas = 'TGDIPlusCanvas'
ColorPaletteIndex = 13
object Series1: TBarSeries
OnGetMarkText = Series1GetMarkText
XValues.Name = 'X'
XValues.Order = loAscending
YValues.Name = 'Bar'
YValues.Order = loNone
end
end
end

Panel being shown under grid scrollbar

I'm using windows 10 theme in my project, and i've have noticed that: Panels that are positioned on edges of grids, they're shown under the grid scrollbar,
like this image:
I haven't changed any behavior of the VCL, or the grid or scroll behavior.
pas file:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Vcl.StdCtrls,
Datasnap.DBClient, Vcl.Grids, Vcl.DBGrids, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Panel1: TPanel;
DbGrid: TDBGrid;
Panel2: TPanel;
ClientDataSet: TClientDataSet;
DataSource1: TDataSource;
ButtonAdd: TButton;
ShowPanel: TButton;
ClientDataSetname: TStringField;
ClientDataSetaddress: TStringField;
procedure ButtonAddClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ShowPanelClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ButtonAddClick(Sender: TObject);
begin
ClientDataset.Append;
ClientDataSetname.AsString := 'Test name';
ClientDataSetaddress.AsString := 'Test address';
ClientDataset.Insert;
end;
procedure TForm1.ShowPanelClick(Sender: TObject);
begin
if Panel2.Visible then
Panel2.Visible := False
else
Panel2.Visible := True;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ClientDataset.CreateDataSet;
end;
end.
dfm file:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 201
ClientWidth = 555
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 460
Top = 0
Width = 95
Height = 201
Align = alRight
TabOrder = 0
object ButtonAdd: TButton
Left = 10
Top = 16
Width = 75
Height = 25
Caption = 'ButtonAdd'
TabOrder = 0
OnClick = ButtonAddClick
end
object ShowPanel: TButton
Left = 10
Top = 47
Width = 75
Height = 25
Caption = 'ShowPanel'
TabOrder = 1
OnClick = ShowPanelClick
end
end
object DbGrid: TDBGrid
Left = 0
Top = 0
Width = 460
Height = 201
Align = alClient
DataSource = DataSource1
TabOrder = 1
TitleFont.Charset = DEFAULT_CHARSET
TitleFont.Color = clWindowText
TitleFont.Height = -11
TitleFont.Name = 'Tahoma'
TitleFont.Style = []
Columns = <
item
Expanded = False
FieldName = 'name'
Visible = True
end
item
Expanded = False
FieldName = 'address'
Visible = True
end>
end
object Panel2: TPanel
Left = 0
Top = 160
Width = 185
Height = 41
Caption = 'panel2'
TabOrder = 2
Visible = False
end
object ClientDataSet: TClientDataSet
Aggregates = <>
Params = <>
Left = 216
Top = 104
object ClientDataSetname: TStringField
FieldName = 'name'
Size = 50
end
object ClientDataSetaddress: TStringField
FieldName = 'address'
Size = 50
end
end
object DataSource1: TDataSource
DataSet = ClientDataSet
Left = 152
Top = 88
end
end
The bug happens after second click on ShowPanel.
You can Define DBgrid as parent of your panel.
procedure TForm1.FormShow(Sender: TObject);
begin
panel1.Parent := dbgrid1;
panel1.align := alBottom;
end;
I've found a way to "solve" this problem just changing grid seBorder to false, i used Notepad++ to locate in all project for ": TDBGrid"¹ and replace to ": TDBGrid StyleElements = [seFont, seClient]"¹. Isn't the better way to solve this problem i think, because changing seBorder to false will make the scrollbars style looks like the scrolls of your windows version.
¹ Ignore the quotes when you try to do it.
In fact, the only way is to work with the parent of the DBgrid.
This example work fine for me :
procedure TForm1.adjustPanelTo(const aPanel: TPanel; aWcontrol: TWinControl);
begin
if aPanel = nil then Exit;
if aWcontrol = nil then Exit;
if aWcontrol.Parent = nil then Exit;
aPanel.Parent := aWcontrol.Parent;
aPanel.Anchors := [akLeft, akTop];
aPanel.Left := aWcontrol.Left + 1;
aPanel.Top := aWcontrol.Top + aWcontrol.ClientHeight - aPanel.Height;
aPanel.BringToFront;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
adjustPanelTo(Panel1, DBGrid1);
end;

Resources