The Road to Delphi

Delphi – Free Pascal – Oxygene

Adding a Standard Context Popup Menu to a SynEdit

6 Comments

When you uses an Edit control like a TMemo or TEdit component a context menu pops up with options to undo, copy, paste, select all, etc.

Unfortunally the TSynEdit component doesn’t include a menu like this, so you must write you own. You can fix this in a few lines of code using a interposer class. Check this unit which implements a standard context menu for a TSynEdit component, based on a TActionList (only be sure to include the uSynEditPopupEdit unit after of the SynEdit unit in your uses list).

unit uSynEditPopupEdit;

interface

uses
 ActnList,
 Menus,
 Classes,
 SynEdit;

type
  TSynEdit = class(SynEdit.TSynEdit)
  private
    FActnList: TActionList;
    FPopupMenu : TPopupMenu;
    procedure CreateActns;
    procedure FillPopupMenu(APopupMenu : TPopupMenu);
    procedure CutExecute(Sender: TObject);
    procedure CutUpdate(Sender: TObject);
    procedure CopyExecute(Sender: TObject);
    procedure CopyUpdate(Sender: TObject);
    procedure PasteExecute(Sender: TObject);
    procedure PasteUpdate(Sender: TObject);
    procedure DeleteExecute(Sender: TObject);
    procedure DeleteUpdate(Sender: TObject);
    procedure SelectAllExecute(Sender: TObject);
    procedure SelectAllUpdate(Sender: TObject);
    procedure RedoExecute(Sender: TObject);
    procedure RedoUpdate(Sender: TObject);
    procedure UndoExecute(Sender: TObject);
    procedure UndoUpdate(Sender: TObject);
    procedure SetPopupMenu_(const Value: TPopupMenu);
    function  GetPopupMenu_: TPopupMenu;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property PopupMenu: TPopupMenu read GetPopupMenu_ write SetPopupMenu_;
  end;

implementation

uses
 SysUtils;

const
 MenuName='uSynEditPopupMenu';

procedure TSynEdit.CopyExecute(Sender: TObject);
begin
  Self.CopyToClipboard;
end;

procedure TSynEdit.CopyUpdate(Sender: TObject);
begin
  TAction(Sender).Enabled :=Self.SelAvail;
end;

procedure TSynEdit.CutExecute(Sender: TObject);
begin
  Self.CutToClipboard;
end;

procedure TSynEdit.CutUpdate(Sender: TObject);
begin
  TAction(Sender).Enabled :=Self.SelAvail and not Self.ReadOnly;
end;

procedure TSynEdit.DeleteExecute(Sender: TObject);
begin
  Self.SelText := '';
end;

procedure TSynEdit.DeleteUpdate(Sender: TObject);
begin
  TAction(Sender).Enabled :=Self.SelAvail and not Self.ReadOnly;
end;

procedure TSynEdit.PasteExecute(Sender: TObject);
begin
 Self.PasteFromClipboard;
end;

procedure TSynEdit.PasteUpdate(Sender: TObject);
begin
 TAction(Sender).Enabled :=Self.CanPaste;
end;

procedure TSynEdit.RedoExecute(Sender: TObject);
begin
 Self.Redo;
end;

procedure TSynEdit.RedoUpdate(Sender: TObject);
begin
 TAction(Sender).Enabled :=Self.CanRedo;
end;

procedure TSynEdit.SelectAllExecute(Sender: TObject);
begin
 Self.SelectAll;
end;

procedure TSynEdit.SelectAllUpdate(Sender: TObject);
begin
 TAction(Sender).Enabled :=Self.Lines.Text<>'';
end;

procedure TSynEdit.UndoExecute(Sender: TObject);
begin
 Self.Undo;
end;

procedure TSynEdit.UndoUpdate(Sender: TObject);
begin
 TAction(Sender).Enabled :=Self.CanUndo;
end;

constructor TSynEdit.Create(AOwner: TComponent);
begin
  inherited;
  FActnList:=TActionList.Create(Self);
  FPopupMenu:=TPopupMenu.Create(Self);
  FPopupMenu.Name:=MenuName;
  CreateActns;
  FillPopupMenu(FPopupMenu);
  PopupMenu:=FPopupMenu;
end;

procedure TSynEdit.CreateActns;

 procedure AddActItem(const AText:string;AShortCut : TShortCut;AEnabled:Boolean;OnExecute,OnUpdate:TNotifyEvent);
 Var
    ActionItem  : TAction;
  begin
    ActionItem:=TAction.Create(FActnList);
    ActionItem.ActionList:=FActnList;
    ActionItem.Caption:=AText;
    ActionItem.ShortCut:=AShortCut;
    ActionItem.Enabled :=AEnabled;
    ActionItem.OnExecute :=OnExecute;
    ActionItem.OnUpdate  :=OnUpdate;
  end;

begin
  AddActItem('&Undo',Menus.ShortCut(Word('Z'), [ssCtrl]),False,UndoExecute, UndoUpdate);
  AddActItem('&Redo',Menus.ShortCut(Word('Z'), [ssCtrl,ssShift]),False,RedoExecute, RedoUpdate);
  AddActItem('-',0,False,nil,nil);
  AddActItem('Cu&t',Menus.ShortCut(Word('X'), [ssCtrl]),False,CutExecute, CutUpdate);
  AddActItem('&Copy',Menus.ShortCut(Word('C'), [ssCtrl]),False,CopyExecute, CopyUpdate);
  AddActItem('&Paste',Menus.ShortCut(Word('V'), [ssCtrl]),False,PasteExecute, PasteUpdate);
  AddActItem('De&lete',0,False,DeleteExecute, DeleteUpdate);
  AddActItem('-',0,False,nil,nil);
  AddActItem('Select &All',Menus.ShortCut(Word('A'), [ssCtrl]),False,SelectAllExecute, SelectAllUpdate);
end;

procedure TSynEdit.SetPopupMenu_(const Value: TPopupMenu);
Var
  MenuItem : TMenuItem;
begin
  SynEdit.TSynEdit(Self).PopupMenu:=Value;
  if CompareText(MenuName,Value.Name)<>0 then
  begin
   MenuItem:=TMenuItem.Create(Value);
   MenuItem.Caption:='-';
   Value.Items.Add(MenuItem);
   FillPopupMenu(Value);
  end;
end;

function TSynEdit.GetPopupMenu_: TPopupMenu;
begin
  Result:=SynEdit.TSynEdit(Self).PopupMenu;
end;

destructor TSynEdit.Destroy;
begin
  FPopupMenu.Free;
  FActnList.Free;
  inherited;
end;

procedure TSynEdit.FillPopupMenu(APopupMenu : TPopupMenu);
var
  i        : integer;
  MenuItem : TMenuItem;
begin
  if Assigned(FActnList) then
  for i := 0 to FActnList.ActionCount-1 do
  begin
    MenuItem:=TMenuItem.Create(APopupMenu);
    MenuItem.Action  :=FActnList.Actions[i];
    APopupMenu.Items.Add(MenuItem);
  end;
end;

end.

And this is the final result.

Unknown's avatar

Author: Rodrigo

Just another Delphi guy.

6 thoughts on “Adding a Standard Context Popup Menu to a SynEdit

  1. HeartWare's avatar

    How about the CUA shortcuts?

  2. Ralf's avatar

    That does not include the unicode and IME menu items!

    ;.)

  3. CopyPaste's avatar

    I know this topic is quite old. But since it’s still a valid solution I’d like to add an improvement that I implemented after running into some irritating problems. Your current implementation creates “global” hotkeys that grab all normal keyboard actions (like Ctrl+V) to the SynEdit, even if the focus is in a totaly different control. Also it pastes inside a disabled SynEdit.

    So to solve this I’ve set FActnList.State := asSuspended; in CreateActns and added:
    procedure TSynEdit.DoEnter;
    begin
    FActnList.State := asNormal;
    inherited;
    end;
    procedure TSynEdit.DoExit;
    begin
    FActnList.State := asSuspended;
    inherited;
    end;

    That way the hotkeys become only active if the focus is inside the SynEdit.
    Just wanted to give something back to you. Thanks for all your Delphi work!

  4. Antonio's avatar

    I use your solution with Lazarus (fpc)

    Thanks.

  5. Tiger Jakt's avatar

    Thank you RRUZ. That was a great help to me.

Leave a reply to HeartWare Cancel reply