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
Related
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.
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
(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.
How do you loop through a cxgrids records? i.e. make delphi program go through/check each record in the cxgrid from top to bottom.
I have a cxgrid which is displaying records from a tadquery which is talking to a database table if this helps in anyway.
Sample code to do this for a TcxGridDBTableView in a TcxGrid, and also to iterate an DataSet below. Both of these samples will work regardless of whether the DataSet is filtered or not.
The sample for the TcxGrid assumes you've pulled the grid from the palette, added a TcxDBTableView to it, added the dataset's columns to it and left all grid properties in the Object Inspector at their defaults, except the KeyFieldNames of the TableView, which needs setting to the DataSet's primary key, in my case "FilesID". This is so as to be able to identify the dataset record for a given row in the TableView - you obtain the key value, ID, for the row like so:
ID := cxGrid1DBTableView1.DataController.GetRecordId(TopRowIndex + Row);
The ID value is then used by the call to CDS1.Locate() to retrieve the record.
The TBookmark is used to note the current CDS record before the operation and to return to it afterwards. The calls to DisableControls and EnableControls are to prevent the cxGrid (and any other DB-aware controls connected to the CDS) from being changed while the operation is in progress.
procedure TForm1.IterateVisibleGridRows;
var
BM : TBookmark;
TopRowIndex : Integer;
VisibleCount : Integer;
Row : Integer;
ID : Integer;
Controller : TcxGridTableController;
ViewInfo : TcxGridTableViewInfo;
begin
BM := CDS1.GetBookmark;
try
Controller := cxGrid1DBTableView1.Controller;
ViewInfo := TcxGridTableViewInfo(Controller.ViewInfo);
TopRowIndex := Controller.TopRowIndex;
VisibleCount := ViewInfo.RecordsViewInfo.VisibleCount;
CDS1.DisableControls;
Row := 0;
while Row < VisibleCount do begin
ID := cxGrid1DBTableView1.DataController.GetRecordId(TopRowIndex + Row);
if CDS1.Locate('FilesID', ID, []) then begin
// Do what you want here
end;
Inc(Row);
end;
finally
CDS1.GotoBookmark(BM);
CDS1.FreeBookmark(BM);
CDS1.EnableControls;
end;
end;
Btw, I know this is not what you asked, but if you want to iterate a dataset without doing it using a TcxGrid, it's actually much simpler:
procedure IterateDataSetRows(DataSet : TDataSet);
var
BM : TBookmark;
begin
BM := CDS1.GetBookmark;
try
// delete the following 2 lines and the one in the finally block if you don't want a "Wait" cursor
Screen.Cursor := crSqlWait;
Screen.ActiveForm.Update;
DataSet.DisableControls;
DataSet.First;
while not DataSet.Eof do begin
// Do what you want here
DataSet.Next;
end;
finally
DataSet.GotoBookmark(BM);
DataSet.FreeBookmark(BM);
DataSet.EnableControls;
Screen.Cursor := crDefault;
end;
end;
You didn't say where it was all records in your grid or just the visible ones:
Any how heres is an example for doing both:
This example uses a form with a cxGrid, cxButton and a cxMemo. Plus a dxMemdataset
Here is the DFM code:
object Form20: TForm20
Left = 0
Top = 0
Caption = 'Form20'
ClientHeight = 299
ClientWidth = 462
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
DesignSize = (
462
299)
PixelsPerInch = 96
TextHeight = 13
object cxGrid1: TcxGrid
Left = 0
Top = 0
Width = 299
Height = 299
Align = alLeft
TabOrder = 0
ExplicitHeight = 635
object cxGrid1DBTableView1: TcxGridDBTableView
Navigator.Buttons.CustomButtons = <>
DataController.DataSource = DataSource1
DataController.Summary.DefaultGroupSummaryItems = <>
DataController.Summary.FooterSummaryItems = <>
DataController.Summary.SummaryGroups = <>
object cxGrid1DBTableView1RecId: TcxGridDBColumn
DataBinding.FieldName = 'RecId'
Visible = False
end
object cxGrid1DBTableView1Field1: TcxGridDBColumn
DataBinding.FieldName = 'Field1'
end
object cxGrid1DBTableView1Field2: TcxGridDBColumn
DataBinding.FieldName = 'Field2'
end
end
object cxGrid1Level1: TcxGridLevel
GridView = cxGrid1DBTableView1
end
end
object cxButton1: TcxButton
Left = 305
Top = 8
Width = 154
Height = 25
Caption = 'Do the trick'
TabOrder = 1
OnClick = cxButton1Click
end
object cxMemo1: TcxMemo
Left = 305
Top = 39
Anchors = [akLeft, akTop, akRight, akBottom]
Lines.Strings = (
'cxMemo1')
TabOrder = 2
Height = 260
Width = 154
end
object dxMemData1: TdxMemData
Indexes = <>
SortOptions = []
Left = 160
Top = 144
object dxMemData1Field1: TIntegerField
FieldName = 'Field1'
end
object dxMemData1Field2: TIntegerField
FieldName = 'Field2'
end
end
object DataSource1: TDataSource
DataSet = dxMemData1
Left = 168
Top = 152
end
end
First thing first at form create I generate some random Data:
procedure TForm20.FormCreate(Sender: TObject);
var
i: Integer;
begin
randomize;
dxMemData1.DisableControls;
try
dxMemData1.Open;
for i := 0 to 999 do
dxMemData1.AppendRecord([i, Random(500), Random(500)]);
finally
dxMemData1.EnableControls;
end;
end;
Since my grid is bound to the dataset data will show up on screen.
Here is my form definition:
type
TForm20 = class(TForm)
dxMemData1: TdxMemData;
dxMemData1Field1: TIntegerField;
dxMemData1Field2: TIntegerField;
cxGrid1DBTableView1: TcxGridDBTableView;
cxGrid1Level1: TcxGridLevel;
cxGrid1: TcxGrid;
DataSource1: TDataSource;
cxGrid1DBTableView1RecId: TcxGridDBColumn;
cxGrid1DBTableView1Field1: TcxGridDBColumn;
cxGrid1DBTableView1Field2: TcxGridDBColumn;
cxButton1: TcxButton;
cxMemo1: TcxMemo;
procedure FormCreate(Sender: TObject);
procedure cxButton1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
Then you just have to push the button:
procedure TForm20.cxButton1Click(Sender: TObject);
var
RecNo, i: Integer;
cxCustomGridRecordViewInfo: TcxCustomGridRecordViewInfo;
s: string;
begin
s := Format(
'You have' + sLineBreak +
' %d records in your Dataset' + sLineBreak +
' %d records in your grid' + sLineBreak +
' %d VISIBLE records in your grid'
, [
cxGrid1DBTableView1.DataController.RecordCount,
cxGrid1DBTableView1.DataController.FilteredRecordCount,
cxGrid1DBTableView1.ViewInfo.VisibleRecordCount
]
);
MessageDlg(s, mtInformation, [mbOK], 0);
cxMemo1.Lines.BeginUpdate;
cxMemo1.Lines.Clear;
cxMemo1.Lines.Add(' *** Filtered Records ***');
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do
begin
RecNo := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
cxMemo1.Lines.Add(cxGrid1DBTableView1.DataController.Values[RecNo, 1]);
end;
cxMemo1.Lines.Add(' *** Visible Records ***');
for i := 0 to cxGrid1DBTableView1.ViewInfo.VisibleRecordCount - 1 do
begin
cxCustomGridRecordViewInfo := cxGrid1DBTableView1.ViewInfo.RecordsViewInfo[i];
cxMemo1.Lines.Add(cxCustomGridRecordViewInfo.GridRecord.Values[1]);
end;
cxMemo1.Lines.EndUpdate;
end;
So there you see Filtered records are the one you have in your grid after possibly after a filter had been applyed. Visible Record are the onec actually visible on the screen.
I have two string lists that I'm working with. One that has a list of keywords, and then another that has a list of negative keywords. I want to be able to search through the list and pick out the list items that do not contain the negative keyword and output to a third keyword list. I was using the AnsiPos function but that found the negative keywords if they were part of a word, vs full word.
Any suggestions on a relatively easy way to do this? Speed is not that important, but would be nice.
Example of what I'm looking to do:
Keyword List:
Cat
Catfish
Fish Sticks
Dog Food
Negative Keyword List:
Fish
Returned Values Wanted:
Cat
Catfish
Dog Food
This is what I've got so far.. which does not work. I used information from: Is There An Efficient Whole Word Search Function in Delphi?
function ExistWordInString(aString: PAnsichar; aSearchString: string;
aSearchOptions: TStringSearchOptions): Boolean;
var
Size : Integer;
begin
Size := StrLen(aString);
result := SearchBuf(aString, Size, 0, 0, aSearchString, aSearchOptions) <> nil;
end;
procedure TForm2.Button1Click(Sender: TObject);
var
i, j, index: integer;
s: string;
stl: tstringlist;
begin
stl := TStringList.Create;
stl.Text := listbox1.Items.Text;
for I := 0 to stl.Count - 1 do
begin
for j := 0 to listbox2.Count - 1 do
begin
if not ExistWordInString(PAnsiChar(listbox2.Items.Strings[j]),
listbox1.Items.Strings[i], [soWholeWord, soDown])
then
listbox3.Items.Append(stl.Strings[i]);
end;
end;
end;
If spaces are the only word delimiter you need to worry about, then you can do a whole word match using AnsiPos by adding a space before and after both the keyword and the negative keyword, ie
AnsiPos(' '+SubStr+' ', ' '+Str+' ')
You'd need a loop to check every entry from the negative keyword list.
this sample code works like a charm (using Delphi 7):
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, StrUtils;
type
TForm1 = class(TForm)
Button1: TButton;
ListBox1: TListBox;
ListBox2: TListBox;
ListBox3: TListBox;
procedure Button1Click(Sender: TObject);
private
function ExistWordInString(aString, aSearchString:string;aSearchOptions: TStringSearchOptions): Boolean;
public
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
i,k: integer;
begin
for k:= 0 to ListBox2.Count -1 do
for i:= 0 to ListBox1.Count - 1 do
begin
if not ExistWordInString(ListBox1.Items[i], ListBox2.Items[k],[soWholeWord,soDown]) then
ListBox3.Items.Append(ListBox1.Items[i]);
end;
end;
function TForm1.ExistWordInString(aString, aSearchString: string; aSearchOptions: TStringSearchOptions): Boolean;
var
Size : Integer;
begin
Size:=Length(aString);
Result := SearchBuf(PChar(aString), Size, 0, 0, aSearchString, aSearchOptions)<>nil;
end;
end.
and here's the form:
object Form1: TForm1
Left = 1008
Top = 398
Width = 411
Height = 294
Caption = 'Form1'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 320
Top = 8
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object ListBox1: TListBox
Left = 8
Top = 8
Width = 177
Height = 97
ItemHeight = 13
Items.Strings = (
'Cat '
'Catfish'
'Fish Sticks'
'Dog Food')
TabOrder = 1
end
object ListBox2: TListBox
Left = 192
Top = 8
Width = 121
Height = 97
ItemHeight = 13
Items.Strings = (
'Fish')
TabOrder = 2
end
object ListBox3: TListBox
Left = 8
Top = 112
Width = 305
Height = 137
ItemHeight = 13
TabOrder = 3
end
end
hope this helps.
Reinhard :-)
I think I figured it out. Use stringlist.find('fish',index);
I didn't figure it out. .find did not work.
-Brad
You can use the SearchBuf function (see the pastacool's answer) IF you are not interested in other characters except A..Z / Unicode.
If you have an Unicode Delphi (D2009 or D2010) then you must use TCharacter.IsLetterOrDigit(aString: string; aIndex: integer): boolean; from the Character unit. A simple example for you to get the idea:
procedure TForm7.btn1Click(Sender: TObject);
var
bMatches: boolean;
begin
with rgx1 do //custom component - disregard it
begin
RegEx:=edtTextToFind.Text; //text to find
Subject:=mmoResult.Text; //text in which to search
if Match then //aha! found it!
begin
bMatches:=True;
if chkWholeWord.Checked then //be attentive from here!! - I think that's self explaining...
begin
if MatchedExpressionOffset>1 then
bMatches:=not TCharacter.IsLetterOrDigit(Subject, MatchedExpressionOffset-1);
if bMatches and (MatchedExpressionOffset+MatchedExpressionLength<=Length(Subject)) then
bMatches:=not TCharacter.IsLetterOrDigit(Subject, MatchedExpressionOffset+MatchedExpressionLength);
end;
if bMatches then //select it in the memo
begin
mmoResult.SelStart:=MatchedExpressionOffset-1;
mmoResult.SelLength:=MatchedExpressionLength;
mmoResult.SetFocus;
end
else
ShowMessage('Text not found!');
end
else
ShowMessage('Text not found!');
end;
end;
Change your function to read:
function ExistWordInString(aString:PAnsichar;
aSearchString:string;
aSearchOptions: TStringSearchOptions): Boolean;
var
b : boolean;
begin
if soWholeWord in aSearchOptions then
b := Pos(' '+Uppercase(aSearchString)+' ',' '+UpperCase(aString)+' ') > 0;
else
b := Pos(UpperCase(aSearchString),UpperCase(aString)) > 0;
Result := b;
end;
If your using Delphi 2009/2010 then change it from Pos to AnsiPos. My assumption here is that soWholeWord means that the match "Fish" would match "Fish Sticks" but not "catfish".