7
votes

I wonder what is the suggested way to embed and control MS Word in a TForm, layout-wise? Currently, I (1) put two TPanel on TForm. The alBottom TPanel has one TButton, and the alClient TPanel has a alNone TOleContainer. (2) set up the layout in TMainForm.FormCreate event handler.

The problem is MS Word likes to take up all the space of its parent form. Only the fourth way as shown below seems to give acceptable layout. Based on trial-and-error, it seems necessary to use a sub-form instead of TPanel to host TOleContainer. (Also, Windows.SetParent seems necessary.) I wonder whether a sub-form is the correct way to go?

PS: Delphi XE, Word 2010, Windows 7

PS: web pages related to "hosting external applications":

Binh Ly's site

Deborah's site

How to shell to another app and have it appear in a delphi form

TOleContainer Revisited

Open word document in delphi?

Delphi & Word (SimpChn)

PS: web pages related to "Form in Panel (sub-form)":

how to make a transparent form inside Panel?

Delphi - OleContainer - PowerPoint - AutoPlay

FreePascal/Lazarus MultiDoc

TForm in a panel

How to create a delphi form containing multiple 'child' forms that can be moved/sized and show activated

sample code

unit uMainForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleCtnrs, ExtCtrls, StdCtrls;

type
  TMainForm = class(TForm)
    PanelOle: TPanel;
    PanelBtn: TPanel;
    OleContainer1: TOleContainer;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

procedure TMainForm.FormCreate(Sender: TObject);
var
  OleForm: TForm;
begin
////
//// 1
////
//  OleContainer1.Parent := PanelOle;
//  OleContainer1.Align := alClient;
//
////
//// 2
////
//  Windows.SetParent(OleContainer1.Handle, PanelOle.Handle);
//  OleContainer1.Align := alClient;
//
////
//// 3
////
//  OleForm := TForm.Create(Self);
//  OleForm.Parent := PanelOle;
//  OleForm.Align := alClient;
//  OleForm.Visible := True;
//  OleContainer1.Parent := OleForm;
//  OleContainer1.Align := alClient;
//
////
//// 4 Works!
////
//  OleForm := TForm.Create(Self);
//  Windows.SetParent(OleForm.Handle, PanelOle.Handle);
//  OleForm.Align := alClient;
//  OleForm.Visible := True;
//  OleContainer1.Parent := OleForm;
//  OleContainer1.Align := alClient;
//
////
//// 5
////
//  OleForm := TForm.Create(Self);
//  OleForm.Parent := PanelOle;
//  OleForm.Align := alClient;
//  OleForm.Visible := True;
//  Windows.SetParent(OleContainer1.Handle,OleForm.Handle);
//  OleContainer1.Align := alClient;
//
////
//// 6
////
//  OleForm := TForm.Create(Self);
//  Windows.SetParent(OleForm.Handle, PanelOle.Handle);
//  OleForm.Align := alClient;
//  OleForm.Visible := True;
//  Windows.SetParent(OleContainer1.Handle,OleForm.Handle);
//  OleContainer1.Align := alClient;

end;

procedure TMainForm.Button1Click(Sender: TObject);
var
// Declare the item to be a generic OleVariant to force late binding
  Ds: OleVariant;
  D: OleVariant;
begin
  OleContainer1.CreateObjectFromFile('sample.docx', False);

  OleContainer1.Run;

  OleContainer1.AutoActivate := aaManual;
  OleContainer1.DoVerb(ovShow);  // not in FormCreate, in or after FormShow

  Ds := OleContainer1.OleObject.Application.Documents;
  Caption := IntToStr(Ds.Count);
end;

end.
1
It's a bit weird.. No need to parent another form, just do not 'client' align the panel holding the ole container. Top align it, use anchors etc., but do not use 'alClient' on it. Alternatively, place the bottom aligned panel on the panel with the ole container, IOW make the bottom panel a sibling of the container.Sertac Akyuz
@SertacAkyuz: Thank you very much for your insightful comments! Both of your suggestions help to prevent the activated MSWord's OleContainer from expanding and overlapping with the other panel and buttons! Wow! I wonder, could you help to comment how to systematically learn these knowledge that you just taught? I mean, are there some books to recommend? :DSOUser
@SertacAkyuz: Now if I put another button (that does nothing) on the other panel, and click it, the menu of the embedded Word disappears. I seems to be related to "activate/deactivate", but I have no clue what to do or look for. Could you help to comment on possible solutions? Thank you for your time!SOUser
(1) Sorry I won't be able to recommend anything, it was just some trial and error. In fact if I knew why this helps solve the problem, I'd provide an answer. (2) Call the 'Show' verb whenever you need to reactivate the ole object. If you need it to not to deactivate at all, use non-windowed buttons - like toolbar buttons or speed buttons..Sertac Akyuz
@SertacAkyuz: TOleContainer.DoVerb works great! Thank you very much for your time and patient help again!SOUser

1 Answers

2
votes

Subform is a correct way to do it. We used this approach in a production environment and it worked. We hosted our "sub" form in a panel. However, we modified TOleContainer and TOleForm with a flag whether to use the parent form, or the topmost form:

procedure TOurOleContainer.InitObject;
...
begin
  if FDrawInTopForm then
    DocForm := GetParentForm(Self)
  else
    DocForm := TCustomForm(Parent);
...

Where FDrawInTopForm is a property we introduced. We also modified:

function GetVCLFrameForm(Form: TCustomForm; DrawInTopForm: Boolean): IVCLFrameForm;
begin
  if Form.OleFormObject = nil then TOleForm.Create(Form, DrawInTopForm); 
  Result := Form.OleFormObject as IVCLFrameForm;
end;

Unfortunately, due to agreements with the customer, I cannot post the complete solution here.