Ole Kullmann
asked on
Visibility of toolbox out of control
I am working on an application that has a toolbox (a small window that stays on top of, or even outside the main form).
There are three wishes to the visibility of this toolbox:
1) The user must be able to open and close (show and hide) the toolbox as s/he wishes. The toolbox can be opened AND closed via a menu item which uses the Checked flag to indicate the current state. The toolbox can also be closed via the X button of the toolbox itself.
2) When the user switches to a different application, the toolbox (if visible), must be hidden until the user switches back to the application in question. This is important because the user may have more instances of the application open and switching between them must also switch toolbox so that a visible toolbox is assured to be linked to the active application. The toolbox must only be shown again if it was visible before the application switch.
3) If the application is minimized, the toolbox (if visible), must be hidden until the application is restored. The toolbox must only be shown again if it was visible before the minimize.
Now, that's three wishes at the same time. Maybe I should have bought a Kinder Egg...
Opening and closing via a menu item is easy. I can simply ask for the current state of visibility, in order to determine whether to Show or to Hide. However, the nasty user can also close via the X button of the toolbox, so I cannot just set the menu item Checked property when processing the menu click. To solve that, I added OnShow and OnHide (could be OnClose) handlers in the toolbox, which call back to the main form, in which the code now is something like:
procedure TForm1.ViewToolboxClick(Se nder: TObject); // Menu item click
begin
if Form2.Visible then // or: if ViewToolbox.Checked then
Form2.Hide
else
Form2.Show;
end;
procedure TForm1.ToolboxShow(Sender: TObject); // Form2.OnShow callback
begin
ViewToolbox.Checked := True;
end;
procedure TForm1.ToolboxHide(Sender: TObject); // Form2.OnHide callback
begin
ViewToolbox.Checked := False;
end;
Switching application is a little more tricky. I cannot use FormActivate / FormDeactive as they are also called for switches between main form and toolbox. Instead, I use some event handlers in the global Application object:
procedure TForm1.FormShow(Sender: TObject);
begin
Application.OnActivate := AppActivate;
Application.OnDeactivate := AppDeactivate;
Form2.OnShow := ToolboxShow;
Form2.OnHide := ToolboxHide;
FShowToolbox := True; // Toolbox will be shown in AppActivate
end;
And as you can see, I have introduced a variable to control whether AppActivate should open the toolbox or not.
procedure TForm1.AppActivate(Sender: TObject);
begin
if FShowToolbox then
Form2.Show;
end;
procedure TForm1.AppDeactivate(Sende r: TObject);
begin
if FShowToolbox then // could also be testing Form2.Visible
begin
Form2.Hide;
FShowToolbox := True; // To make it visible again at AppActivate
end;
end;
But what is that last setting of FShowToolbox? It seems that this variable is never set False. Well, it is. I extended the previous code a little:
procedure TForm1.ToolboxShow(Sender: TObject);
begin
FShowToolbox := True;
ViewToolbox.Checked := True;
end;
procedure TForm1.ToolboxHide(Sender: TObject);
begin
FShowToolbox := False;
ViewToolbox.Checked := False;
end;
So the "Form2.Hide" statement in TForm1.AppDeactivate sets the FShowToolbox to False and I need to set it back to True because I'm only closing temporarily - it's not a close of toolbox requested by the user.
That's about how much I had done to that for a start. I had NOT considered the third wish at all. I thought it was a simple case of "application switch".
But one day last week I happened to minimize the application. To my surprise, the toolbox remained visible. Or rather, it was minimized and immediately redisplayed. Only the main form was effectively minimized.
I changed a little and tested again. Now minimizing worked, but application switch did not. No matter what I did, I couldn't get the program to fulfil all three wishes.
I then decided to isolate the problem in a small test program instead of trying wild things in my large project.
Doing so didn't make me less confused, but it allowed me to introduce a semi-automatic memorization of what happened in the program: The test program has a memo and each method in the main program writes a line to that memo. It's being logged with a time stamp and includes the name of the method. While running, I can manually add comments in the memo. When closing, the test program saves the memo to a text file.
After having tried various things, the test program no longer has the original problem, but that doesn't mean, it's better. The current problems are:
>>> After minimizing by clicking the MINIMIZE BUTTON of Form1 and restoring the application by clicking the taskbar icon, the toolbox is displayed as it should, but clicking X on the toolbox does not close the toolbox. <<<
>>> After minimizing by clicking the TASKBAR ICON and restoring the same way, the toolbox remains invisible although it was open before. <<<
I have tested this with Delphi XE[1] and RAD Studio 10.1 Berlin Starter on Windows 7 and 10. Same results.
Is my approach to the toolbox visibility insane or what? My setting of FShowToolbox is questionable, I admit, but can you come up with something better?
Attached to this question, you should find the source of the test program. Feel free to test any changes you like, but make sure that you fulfil all three wishes and remember: There are two ways the user may close the toolbox (menu item or X button). There are - at least - two ways to switch application (clicking an app window or using Alt+TAB) and there are two ways to minimize (minimize button of main form or clicking the app icon in the taskbar). All should be working, if you are want to make me happy again.
Best Regards,
Ole Kullmann
Unit1.dfm
Unit1.pas
Unit2.dfm
Unit2.pas
There are three wishes to the visibility of this toolbox:
1) The user must be able to open and close (show and hide) the toolbox as s/he wishes. The toolbox can be opened AND closed via a menu item which uses the Checked flag to indicate the current state. The toolbox can also be closed via the X button of the toolbox itself.
2) When the user switches to a different application, the toolbox (if visible), must be hidden until the user switches back to the application in question. This is important because the user may have more instances of the application open and switching between them must also switch toolbox so that a visible toolbox is assured to be linked to the active application. The toolbox must only be shown again if it was visible before the application switch.
3) If the application is minimized, the toolbox (if visible), must be hidden until the application is restored. The toolbox must only be shown again if it was visible before the minimize.
Now, that's three wishes at the same time. Maybe I should have bought a Kinder Egg...
Opening and closing via a menu item is easy. I can simply ask for the current state of visibility, in order to determine whether to Show or to Hide. However, the nasty user can also close via the X button of the toolbox, so I cannot just set the menu item Checked property when processing the menu click. To solve that, I added OnShow and OnHide (could be OnClose) handlers in the toolbox, which call back to the main form, in which the code now is something like:
procedure TForm1.ViewToolboxClick(Se
begin
if Form2.Visible then // or: if ViewToolbox.Checked then
Form2.Hide
else
Form2.Show;
end;
procedure TForm1.ToolboxShow(Sender:
begin
ViewToolbox.Checked := True;
end;
procedure TForm1.ToolboxHide(Sender:
begin
ViewToolbox.Checked := False;
end;
Switching application is a little more tricky. I cannot use FormActivate / FormDeactive as they are also called for switches between main form and toolbox. Instead, I use some event handlers in the global Application object:
procedure TForm1.FormShow(Sender: TObject);
begin
Application.OnActivate := AppActivate;
Application.OnDeactivate := AppDeactivate;
Form2.OnShow := ToolboxShow;
Form2.OnHide := ToolboxHide;
FShowToolbox := True; // Toolbox will be shown in AppActivate
end;
And as you can see, I have introduced a variable to control whether AppActivate should open the toolbox or not.
procedure TForm1.AppActivate(Sender:
begin
if FShowToolbox then
Form2.Show;
end;
procedure TForm1.AppDeactivate(Sende
begin
if FShowToolbox then // could also be testing Form2.Visible
begin
Form2.Hide;
FShowToolbox := True; // To make it visible again at AppActivate
end;
end;
But what is that last setting of FShowToolbox? It seems that this variable is never set False. Well, it is. I extended the previous code a little:
procedure TForm1.ToolboxShow(Sender:
begin
FShowToolbox := True;
ViewToolbox.Checked := True;
end;
procedure TForm1.ToolboxHide(Sender:
begin
FShowToolbox := False;
ViewToolbox.Checked := False;
end;
So the "Form2.Hide" statement in TForm1.AppDeactivate sets the FShowToolbox to False and I need to set it back to True because I'm only closing temporarily - it's not a close of toolbox requested by the user.
That's about how much I had done to that for a start. I had NOT considered the third wish at all. I thought it was a simple case of "application switch".
But one day last week I happened to minimize the application. To my surprise, the toolbox remained visible. Or rather, it was minimized and immediately redisplayed. Only the main form was effectively minimized.
I changed a little and tested again. Now minimizing worked, but application switch did not. No matter what I did, I couldn't get the program to fulfil all three wishes.
I then decided to isolate the problem in a small test program instead of trying wild things in my large project.
Doing so didn't make me less confused, but it allowed me to introduce a semi-automatic memorization of what happened in the program: The test program has a memo and each method in the main program writes a line to that memo. It's being logged with a time stamp and includes the name of the method. While running, I can manually add comments in the memo. When closing, the test program saves the memo to a text file.
After having tried various things, the test program no longer has the original problem, but that doesn't mean, it's better. The current problems are:
>>> After minimizing by clicking the MINIMIZE BUTTON of Form1 and restoring the application by clicking the taskbar icon, the toolbox is displayed as it should, but clicking X on the toolbox does not close the toolbox. <<<
>>> After minimizing by clicking the TASKBAR ICON and restoring the same way, the toolbox remains invisible although it was open before. <<<
I have tested this with Delphi XE[1] and RAD Studio 10.1 Berlin Starter on Windows 7 and 10. Same results.
Is my approach to the toolbox visibility insane or what? My setting of FShowToolbox is questionable, I admit, but can you come up with something better?
Attached to this question, you should find the source of the test program. Feel free to test any changes you like, but make sure that you fulfil all three wishes and remember: There are two ways the user may close the toolbox (menu item or X button). There are - at least - two ways to switch application (clicking an app window or using Alt+TAB) and there are two ways to minimize (minimize button of main form or clicking the app icon in the taskbar). All should be working, if you are want to make me happy again.
Best Regards,
Ole Kullmann
Unit1.dfm
Unit1.pas
Unit2.dfm
Unit2.pas
ASKER
Thanks, Geert
Bad description I made, if you get that impression. The toolbox is just a form in a unit - not a separate application. Looking forward to your sample. I'm not sure what you mean by "observer pattern", but the sample will probably clarify that to me.
Ole
Bad description I made, if you get that impression. The toolbox is just a form in a unit - not a separate application. Looking forward to your sample. I'm not sure what you mean by "observer pattern", but the sample will probably clarify that to me.
Ole
the first problem to solve is to be able to give an event to your toolbox unit
which gets called when an action happens on the toolbox
like closing the toolbox with the X
this is the best implementation i ever found with adding/removing events to a list
http://delphi.cjcsoft.net/viewthread.php?tid=44901
i had to fix some typos because the browser interprets the > and < symbols
which gets called when an action happens on the toolbox
like closing the toolbox with the X
this is the best implementation i ever found with adding/removing events to a list
http://delphi.cjcsoft.net/viewthread.php?tid=44901
i had to fix some typos because the browser interprets the > and < symbols
unit uPatterns;
interface
uses Classes, SysUtils;
type
TMultiEvent = class
private
FObservers: TList;
protected
function FindObserver(Observer: TMethod): integer;
function GetObserver(Index: integer): TMethod;
procedure SignalObserver(Observer: TMethod); virtual;
public
constructor Create;
destructor Destroy; override;
procedure Attach(Observer: TMethod);
procedure Detach(Observer: TMethod);
procedure Signal;
end;
TMultiNotifyEvent = class(TMultiEvent)
private
FSender: TObject;
protected
procedure SignalObserver(Observer: TMethod); override;
public
procedure Attach(Observer: TNotifyEvent);
procedure Detach(Observer: TNotifyEvent);
procedure Signal(Sender: TObject);
end;
TSubject = class
private
FOnChange: TMultiNotifyEvent;
FValue: string;
procedure SetValue(const aValue: string);
protected
procedure DoChange; virtual;
public
constructor Create(AValue: string);
destructor Destroy; override;
property OnChange: TMultiNotifyEvent read FOnChange;
property Value: string read FValue write SetValue;
end;
implementation
{ TEvent }
procedure TMultiEvent.Attach(Observer: TMethod);
var
Index: integer;
begin
Index := FindObserver(Observer);
{ A method contains two pointers: }
{ - The code pointer, that's where the procedure is in memory }
{ - The data pointer, this tells Delphi what instance of the }
{ object calls the procedure }
{ We must store both pointers in order to use that callback. }
if Index < 0 then
begin
FObservers.Add(Observer.Code);
FObservers.Add(Observer.Data);
end;
end;
constructor TMultiEvent.Create;
begin
inherited Create;
FObservers := TList.Create;
end;
destructor TMultiEvent.Destroy;
begin
FreeAndNil(FObservers);
inherited Destroy;
end;
procedure TMultiEvent.Detach(Observer: TMethod);
var
Index: integer;
begin
Index := FindObserver(Observer) * 2;
if Index >= 0 then
begin
FObservers.Delete(Index); // Delete code pointer
FObservers.Delete(Index); // Delete data pointer
end;
end;
function TMultiEvent.FindObserver(Observer: TMethod): integer;
var
i: integer;
begin
{ Search fails by default, if there is a match, result will be updated. }
Result := -1;
for i := (FObservers.Count div 2)-1 downto 0 do
begin
{ We have a match only if both the Code and Data pointers are the same. }
if (Observer.Code = FObservers[i * 2 ]) and (Observer.Data = FObservers[i * 2 + 1]) then
begin
Result := i;
break;
end;
end;
end;
function TMultiEvent.GetObserver(Index: integer): TMethod;
begin
{ Fill the TMethod record with the code and data pointers. }
Result.Code := FObservers[Index * 2];
Result.Data := FObservers[Index * 2 + 1];
end;
procedure TMultiEvent.SignalObserver(Observer: TMethod);
begin
{ Descendants must take care to notify the Observer by themselves }
{ because we cannot know the parameters required by the event. }
Assert(Assigned(@Observer));
{ We could make this method Abstract and force descendants, but }
{ I prefer to do a run-time check to validate the passe methods }
end;
procedure TMultiEvent.Signal;
var
i: integer;
begin
{ Call SignalObserver for each stored observers in reverse order. }
{ SignalObserver (which is declared in sub-classes) will typecast }
{ the TMethod record into whatever procedure type it handles. }
{ See the TMultiNotifyEvent below for an example. }
for i := (FObservers.Count div 2)-1 downto 0 do
begin
SignalObserver(GetObserver(i));
end;
end;
{ TMultiNotifyEvent }
procedure TMultiNotifyEvent.Attach(Observer: TNotifyEvent);
begin
inherited Attach(TMethod(Observer));
end;
procedure TMultiNotifyEvent.Detach(Observer: TNotifyEvent);
begin
inherited Detach(TMethod(Observer));
end;
procedure TMultiNotifyEvent.Signal(Sender: TObject);
begin
FSender := Sender;
inherited Signal;
end;
procedure TMultiNotifyEvent.SignalObserver(Observer: TMethod);
begin
inherited SignalObserver(Observer);
TNotifyEvent(Observer)(FSender);
end;
{ TSubject }
constructor TSubject.Create(AValue: string);
begin
inherited Create;
FValue := AValue;
FOnChange := TMultiNotifyEvent.Create;
end;
destructor TSubject.Destroy;
begin
FOnChange.Free;
inherited Destroy;
end;
procedure TSubject.DoChange;
begin
{ Signal is the method that notify every observers }
OnChange.Signal(Self);
end;
procedure TSubject.SetValue(const aValue: string);
begin
if fValue <> aValue then
begin
FValue := aValue;
DoChange;
end;
end;
end.
i create a sizeable toolwindow stayontop form
with this code:
and this dfm:
with this code:
unit uToolbox;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
uPatterns;
type
TfrmToolbox = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormWindowStateChanged(Sender: TObject);
procedure FormActivate(Sender: TObject);
private
fSOWindowState: TSubject;
public
constructor Create(aOwner: TComponent); override;
destructor Destroy; override;
function VisibleWindowState: string;
property SOWindowState: TSubject read fSOWindowState;
end;
var
frmToolbox: TfrmToolbox = nil;
function Toolbox: TfrmToolbox;
implementation
{$R *.dfm}
function WindowStateToString(aWindowState: TWindowState): string;
begin
Result := 'Normal';
case aWindowState of
wsMinimized: Result := 'Minimized';
wsMaximized: Result := 'Maximized';
//else
// wsNormal
// Result := 'Normal';
end;
end;
function VisibleToString(aVisible: Boolean): string;
begin
Result := 'Hidden';
if aVisible then
Result := 'Visible';
end;
function Toolbox: TfrmToolbox;
begin
if not Assigned(frmToolbox) or (frmToolbox = nil) then
frmToolbox := TfrmToolbox.Create(Application);
Result := frmToolbox;
end;
constructor TfrmToolbox.Create(aOwner: TComponent);
begin
inherited Create(AOwner);
fSOWindowState := TSubject.Create(VisibleWindowState);
end;
destructor TfrmToolbox.Destroy;
begin
fSOWindowState.Free;
inherited Destroy;
end;
procedure TfrmToolbox.FormActivate(Sender: TObject);
begin
Visible := True;
end;
procedure TfrmToolbox.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caHide;
FormWindowStateChanged(Self);
end;
procedure TfrmToolbox.FormWindowStateChanged(Sender: TObject);
begin
fSOWindowState.Value := VisibleWindowState;
end;
function TfrmToolbox.VisibleWindowState: string;
begin
Result := VisibleToString(Visible) + '_' + WindowStateToString(WindowState);
end;
end.
and this dfm:
object frmToolbox: TfrmToolbox
Left = 0
Top = 0
BorderStyle = bsSizeToolWin
Caption = 'Toolbox'
ClientHeight = 478
ClientWidth = 387
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
FormStyle = fsStayOnTop
OldCreateOrder = False
Visible = True
OnActivate = FormActivate
OnClose = FormClose
OnHide = FormWindowStateChanged
OnResize = FormWindowStateChanged
OnShow = FormWindowStateChanged
PixelsPerInch = 96
TextHeight = 13
end
and the main form has a menu windows, which can be used to hide/show the toolbox
and a memo to display the changes of toolbox window state:
the dfm code:
and a memo to display the changes of toolbox window state:
unit uMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.StdCtrls;
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
mnuWindows: TMenuItem;
mnuToolbox: TMenuItem;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure mnuToolboxClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
procedure ToolboxChangedWindowState(Sender: TObject);
end;
var
Form1: TForm1;
implementation
uses uToolbox;
{$R *.dfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
Toolbox.SOWindowState.OnChange.Attach(ToolboxChangedWindowState);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// this throws an AV because the TfromToolbox is destroyed first
// Toolbox.SOWindowState.OnChange.Detach(ToolboxChangedWindowState);
end;
procedure TForm1.mnuToolboxClick(Sender: TObject);
begin
Toolbox.Visible := mnuToolbox.Checked;
end;
procedure TForm1.ToolboxChangedWindowState(Sender: TObject);
var temp: string;
IsVisible: Boolean;
begin
temp := Toolbox.VisibleWindowState;
IsVisible := SameText(Copy(temp, 1, 7), 'Visible');
if mnuToolbox.Checked <> IsVisible then
mnuToolbox.Checked := IsVisible;
memo1.Lines.Add('Toolbox window state changed to : ' + temp);
end;
end.
the dfm code:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 426
ClientWidth = 828
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
Menu = MainMenu1
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 13
object Memo1: TMemo
Left = 56
Top = 96
Width = 545
Height = 225
Lines.Strings = (
'Memo1')
TabOrder = 0
end
object MainMenu1: TMainMenu
Left = 408
Top = 216
object mnuWindows: TMenuItem
Caption = 'Windows'
object mnuToolbox: TMenuItem
AutoCheck = True
Caption = 'Toolbox'
Checked = True
OnClick = mnuToolboxClick
end
end
end
end
now, the trick is to show the toolbox only when the app is active
ASKER
Thanks again, Geert
I will be back on this after a thorough study of the material you supplied. It may take a few days, but I can assure you, that I will be back here.
Ole
I will be back on this after a thorough study of the material you supplied. It may take a few days, but I can assure you, that I will be back here.
Ole
when i changed the toolbox code like below...
the toolbox only remains visible when the app is active
the toolbox only remains visible when the app is active
unit uToolbox;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
uPatterns;
type
TfrmToolbox = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormWindowStateChanged(Sender: TObject);
private
fSOWindowState: TSubject;
fVisibleOnDeactive: Boolean;
fOldAppDeactivate: TNotifyEvent;
fOldAppActivate: TNotifyEvent;
fDeactivating: Boolean;
procedure OnAppDeactivate(Sender: TObject);
procedure OnAppActivate(Sender: TObject);
public
constructor Create(aOwner: TComponent); override;
destructor Destroy; override;
function VisibleWindowState: string;
property SOWindowState: TSubject read fSOWindowState;
end;
var
frmToolbox: TfrmToolbox = nil;
function Toolbox: TfrmToolbox;
implementation
{$R *.dfm}
function WindowStateToString(aWindowState: TWindowState): string;
begin
Result := 'Normal';
case aWindowState of
wsMinimized: Result := 'Minimized';
wsMaximized: Result := 'Maximized';
//else
// wsNormal
// Result := 'Normal';
end;
end;
function VisibleToString(aVisible: Boolean): string;
begin
Result := 'Hidden';
if aVisible then
Result := 'Visible';
if not Application.Active then
Result := 'Inactive_' + Result;
end;
function Toolbox: TfrmToolbox;
begin
if not Assigned(frmToolbox) or (frmToolbox = nil) then
frmToolbox := TfrmToolbox.Create(Application);
Result := frmToolbox;
end;
constructor TfrmToolbox.Create(aOwner: TComponent);
begin
inherited Create(AOwner);
fVisibleOnDeactive := True;
fSOWindowState := TSubject.Create(VisibleWindowState);
fDeactivating := False;
fOldAppActivate := Application.OnActivate;
Application.OnActivate := OnAppActivate;
fOldAppDeactivate := Application.OnDeactivate;
Application.OnDeactivate := OnAppDeactivate;
end;
destructor TfrmToolbox.Destroy;
begin
fSOWindowState.Free;
inherited Destroy;
end;
procedure TfrmToolbox.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caHide;
FormWindowStateChanged(Self);
end;
procedure TfrmToolbox.FormWindowStateChanged(Sender: TObject);
begin
if not fDeactivating then
fSOWindowState.Value := VisibleWindowState;
end;
procedure TfrmToolbox.OnAppActivate(Sender: TObject);
begin
if fVisibleOnDeactive then
Visible := True;
end;
procedure TfrmToolbox.OnAppDeactivate(Sender: TObject);
begin
fVisibleOnDeactive := Visible;
fDeactivating := True;
try
Hide;
finally
fDeactivating := False;
end;
end;
function TfrmToolbox.VisibleWindowState: string;
begin
Result := VisibleToString(Visible) + '_' + WindowStateToString(WindowState);
end;
end.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Hi Geert,
Thanks again for your interest in my problems. I read the article about Observer Patterns you linked to, and I copied your edited copy of uPatterns.
I find the that uPatterns has a very interesting way of solving the problem, that you cannot assign more than one event handler at a time in Delphi (other languages allow this).
Anyway, you sent my memory back to 2013, when I created a TStackableEventHandler which signals the event handler on the top of the stack only. Possible to pick a handler off the stack, of course. In that way, a number of forms in the application might set their individual handlers in FormShow and pick them off in FormHide. Around the same time, I also created a multi-purpose TOneTimer taking care of many different timing needs through one single TTimer component. uPattern's little trick with saving both .Code and .Data from the event handler reminded me of something, I have seen before. Unfortunately, I just don't remember what it was.
But, back from memory lane...
As I said Friday, we agree upon a toolbox being a form in a unit. Bad names in my sample. Form1 should be fMain (or frmMain if you like) and Form2 should be fToolbox (or frmToolbox).
You write that "in reality it should only be a separate unit which you include in the uses list of the project that alone should solve the correct toolbox being active with the active version of the app" and yes, we agree again, except that there must be SOME communication between the main program and the toolbox. HOW that communication is established, I will be happy to discuss further. Actually, I will come back to this a little later.
In the same paragraph, you also wrote "it is possible you have multiple toolboxes open, but clicking on a another toolbox will activate that other application". In theory - for another project - that could be reasonable, but for the large application, I am working on, this is not desired. On the contrary, I might reveal, there are going to be several DIFFERENT toolboxes available, and the user may want some open for one instance of the application, but other toolboxes open for another instance of the application. In our discussion here, let's just stick to a single toolbox connected to a single application.
You continue with "you should not mix code of this toolbox" and again, I can only agree as far as solving the case allows. I generally use f<subform name>.Show in my coding, and even though you could write f<subform name>.Visible := True or even F<subform name>.<some public method created for the purpose> to get the same result, I don't see any way to get around referencing the toolbox (the subform in question), when the main program needs to wake it up for some reason.
But was it my callbacks from OnShow and OnHide, you didn't like? Well, I will gladly accept uPatterns' TSubject in order to solve my challenges. But that doesn't change the fact, that there will be calls originated in the toolbox, arriving to the main form via event handlers.
You pinpoint "code reusability". Absolutely, reusability is a very important issue to me. You also warn me about "circular unit reference", but there are no reference from Form2 to Form1 in my sample code. Generally, I tend to avoid circular references as much as possible and I think uPatterns will minimize that need even further in the future.
So, your reference to uPatterns was inspiring, but your uToolbox sent me back to square one (or bug one, if you like), I'm afraid. Try creating an empty main form and just add uToolbox to your project. Run the program and you will see the Toolbox and the main form.
Now, click the application icon in the taskbar and see what happens: The toolbar and the main form are minimized, but the toolbar immediately pops up again.
That is exactly where my problems started, as described in my initial text above. And I still have no idea how to fix it!
I understand that your uToolbox is only created to show the principles, but I read the code as it is and have one major comment: In "constructor TfrmToolbox.Create(aOwner: TComponent)" you set Application.OnActivate and Deactivate. You do save the original values, so you intend to restore them somewhere, but is it reasonable at all, that some inferior subform should mingle with the global Application variable? Well, maybe while being active, but not permanently. You would be in trouble when creating more toolboxes or subforms based on that idea.
Due to this problem, I chose to discard your uToolbox and create one for myself.
I attach a new set of files in which I have changed names to follow my usual naming convention. I have used uPattern as I believe you wanted me to do it. I have added a button to the toolbox in order to get your opinion on the communication between toolbox and main form when the user actually uses the toolbox.
Are you still hanging on? Thanks! I will look forward to hearing from you again.
Ole
ToolboxTesting.dpr
uMain.pas
uMain.dfm
Thanks again for your interest in my problems. I read the article about Observer Patterns you linked to, and I copied your edited copy of uPatterns.
I find the that uPatterns has a very interesting way of solving the problem, that you cannot assign more than one event handler at a time in Delphi (other languages allow this).
Anyway, you sent my memory back to 2013, when I created a TStackableEventHandler which signals the event handler on the top of the stack only. Possible to pick a handler off the stack, of course. In that way, a number of forms in the application might set their individual handlers in FormShow and pick them off in FormHide. Around the same time, I also created a multi-purpose TOneTimer taking care of many different timing needs through one single TTimer component. uPattern's little trick with saving both .Code and .Data from the event handler reminded me of something, I have seen before. Unfortunately, I just don't remember what it was.
But, back from memory lane...
As I said Friday, we agree upon a toolbox being a form in a unit. Bad names in my sample. Form1 should be fMain (or frmMain if you like) and Form2 should be fToolbox (or frmToolbox).
You write that "in reality it should only be a separate unit which you include in the uses list of the project that alone should solve the correct toolbox being active with the active version of the app" and yes, we agree again, except that there must be SOME communication between the main program and the toolbox. HOW that communication is established, I will be happy to discuss further. Actually, I will come back to this a little later.
In the same paragraph, you also wrote "it is possible you have multiple toolboxes open, but clicking on a another toolbox will activate that other application". In theory - for another project - that could be reasonable, but for the large application, I am working on, this is not desired. On the contrary, I might reveal, there are going to be several DIFFERENT toolboxes available, and the user may want some open for one instance of the application, but other toolboxes open for another instance of the application. In our discussion here, let's just stick to a single toolbox connected to a single application.
You continue with "you should not mix code of this toolbox" and again, I can only agree as far as solving the case allows. I generally use f<subform name>.Show in my coding, and even though you could write f<subform name>.Visible := True or even F<subform name>.<some public method created for the purpose> to get the same result, I don't see any way to get around referencing the toolbox (the subform in question), when the main program needs to wake it up for some reason.
But was it my callbacks from OnShow and OnHide, you didn't like? Well, I will gladly accept uPatterns' TSubject in order to solve my challenges. But that doesn't change the fact, that there will be calls originated in the toolbox, arriving to the main form via event handlers.
You pinpoint "code reusability". Absolutely, reusability is a very important issue to me. You also warn me about "circular unit reference", but there are no reference from Form2 to Form1 in my sample code. Generally, I tend to avoid circular references as much as possible and I think uPatterns will minimize that need even further in the future.
So, your reference to uPatterns was inspiring, but your uToolbox sent me back to square one (or bug one, if you like), I'm afraid. Try creating an empty main form and just add uToolbox to your project. Run the program and you will see the Toolbox and the main form.
Now, click the application icon in the taskbar and see what happens: The toolbar and the main form are minimized, but the toolbar immediately pops up again.
That is exactly where my problems started, as described in my initial text above. And I still have no idea how to fix it!
I understand that your uToolbox is only created to show the principles, but I read the code as it is and have one major comment: In "constructor TfrmToolbox.Create(aOwner:
Due to this problem, I chose to discard your uToolbox and create one for myself.
I attach a new set of files in which I have changed names to follow my usual naming convention. I have used uPattern as I believe you wanted me to do it. I have added a button to the toolbox in order to get your opinion on the communication between toolbox and main form when the user actually uses the toolbox.
Are you still hanging on? Thanks! I will look forward to hearing from you again.
Ole
ToolboxTesting.dpr
uMain.pas
uMain.dfm
ASKER
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
lol, lot's of memories
didn't know about the AppActivate and the taskbar button
guess i didnt really see that problem
you have multiple toolboxes in the same app ?
and you want only 1 of them active
2 ways, descend from the same parent class and implement a singleton visible in that
or add an interface to each and apply the singleton visible idea via that interface
didn't know about the AppActivate and the taskbar button
guess i didnt really see that problem
you have multiple toolboxes in the same app ?
and you want only 1 of them active
2 ways, descend from the same parent class and implement a singleton visible in that
or add an interface to each and apply the singleton visible idea via that interface
ASKER
It works!
Geert's comments were inspiring, but did not solve the problem.
Geert's comments were inspiring, but did not solve the problem.
in reality it should only be a separate unit which you include in the uses list of the project
that alone should solve the correct toolbox being active with the active version of the app
it is possible you have multiple toolboxes open, but clicking on a another toolbox will activate that other application
you'd only need to worry about hiding the toolbox when the app is deactivated
more of a nice to have, to minimize confusion
because of this separate unit, you should not mix code of this toolbox in other units
the toolbox unit should be an independant unit
> code resusability
so what you are doing with your references inside Form1 to Form2 is completely wrong
there is some work here to prevent circular unit reference too
like a callback for the menu, or some plain global variables
or even better ... an observer pattern
this is actually a very good idea to use an observer pattern on
the menu needs to be changed based on the state of toolbox
vice versa works too ... the toolbox needs to change state (hidden) when app is deactived
so you need a few observers and subsribers
i'll see if i can create a sample with those ideas