windows - How to correctly have modeless form appear in taskbar -
i trying achieve age-old delphi dream of having modeless form appear in taskbar.
what correct way have modeless form appear in taskbar?
research effort
these attempts solve problem. there lot of things needed make behave correctly - having button appear on taskbar not solution. having windows application behave correctly windows application should goal.
for know me, , how deep "shows research effort" goes, hang on because wild ride down rabbit hole.
the question in title, above horizontal line above. below serves show why on oft-repeated suggestions incorrect.
windows creates taskbar button unowned windows
initially have "main form", show other modeless form:
procedure tfrmmain.button2click(sender: tobject); begin if frmmodeless = nil application.createform(tfrmmodeless, frmmodeless); frmmodeless.show; end;
this correctly shows new form, no new button appears on taskbar:
the reason no taskbar button created because design. windows show taskbar button window "unowned". modeless delphi form owned. in case owned application.handle
:
my project's name modelessformfail.dpr
, origin of windows class name modelessformfail
associated owner.
fortunately there way force windows create taskbar button window, though window owned:
just use ws_ex_appwindow
the msdn documentation of ws_ex_appwindow
says it:
ws_ex_appwindow
0x00040000l
forces top-level window onto taskbar when window visible.
it well-known delphi trick override createparams
, manually add ws_ex_appwindow
style:
procedure tfrmmodeless.createparams(var params: tcreateparams); begin inherited; params.exstyle := params.exstyle or ws_ex_appwindow; //force owned window appear in taskbar end;
when run this, newly created modeless form does indeed own taskbar button:
and we're done? no, because doesn't behave correctly.
if user clicks on frmmain taskbar button, window not brought forward. instead other form (frmmodeless) brought forward:
this makes sense once understand windows concept of ownership. windows will, design, bring child owned forms forward. entire purpose of ownership - keep owned forms on top of owners.
make form unowned
the solution, as of know not fight against taskbar heuristics , windows. if want form unowned, make unowned.
this (fairly) simple. in createparam
force owner windows null
:
procedure tfrmmodeless.createparams(var params: tcreateparams); begin inherited; //doesn't work, because form still owned // params.exstyle := params.exstyle or ws_ex_appwindow; //force owned windows appear in taskbar //make form unonwed; it's want params.wndparent := 0; //unowned. unowned windows naturally appear on taskbar. //there may way simulate popupparent , popupmode. end;
as aside, wanted investigate there way use popupmode
, popupparent
properties make window unowned. swear read comment (from david) somewhere on saying if passed self
popupparent
, e.g.:
procedure tfrmmain.button1click(sender: tobject); begin if frmmodeless = nil begin application.createform(tfrmmodeless, frmmodeless); frmmodeless.popupparent := frmmodeless; //the super-secret way "unowned"? swear david heffernan mentioned somewhere on so, damned if can find now. frmmodeless.popupmode := pmexplicit; //happens automatically when set popupparent, idea end; frmmodeless.show; end;
it supposed super-secret way indicate delphi want form have "no owner". cannot find comment anywhere on now. unfortunately, no combination of popupparent
, popupmode
cause form actually un-owned:
- popupmode: pmnone
- owner hwnd:
application.handle/application.mainform.handle
- owner hwnd:
- popupmode: pmauto
- owner hwnd:
screen.activeform.handle
- owner hwnd:
- popupmode: pmexplicit
- popupparent: nil
- owner hwnd:
application.mainform.handle
- owner hwnd:
- popupparent:
aform
- owner hwnd:
aform.handle
- owner hwnd:
- popupparent: self
- owner hwnd:
application.mainform.handle
- owner hwnd:
- popupparent: nil
nothing could cause form have no owner (each time checking spy++).
setting wndparent
manually during createparams
:
- does make form unowned
- it does have taskbar button
- and both taskbar buttons dobehave correctly:
and we're done, right? thought so. changed use new technique.
except there problems fix seem cause other problems - delphi didn't me changing ownership of form.
hint windows
one of controls on modeless window has tooltop:
the problem when tooltip window appears, causes other form (frmmain, modal one) come forward. doesn't gain activation focus; obscure form at:
the reason logical. delphi hintwindow owned either application.handle
or application.mainform.handle
, rather being owned form should owned by:
i have considered bug on delphi's part; using wrong owner.
diversion see actual app layout
now it's important take moment show application isn't main form , modeless form:
it's actually:
- a login screen (a sacrificial main form gets hidden)
- a main screen
- a modal control panel
- that shows modeless form
even reality of application layout, except hint window ownership works. there 2 taskbar buttons, , clicking them brings proper form forward:
but still have problem of hintwindow ownership bringing wrong form forward:
showmainformontaskbar
it when attempting create minimal application reproduce problem when realize couldn't. there different:
- between delphi 5 application ported xe6
- a new application created in xe6
after comparing everything, traced down fact new applications in xe6 add mainformontaskbar := true
default in new project (presumably not break existing applications):
program modelessformfail; //... begin application.initialize; application.mainformontaskbar := true; application.createform(tfrmsacrificialmain, frmsacrificialmain); //application.createform(tfrmmain, frmmain); application.run; end.
when added option, appearance of tooltip didn't bring wrong form forward!:
success! except, people know what's coming know what's coming. "sacrificial" main login form shows "real" main form, hiding itself:
procedure tfrmsacrificialmain.button1click(sender: tobject); var frmmain: tfrmmain; begin frmmain := tfrmmain.create(application); self.hide; try frmmain.showmodal; self.show; end; end;
when happens, , "login", taskbar icon disappers entirely:
this happens because:
- the un-owned sacrificial main form not invisible: button goes it
- the real main form owned not toolbar button
use ws_app_appwindow
now have opportunity use ws_ex_appwindow
. want force main form, owned, appear on taskbar. override createparams
, force appear on taskbar:
procedure tfrmmain.createparams(var params: tcreateparams); begin inherited; params.exstyle := params.exstyle or ws_ex_appwindow; //force owned window appear in taskbar end;
and give whirl:
looking pretty good!
- two taskbar buttons
- the tooltip doesn't pop wrong owner form forward
except, when click on first toolbar button, wrong form comes up. shows modal frmmain, rather modal frmcontrolpanel:
presumably because newly created frmcontrolpanel popupparented application.mainform rather screen.activeform. check in spy++:
yes, parent mainform.handle
. turns out because of bug in vcl. if form's popupmode
is:
- pmauto
- pmnone (if it's modal form)
the vcl attempts use application.activeformhandle
hwndparent
. unfortunately checks if modal form's parent enabled:
if (wndparent <> 0) , ( isiconic(wndparent) or not iswindowvisible(wndparent) or not iswindowenabled(wndparent))
of course modal form's parent not enabled. if was, not modal form. vcl falls using:
wndparent := application.mainformhandle;
manual parenting
this means have sure manually(?) set popup parenting?
procedure tfrmmain.button2click(sender: tobject); var frmcontrolpanel: tfrmcontrolpanel; begin frmcontrolpanel := tfrmcontrolpanel.create(application); try frmcontrolpanel.popupparent := self; frmcontrolpanel.popupmode := pmexplicit; //automatically set pmexplicit when set popupparent. idea. frmcontrolpanel.showmodal; frmcontrolpanel.free; end; end;
except didn't work either. clicking first taskbar button causes wrong form activate:
at point i'm thoroughly confused. parent of modal form should frmmain, , is!:
so now?
i have sense of might going on.
that taskbar button representation of frmmain. windows bringing forward.
except behaved correctly when mainformontaskbar set false.
there must magic in delphi vcl caused correctness before, gets disabled mainformontaskbar := true, it?
i not first person want delphi application behave nicely windows 95 toolbar. , i've asked question in past, answers geared towards delphi 5 , it's old central routing window.
i've been told fixed around delphi 2007 timeframe.
so correct solution?
bonus reading
- http://blogs.msdn.com/b/oldnewthing/archive/2003/12/29/46371.aspx
- what ws_ex_appwindow do?
- my detail form hidden behind main form when calling tsavedialog
- the oracle @ delphi blog: popupmode , popupparent
- docwiki: vcl.forms.tform.popupmode
- docwiki: vcl.forms.tcustomform.popupparent
- how can start delphi application hidden main form?
it seems me fundamental problem main form is, in eyes of vcl, not main form. once fix that, problems go away.
you should:
- call
application.createform
once, real main form. rule follow. consider job ofapplication.createform
to create main form of application. - create login form , set
wndparent
0
. makes sure appears on taskbar. show modally. - create main form in usual way calling
application.createform
. - set
mainformontaskbar
true
. - set
wndparent
0
modeless form.
and that's it. here's complete example:
project1.dpr
program project1; uses vcl.forms, umain in 'umain.pas' {mainform}, ulogin in 'ulogin.pas' {loginform}, umodeless in 'umodeless.pas' {modelessform}; {$r *.res} begin application.initialize; application.showhint := true; application.mainformontaskbar := true; tloginform.create(application) begin showmodal; free; end; application.createform(tmainform, mainform); application.run; end.
ulogin.pas
unit ulogin; interface uses winapi.windows, winapi.messages, system.sysutils, system.variants, system.classes, vcl.graphics, vcl.controls, vcl.forms, vcl.dialogs; type tloginform = class(tform) protected procedure createparams(var params: tcreateparams); override; end; implementation {$r *.dfm} procedure tloginform.createparams(var params: tcreateparams); begin inherited; params.wndparent := 0; end; end.
ulogin.dfm
object loginform: tloginform left = 0 top = 0 caption = 'loginform' clientheight = 300 clientwidth = 635 color = clbtnface font.charset = default_charset font.color = clwindowtext font.height = -11 font.name = 'ms sans serif' font.style = [] oldcreateorder = false pixelsperinch = 96 textheight = 13 end
umain.pas
unit umain; interface uses winapi.windows, winapi.messages, system.sysutils, system.variants, system.classes, vcl.graphics, vcl.controls, vcl.forms, vcl.dialogs, vcl.stdctrls, umodeless; type tmainform = class(tform) button1: tbutton; procedure button1click(sender: tobject); end; var mainform: tmainform; implementation {$r *.dfm} procedure tmainform.button1click(sender: tobject); begin tmodelessform.create(self) begin show; end; end; end.
umain.dfm
object mainform: tmainform left = 0 top = 0 caption = 'mainform' clientheight = 300 clientwidth = 635 color = clbtnface font.charset = default_charset font.color = clwindowtext font.height = -11 font.name = 'ms sans serif' font.style = [] oldcreateorder = false pixelsperinch = 96 textheight = 13 object button1: tbutton left = 288 top = 160 width = 75 height = 23 caption = 'button1' taborder = 0 onclick = button1click end end
umodeless.pas
unit umodeless; interface uses winapi.windows, winapi.messages, system.sysutils, system.variants, system.classes, vcl.graphics, vcl.controls, vcl.forms, vcl.dialogs, vcl.stdctrls; type tmodelessform = class(tform) label1: tlabel; protected procedure createparams(var params: tcreateparams); override; end; implementation {$r *.dfm} procedure tmodelessform.createparams(var params: tcreateparams); begin inherited; params.wndparent := 0; end; end.
umodeless.dfm
object modelessform: tmodelessform left = 0 top = 0 caption = 'modelessform' clientheight = 300 clientwidth = 635 color = clbtnface font.charset = default_charset font.color = clwindowtext font.height = -11 font.name = 'ms sans serif' font.style = [] oldcreateorder = false showhint = true pixelsperinch = 96 textheight = 13 object label1: tlabel left = 312 top = 160 width = 98 height = 13 hint = 'this hint' caption = 'i'#39'm label hint' end end
if you'd rather modeless form owned main form, can achieve replacing tmodelessform.createparams
with:
procedure tmodelessform.createparams(var params: tcreateparams); begin inherited; params.exstyle := params.exstyle or ws_ex_appwindow; end;
Comments
Post a Comment