In a 32-bit VCL Application in Windows 10 in Delphi 11.1 Alexandria, I am trying to add multiple-column Items to TListBox. The CHM Libraries Reference for VCL in the Vcl.StdCtrls.TCustomListBox.Items topic has the following tip:
So I created the following VCL Application test project:
DPR:
program TListBoxMultiColumn;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
PAS:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
ListBox1.Items.Add('First Column'^I'Second Column');
ListBox1.Items.Add('1'^I'2');
ListBox1.Items.Add('4'^I'5');
end;
end.
DFM:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'TListBox MultiColumn Test'
ClientHeight = 191
ClientWidth = 368
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -15
Font.Name = 'Segoe UI'
Font.Style = []
Position = poScreenCenter
PixelsPerInch = 120
TextHeight = 20
object ListBox1: TListBox
Left = 0
Top = 0
Width = 241
Height = 191
Margins.Left = 4
Margins.Top = 4
Margins.Right = 4
Margins.Bottom = 4
Align = alLeft
Columns = 2
ItemHeight = 20
TabOrder = 0
ExplicitHeight = 413
end
object Button1: TButton
Left = 260
Top = 20
Width = 94
Height = 31
Margins.Left = 4
Margins.Top = 4
Margins.Right = 4
Margins.Bottom = 4
Caption = 'Button1'
TabOrder = 1
OnClick = Button1Click
end
end
However, the result is not what is promised in the documentation:
So how can I add multiple-column Items to a TListBox?
You must set the TabWidth property to a suitable, large enough value:
ListBox1.TabWidth := 100;
ListBox1.Items.Add('First Column'^I'Second Column');
ListBox1.Items.Add('1'^I'2');
ListBox1.Items.Add('4'^I'5');
Bonus information: You may wonder why ^I is used here. Well, since I is the 9th letter in the English alphabet, ^I is equal to #9, that is, the tabulator character.
I would write this
ListBox1.TabWidth := 100;
ListBox1.Items.Add('First Column'#9'Second Column');
ListBox1.Items.Add('1'#9'2');
ListBox1.Items.Add('4'#9'5');
Actually, the current version of the documentation states
Tip: If you have a list box with tab stops enabled (TabStop property) and you want to add data to specific columns, you can set the TabWidth property to obtain a list box in which individual lines can be displayed in columns, as long as they use tabs in their text, as shown in the snippet below (notice #9 is the tab character).
This is a better description, since it mentions the TabWidth property, uses #9 instead of ^I, and doesn't misuse the word "parameter". However, its reference to TabStop is utterly nonsense. The TabStop property is about the form's tab order.
Related
In Delphi 10.3.2, when I programmatically insert items in a TComboBox having "CharCase = ecLowerCase" (or ecUpperCase), I get the error
Project XXXX raise exception class EOutOfResources with message 'Unable to insert a line'
The error only appears when I add to my project the unit SHAREMEM (I need to reference that unit as I have to exchange dynamic strings with a DLL).
It seems that the error is related to this remark I found in the procedure TComboBoxStrings.Add() (unit StdCtrls.pas): From the Windows SDK documentation: Comclt32.dll version 5.0 or later: If CBS_LOWERCASE or CBS_UPPERCASE is set, the Unicode version of CB_ADDSTRING alters the string. If using read-only global memory, this causes the application to fail.
program combo_lowercase;
uses ShareMem,
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
unit Unit1;
interface
uses Forms, Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Controls, Vcl.StdCtrls;
type
TForm1 = class(TForm)
combo: TComboBox;
procedure FormCreate(Sender: TObject);
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
combo.Items.Clear;
combo.Items.Add('AAAAAAAAAAAAAA');
combo.Items.Add('bbbbbbbbbbbbbb');
combo.Items.Add('CCCCCccccccccc');
combo.Items.Add('ddddddDDDDDDDD');
end;
end.
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 206
ClientWidth = 496
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 combo: TComboBox
Left = 48
Top = 20
Width = 145
Height = 21
Style = csDropDownList
CharCase = ecLowerCase
TabOrder = 0
end
end
Is there a workaround?
Thanks in advance.
The code you show does not lead to the error that you report. The only explanation I can come up with to account for the significance of Sharemem is that its use leads to an erroneous version of borlandmm.dll being loaded. Make sure that your process loads the correct version of that DLL. Copy it from the bin directory to the same directory as your executable.
I have a TStringGrid, however if I click and drag on it, it can be panned vertically and horizontally, I don't want the user to be able to do this, how can I stop this from happening?
You can use the OnTopLeftChanged event to catch whenever any sort of "scrolling" has occurred, and decide how to proceed. If you don't want user to go out of range in certain circumstances, you can reset the range as needed. Here's a rough example...
uStringGridTestMain.dfm:
object frmStringGridTestMain: TfrmStringGridTestMain
Left = 0
Top = 0
Caption = 'String Grid Test'
ClientHeight = 416
ClientWidth = 738
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 StringGrid1: TStringGrid
Left = 72
Top = 32
Width = 513
Height = 329
Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine]
TabOrder = 0
OnTopLeftChanged = StringGrid1TopLeftChanged
ColWidths = (
64
64
64
64
64)
RowHeights = (
24
24
24
24
24)
end
end
uStringGridTestMain.pas:
unit uStringGridTestMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids;
type
TfrmStringGridTestMain = class(TForm)
StringGrid1: TStringGrid;
procedure StringGrid1TopLeftChanged(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmStringGridTestMain: TfrmStringGridTestMain;
implementation
{$R *.dfm}
procedure TfrmStringGridTestMain.FormCreate(Sender: TObject);
begin
StringGrid1.Align:= alClient;
//Let's put a big scroll in both directions...
StringGrid1.RowCount:= 50;
StringGrid1.ColCount:= 50;
end;
procedure TfrmStringGridTestMain.StringGrid1TopLeftChanged(Sender: TObject);
begin
//You can change the "current" cell...
StringGrid1.Row:= 1;
StringGrid1.Col:= 1;
//Or you can change the scrolled cell on top-left...
StringGrid1.TopRow:= 1;
StringGrid1.LeftCol:= 1;
end;
end.
To prevent panning on drag you can set the TouchTracking property of TStringGrid to TBehaviorBoolean.False
Create a form and place a TValueListEditor. Set the doColumnTitles display option to False. Try and delete the first row. You will get a 'Grid Index out of Bounds' error. It is an ugly pig and so I am here to ask if using a try except block is the only way to deal with it?
Background:
Row is inherited from TCustomGrid via TCustomDrawGrid (Line 493 VCL.grids.pas) The property getter is private variable FCurrent.Y, the type of which is a value/field from a two value record. I cannot find where the FCurrent value is set by default (so it integer defaults to 0). It is not clear what values exist if the TValueListEditor has only one row and/or if there are no rows selected.
In any case, I get a Grid Index Out of Range Error - presumably raised from the TCustomGrid - but only if there is only one row and/or I attempt a delete on the first row in the TValueListEditor with doColumnTitles := false. Delete on other rows is fine. Regardless of whether this is a bug or not it seems somewhat stupid and inconsistent.
Is there a reason why the TValueList Editor constructor has inherited RowCount := 2;?
The error is thrown by TValueListEditor.DeleteRow according to:
try
if (Strings.Count = 0) or
(ARow < 1) or (ARow > RowCount - FixedRows) then
{$IF DEFINED(CLR)}
raise EInvalidGridOperation.CreateRes(SIndexOutOfRange);
{$ELSE}
raise EInvalidGridOperation.CreateRes(#SIndexOutOfRange);
{$ENDIF}
Strings.Delete(ARow - FixedRows);
finally
FDeleting := False;
end;
(See VCL.Valedit.pas at 804)
Is it a problem that there is an exception if (ARow < 1)?
Otherwise, perhaps it is that FixedRows is set in the constructor of the TCustomGrid:
constructor TCustomGrid.Create(AOwner: TComponent);
const
GridStyle = [csCaptureMouse, csOpaque, csDoubleClicks,
csNeedsBorderPaint, csPannable, csGestures];
begin
inherited Create(AOwner);
if NewStyleControls then
ControlStyle := GridStyle
else
ControlStyle := GridStyle + [csFramed];
FCanEditModify := True;
FColCount := 5;
FRowCount := 5;
FFixedCols := 1;
FFixedRows := 1;
...
and the comments in the header of VCL.grids.pas say "FixedRows The number of non-scrolling rows. This value must be at least one below RowCount." (Line 138).
Methinks that there ends up being a problem with these values when the titles row is removed from the TValueListEditor
Anyway. My question is just - Does this require the use of an exception handler - or is there a more elegant way of un-kludging it?
Here is some basic no seatbelts code:
unit vleDebug;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Buttons, Vcl.StdCtrls, Vcl.Grids, Vcl.ValEdit;
type
TForm1 = class(TForm)
vleWhoCaresNerd: TValueListEditor;
Button1: TButton;
BitBtn1: TBitBtn;
Label1: TLabel;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
procedure BitBtn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
vleWhoCaresNerd.DeleteRow(vleWhoCaresNerd.Row);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
vGUID: TGUID;
begin
vleWhoCaresNerd.InsertRow('id', GUIDToString(vGUID), True);
end;
end.
Here's the .dfm:
object Form1: TForm1
Left = 0
Top = 0
Caption = ':P'
ClientHeight = 568
ClientWidth = 633
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -13
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 120
TextHeight = 16
object Label1: TLabel
Left = 295
Top = 162
Width = 242
Height = 16
Caption = 'Select and delete the first row if you dare.'
end
object Label2: TLabel
Left = 63
Top = 190
Width = 393
Height = 16
Caption =
'Delete other rows if you like. Make some with the Add GUID But' +
'ton.'
end
object vleWhoCaresNerd: TValueListEditor
Left = 8
Top = 8
Width = 617
Height = 145
DisplayOptions = [doAutoColResize, doKeyColFixed]
Strings.Strings = (
'jsonrpc=2.0')
TabOrder = 0
ColWidths = (
150
461)
end
object Button1: TButton
Left = 8
Top = 159
Width = 75
Height = 25
Caption = 'Add GUID'
TabOrder = 1
OnClick = Button1Click
end
object BitBtn1: TBitBtn
Left = 550
Top = 159
Width = 75
Height = 25
Caption = 'bbDelete'
TabOrder = 2
OnClick = BitBtn1Click
end
end
RichEdit control stop drawing text when it became a parent for other control.
Is this a feature or a bug?
Is it possible to make RichEdit to be a parent for other control?
Check out next app:
-- Form1.dfm ---
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 282
ClientWidth = 418
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 = 24
Top = 8
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object RichEdit1: TRichEdit
Left = 16
Top = 72
Width = 145
Height = 105
Font.Charset = RUSSIAN_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
Lines.Strings = (
'RichEdit1')
ParentFont = False
TabOrder = 1
end
end
-- Form1.dfm ---
--- Unit1.pas ---
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;
type
TForm3 = class(TForm)
Button1: TButton;
RichEdit1: TRichEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.Button1Click(Sender: TObject);
begin
Button1.Parent := RichEdit1;
RichEdit1.Invalidate;
end;
end.
--- Unit1.pas ---
Test under Delphi XE5 + Win 7.
I want to create RichEdit with Edit button like this
This is the result that I want to get - RichEdit with DropDown Editor:
Use an interposer class that handles the WM_PAINT message like so:
type
TRichEdit = class(Vcl.ComCtrls.TRichEdit)
protected
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
end;
procedure TRichEdit.WMPaint(var Message: TWMPaint);
begin
DefaultHandler(Message);
end;
For reasons lost in the mists of time, TCustomRichEdit does some special handling of WM_PAINT that was only actually needed for the original version of the rich edit DLL. Moreover, this special handling breaks normal painting when another control is parented to the rich edit. As such, fixing the issue requires re-establishing standard VCL/Windows paint handling, which is what the code above does.
That said, I doubt nesting a button inside a rich edit is really what you want - the text won't wrap around it, for example.
Is it possible to do LiveBinding between controls, i.e. take 2 edit boxes and get the result of adding their contents together into a label. I'm sure it is, I just don't know where to start
Thanks
Have a look at the samples. SVN repository URL: https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio_XE2/LiveBindings
An example:
----- Unit1.dfm -----
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 286
ClientWidth = 426
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 8
Top = 62
Width = 48
Height = 13
Caption = 'Edit1Edit2'
end
object Edit1: TEdit
Left = 8
Top = 8
Width = 121
Height = 21
TabOrder = 0
Text = 'Edit1'
OnChange = EditChange
end
object Edit2: TEdit
Left = 8
Top = 35
Width = 121
Height = 21
TabOrder = 1
Text = 'Edit2'
OnChange = EditChange
end
object BindingsList1: TBindingsList
Methods = <>
OutputConverters = <>
UseAppManager = True
Left = 20
Top = 5
object BindExpressionLabel11: TBindExpression
Category = 'Binding Expressions'
ControlComponent = Label1
SourceComponent = BindScope1
SourceExpression = 'Edit1.Text + Edit2.Text'
ControlExpression = 'Caption'
NotifyOutputs = False
Direction = dirSourceToControl
end
end
object BindScope1: TBindScope
Left = 192
Top = 16
end
end
----- Unit1.pas -----
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.Bind.EngExt, Vcl.Bind.DBEngExt,
System.Rtti, System.Bindings.Outputs, Vcl.Bind.Editors, Data.Bind.Components,
Vcl.StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Label1: TLabel;
BindingsList1: TBindingsList;
BindExpressionLabel11: TBindExpression;
BindScope1: TBindScope;
procedure EditChange(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
System.Bindings.Helper;
procedure TForm1.EditChange(Sender: TObject);
begin
TBindings.Notify(Sender, 'Text');
end;
end.
How to use the IDE designer to produce the result:
put two edits (Edit1, Edit2), a label (Label1) and a TBindScope (BindScope1) on your form (Form1).
create an event handler for both edits' OnChange event (EditChange).
select Label1, expand the drop-down of LiveBindings property, select 'New Live Binding...', select TBindExpression
edit properties of the newly created BindExpressionLabel11: assign Caption to ControlExpression, BindScope1 to SourceComponent, Edit1.Text + Edit2.Text to SourceExpression
The sample project at the (Default) location of:
C:\Users\Public\Documents\RAD Studio\9.0\Samples\Delphi\LiveBinding\Components\bindexpression\fmx\BindExpressionSampleProject.dproj
does precisely that.
You don't need to TBindScope to bind components together. Say you have edit1 and edit2 on the form. If you set edit2 BindingSource to edit1 it will be link to changes to edit1