5
votes

I have a form displaying data from a database, It has a few buttons and several panels. The panels contain a variety of components, specifically TEdits, TComboBox, TDateTimePicker, TCheckBox, TListBox and TstringGrid.

When the form is opened such that the user can view, but not edit, the data I currently disable all the components except for the buttons using

for i := 0 to FrmAddNewMember.ComponentCount-1 do
    if FrmAddNewMember.Components[i] is TPanel then
      (FrmAddNewMember.Components[ i ] as TPanel).enabled := false;

This works fine except that I would now like the user to be able to copy to the clipboard the text in the TEdits, the date in the TDateTimePicker, the selected item in the TComboBox etc etc. but still not change it.

I altered the code to the following which sets the TEdits to Read Only. This does what I want for TEdits but the other type of controls do not have a read only property, so I disabled them as before.

for i := 0 to FrmAddNewMember.ComponentCount-1 do
    if not (FrmAddNewMember.Components[i] is TButton) then //(keep buttons working)
       case FrmAddNewMember.Components[i] of
          TEdit: (FrmAddNewMember.Components[ i ] as TEdit).readonly := true; //allows copying but not editing
          TComboBox: (FrmAddNewMember.Components[ i ] as TComboBox).enabled := false;  //no read only propert?
          TDateTimePicker: (FrmAddNewMember.Components[ i ] as TDateTimePicker).enabled := false;  //ditto
          TCheckBox: (FrmAddNewMember.Components[ i ] as TCheckBox).enabled := false;
          TListBox:  (FrmAddNewMember.Components[ i ] as TListBox).enabled := false;
          TstringGrid: (FrmAddNewMember.Components[ i ] as TstringGrid).enabled := false;
       end;

Is there a way to make the other controls non editable but still allow their contents to be copied to the clipboard?

PS I have looked at

disable-edits-on-datagridview-but-still-allow-for-highlighting-to-copy-and-paste

and

make-all-controls-on-a-form-read-only-at-once-without-one-linkbutton

and searched elsewhere. Maybe it cannot be done simply.

3
Isn't that small edit to my title a little pedantic if you don't also attempt to answer the question? I put the word Delphi there on purpose as I am fed up searching the web for questions/answers involving Delphi and finding some SO ones that look relevant but then turn out to be for a different language. SO may use the tags but Google doesn't.user2834566
Why aren't you using database conrols like TField, TDateTimeField, TMemoField, TBooleanField, etc. All of them have Readonly property to control whether they can be changed or notSilverWarior
"Why aren't you using database controls... " Because the exe is a front end to a remote MySQL database (and is used by several people). The form does a great deal of processing and much of what it displays is processed or formatted data from the database. It is not merely a simple 'window' on the raw database data.user2834566
Strange design to disable component and allow to copy data from it.Josef Švejk
@Dima The controls are all disabled in this view as I use the same layout for both editing and viewing (view has the controls disabled to prevent accidental changes). It's handy for the user to be able to copy things like an email address or reference number to use elsewhere. This can be done from the 'editing' form but there is a risk of altering the data, which would surprise the user when they closed the form and got a message asking them if they want to save changes. Hence I wanted to also allow such copying of data from the safer 'view' form where all the controls are disabled.user2834566

3 Answers

3
votes

I think, you can emulate a popup menu for your components (because standard popup menu will not work for disabled ones).

But if you will have popup menu for the form and FormMouseDown event handler, you can analyze where mouse pointer is (under which component, I mean) and call popup with Copy menu item.

Quick example for listboxes:

unit Unit6;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.StdCtrls, Vcl.ExtCtrls, Clipbrd;

type
  TForm6 = class(TForm)
    Panel1: TPanel;
    ListBox1: TListBox;
    ListBox2: TListBox;
    PopupMenu1: TPopupMenu;
    miCopy: TMenuItem;
    procedure miCopyClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
    selectedText: string;
  public
    { Public declarations }
  end;

var
  Form6: TForm6;

implementation

{$R *.dfm}

procedure TForm6.FormCreate(Sender: TObject);
begin
  ListBox1.ItemIndex := 1;
  ListBox2.ItemIndex := 1;
  Panel1.OnMouseDown := FormMouseDown;
end;

procedure TForm6.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  i, parentX, parentY: integer;
  p: TPoint;
  lb: TListBox;
begin
  if Button <> mbRight then
    exit;

  selectedText := '';
  for i := 0 to ComponentCount - 1 do
    if Components[i] is TListBox then
    begin
      lb := TListBox(Components[i]);
      begin
        p := lb.ParentToClient(Point(X, Y));
        if lb.ClientRect.Contains(p) then
        begin
          parentX := 0;
          parentY := 0;
          if Assigned(lb.Parent) then
          begin
            parentX := lb.Parent.ClientOrigin.X;
            parentY := lb.Parent.ClientOrigin.Y;
          end;

          if lb.ItemIndex > -1 then
          begin
            selectedText := lb.Items[lb.ItemIndex];
            PopupMenu1.Popup(lb.Left + parentX + p.X, lb.Top + parentY + p.Y);
          end;
          break;
        end;
      end;
    end;
end;

procedure TForm6.miCopyClick(Sender: TObject);
begin
  if selectedText = '' then
    exit;

  Clipboard.AsText := selectedText;
end;

end.

Here ListBox1 is placed on the TPanel component. Please note you should assign form's OnMouseDown handler to all your panels or other containers. Also, if you have nested containers, you need use recursive algorithm to find parentX, parentY.

0
votes

You can use this code to make combobox read-only. Also you can use the same approach to other edits if you can obtain the window handle of the edit.

procedure MakeComboboxReadOnly(const ACombobox: TCombobox);
var cbi: TComboBoxInfo;
begin
    cbi.cbSize := SizeOf(cbi);
    GetComboBoxInfo(ACombobox.Handle, cbi);
    SendMessage(cbi.hwndItem, EM_SETREADONLY, 1, 0);
end;
0
votes

Just to add a closure to my own question. The way I did it in the end was to simply place speed buttons next to the controls containing the text I wanted to copy and used the normal

uses ClipBrd;
...
Clipboard.AsText := MyControl1.text;

to copy the data.

The trick though was not to place the speed buttons as children of the panel containing the control, (which is disabled, hence also disabling all the controls inside it) but instead to just place them on the form and then move them so they are positioned in front of the panel next to the relevant control.

That way the speed buttons look like they are part of the panel yet will still operate when the panel and all it's controls are disabled.