6

When I place a TRibbon control on a form that is not the MainForm of the application, that TRibbon's actions (i.e. Cut, Paste) will always return focus to the MainForm after the action is executed.

This occurs even if the TForm that holds the TRibbon is not a child of the MainForm.

I am using Windows 7 64-bit, Embarcadero RAD Studio XE Version 15.0.3953.35171.

Am I using the TRibbon control incorrectly, or is this an issue with the TRibbon?

menjaraz
  • 7,551
  • 4
  • 41
  • 81
Aaron
  • 93
  • 7
  • 2
    A ribbon is intended to be a UI element in your main form, it does in fact "modify" the form it is added to. If you put a ribbon somewhere other than on the main form of your program, and it's sending focus back to application.MainForm, I'm not surprised; it expects it is part of the main form. The VCL comes with source code so you could open the unit up and see if you can find the code in question. – Warren P Dec 06 '11 at 18:24
  • 1
    The application I am designing is going for an "Outlook" feel, which, in its implementation, uses a Ribbon for the main program and a different Ribbon for creating emails, calendar items, contacts, etc. Whenever I use an Outlook Ribbon action in an email, it does not sent my focus back to the main Outlook window. I have done a bit of looking through the source for the TRibbon component, but it is admittedly a bit thick. I will continue to do so to see if I can discover where this is happening, and if I can override this behavior. Thus far, though, I've had no luck. – Aaron Dec 06 '11 at 18:40
  • 2
    Sounds awfully like a bug to me. That behaviour should not happen. If you can repro in a simple app then you should report it to QC – David Heffernan Dec 06 '11 at 20:20
  • I thought ribbon designed applications were meant to be used as a single document interface? Sure, technically you can design how you wish, but I believe a ribbon driven application should be SDI (just look at MS Paint and Wordpad in Windows 7). Having a ribbon on a form other than the main form is a bad design idea in my opinion. –  Dec 07 '11 at 01:36
  • 1
    Regardless of whether or not it's a bad design idea or not, I could replicate this on Rad Studio XE as well. Reading through the documentation it doesn't make it explicitly clear if this is intended behaviour, so it looks like a bug. – darpified Dec 07 '11 at 15:43
  • 1
    @Craig No, that's not the case. Outlook is one example. I don't see where SDI comes into it. – David Heffernan Dec 07 '11 at 19:34
  • Drop Delphi native TRibbon (it's just merely an emulation component), adopt Erik van Bilsen's [Windows Ribbon Framework](http://www.bilsen.com/windowsribbon/index.shtml). It uses native Windows library: It may likely stick to what you need if it is based on real feature found in the MS Ribbon implementation. – menjaraz Dec 19 '11 at 06:07

1 Answers1

2

This is evidently by design. Sample code snippet from 'ribbonactnctrls.pas':

procedure TRibbonBaseButtonControl.Click;
begin
  inherited;
  SetFocus(Application.MainForm.Handle);
end;

As you see there are no conditions checked that would help us avoid the call. There's the same code also in menu item selection and key press handlers.


I would probably modify the source commenting the focus calls, and try to see if there're any side effects.

As an alternative you can restore the focus back to your form after it is switched to the main form. Suppose 'ActionList1' is the TActionList that contains standard actions on the not main form:

type
  TForm2 = class(TForm)
    ..
    procedure ActionList1Execute(Action: TBasicAction; var Handled: Boolean);
  private
   ..

procedure TForm2.ActionList1Execute(Action: TBasicAction; var Handled: Boolean);
begin
  PostMessage(Handle, WM_SETFOCUS, WPARAM(True), 0);
end;

This will however cause the main form to flash briefly every time an action is executed. If you don't want that, you can change the design so that the main form knows when it is getting an unwanted focus, and fake that it's not focused.

In unit1:

const
  UM_CANCELIGNOREFOCUS = WM_USER + 7;

type
  TForm1 = class(TForm)
    ..
  private
    FIgnoreFocus: Boolean;
    procedure UMCancelIgnoreFocus(var Msg: TMessage); message UM_CANCELIGNOREFOCUS;
    procedure WMNCActivate(var Msg: TWMNCActivate); message WM_NCACTIVATE;
  public
    property IgnoreFocus: Boolean write FIgnoreFocus;
  end;

...
uses Unit2;

procedure TForm1.WMNCActivate(var Msg: TWMNCActivate);
begin
  Msg.Result := 0;
  if not (Msg.Active and FIgnoreFocus) then
    inherited;
end;

procedure TForm1.UMCancelIgnoreFocus(var Msg: TMessage);
begin
  FIgnoreFocus := False;
  TForm(Msg.WParam).SetFocus;
end;

in unit2:

uses
  unit1;

procedure TForm2.ActionList1Execute(Action: TBasicAction; var Handled: Boolean);
begin
  Form1.IgnoreFocus := True;
  PostMessage(Form1.Handle, UM_CANCELIGNOREFOCUS, NativeInt(Self), 0);
end;


However, this is not enough if you don't have 'MainFormOnTaskBar' set in project source, since then the main form will not only gain focus but will be brought to front. In this case both forms could respond to the unwanted focus change/activation by freezing their z-orders. The code would then become for unit1:

const
  UM_CANCELIGNOREFOCUS = WM_USER + 7;

type
  TForm1 = class(TForm)
    ..
  private
    FIgnoreFocus: Boolean;
    procedure UMCancelIgnoreFocus(var Msg: TMessage); message UM_CANCELIGNOREFOCUS;
    procedure WMNCActivate(var Msg: TWMNCActivate); message WM_NCACTIVATE;
    procedure WMWindowPosChanging(var Msg: TWMWindowPosChanging);
        message WM_WINDOWPOSCHANGING;
  public
    property IgnoreFocus: Boolean read FIgnoreFocus write FIgnoreFocus;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses Unit2;

procedure TForm1.WMNCActivate(var Msg: TWMNCActivate);
begin
  Msg.Result := 0;
  if not (Msg.Active and FIgnoreFocus) then
    inherited;
end;

procedure TForm1.WMWindowPosChanging(var Msg: TWMWindowPosChanging);
begin
  inherited;
  if FIgnoreFocus then
    Msg.WindowPos.flags := Msg.WindowPos.flags or SWP_NOZORDER;
end;

procedure TForm1.UMCancelIgnoreFocus(var Msg: TMessage);
begin
  FIgnoreFocus := False;
  TForm(Msg.WParam).SetFocus;
end;

and for unit2:

type
  TForm2 = class(TForm)
    ..
    procedure ActionList1Execute(Action: TBasicAction; var Handled: Boolean);
  private
    procedure WMWindowPosChanging(var Msg: TWMWindowPosChanging);
        message WM_WINDOWPOSCHANGING;
  public
  end;

var
  Form2: TForm2;

implementation

uses
  unit1;

{$R *.dfm}

procedure TForm2.ActionList1Execute(Action: TBasicAction; var Handled: Boolean);
begin
  Form1.IgnoreFocus := True;
  PostMessage(Form1.Handle, UM_CANCELIGNOREFOCUS, NativeInt(Self), 0);
end;

procedure TForm2.WMWindowPosChanging(var Msg: TWMWindowPosChanging);
begin
  inherited;
  if Form1.IgnoreFocus then
    Msg.WindowPos.flags := Msg.WindowPos.flags or SWP_NOZORDER;
end;
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • 1
    Great response! I still don't know if anyone can tell me why they designed the control to behave in such a forced manner, when it could have just returned control to the Ribbon's parent - I guess that just a Delphic mystery. – Aaron Jan 17 '12 at 17:08