Board index » delphi » Specifying which form to run on which monitor?

Specifying which form to run on which monitor?

I am trying to figure out how to programmatically specify specific forms
to run on specific monitors.

The TScreen and TMonitor doesn't seem to have the necessary properties
to accomplish this.

For example: I have a main form on which I have 3 buttons to activate 3
different forms. Upon clicking button 1, it should activate Form1 and
run it fullscreen on Monitor 1. Likewise for the other two. I don't want
to have to force the user to start the forms, then manually drag them to
a window.

I notice that TMonitor has a MonitorNum property, but this is read-only.

Likewise, TScreen has a Monitors property, but this is also read-only.

TForm has read-only Monitor property, but it also has a DefaultMonitor
property which can be set. Unfortunately, it can be set to one of only
four enumerated values: dmDesktop, dmPrimary, dmMainForm, dmActiveForm.

None of these help.. I need to have DefaultMonitor (or a property like
it) to be set to an arbitrary number, like 2 or 5...

Please, can anyone help?

Thanks in advance,

Joe

 

Re:Specifying which form to run on which monitor?


"Joseph" <s...@nothere.com> schrieb im Newsbeitrag
news:MPG.17e8816c613c60479896a9@news-server.san.rr.com...

Quote
> I am trying to figure out how to programmatically specify specific forms
> to run on specific monitors.

> The TScreen and TMonitor doesn't seem to have the necessary properties
> to accomplish this.

> For example: I have a main form on which I have 3 buttons to activate 3
> different forms. Upon clicking button 1, it should activate Form1 and
> run it fullscreen on Monitor 1. Likewise for the other two. I don't want
> to have to force the user to start the forms, then manually drag them to
> a window.

> I notice that TMonitor has a MonitorNum property, but this is read-only.

> Likewise, TScreen has a Monitors property, but this is also read-only.

> TForm has read-only Monitor property, but it also has a DefaultMonitor
> property which can be set. Unfortunately, it can be set to one of only
> four enumerated values: dmDesktop, dmPrimary, dmMainForm, dmActiveForm.

> None of these help.. I need to have DefaultMonitor (or a property like
> it) to be set to an arbitrary number, like 2 or 5...

Set the Property "DefaultMonitor" of your form.
You need at least Delphi 5 (or higher) to support multiple
screens.

If you don't see "DefaultProperty" in the object inspector,
use the context menu to enable all categories.

hth
Andreas Schmidt

Re:Specifying which form to run on which monitor?


Yes, but set DefaultMonitor to what value? Say I want Form1 to appear on
Monitor 3..  The four enumerated possibilities for DefaultMonitor are
(dmDesktop, dmPrimary, dmMainForm, dmActiveForm).

Which one of these will correspond to Monitor 3?

Form1.DefaultMonitor :=  ??

Thx,

Joseph

In article <alnmo5$1rb85...@ID-13084.news.dfncis.de>,
a_j_schm...@rocketmail.com says...

Quote

> "Joseph" <s...@nothere.com> schrieb im Newsbeitrag
> news:MPG.17e8816c613c60479896a9@news-server.san.rr.com...
> > I am trying to figure out how to programmatically specify specific forms
> > to run on specific monitors.

> > The TScreen and TMonitor doesn't seem to have the necessary properties
> > to accomplish this.

> > For example: I have a main form on which I have 3 buttons to activate 3
> > different forms. Upon clicking button 1, it should activate Form1 and
> > run it fullscreen on Monitor 1. Likewise for the other two. I don't want
> > to have to force the user to start the forms, then manually drag them to
> > a window.

> > I notice that TMonitor has a MonitorNum property, but this is read-only.

> > Likewise, TScreen has a Monitors property, but this is also read-only.

> > TForm has read-only Monitor property, but it also has a DefaultMonitor
> > property which can be set. Unfortunately, it can be set to one of only
> > four enumerated values: dmDesktop, dmPrimary, dmMainForm, dmActiveForm.

> > None of these help.. I need to have DefaultMonitor (or a property like
> > it) to be set to an arbitrary number, like 2 or 5...

> Set the Property "DefaultMonitor" of your form.
> You need at least Delphi 5 (or higher) to support multiple
> screens.

> If you don't see "DefaultProperty" in the object inspector,
> use the context menu to enable all categories.

> hth
> Andreas Schmidt

Re:Specifying which form to run on which monitor?


"Joseph" <s...@nothere.com> skrev i melding
news:MPG.17e905679c7e22a99896aa@news-server.san.rr.com...

Quote
> Yes, but set DefaultMonitor to what value? Say I want Form1 to appear on
> Monitor 3..  The four enumerated possibilities for DefaultMonitor are
> (dmDesktop, dmPrimary, dmMainForm, dmActiveForm).

> Which one of these will correspond to Monitor 3?

> Form1.DefaultMonitor :=  ??

I was looking throgh this API (user32.ddl, I believe), and it seemed to me
that there is no concept of "Monitor 1", "Monitor 2", etc., her.

  Form1.DefaultMonitor :=  dmDefault;

--
Bj?rge S?ther
bjorge@hahaha_itte.no

Re:Specifying which form to run on which monitor?


Quote
"Joseph" <s...@nothere.com> wrote in message

news:MPG.17e8816c613c60479896a9@news-server.san.rr.com...

Quote
> I am trying to figure out how to programmatically specify specific forms
> to run on specific monitors.

> The TScreen and TMonitor doesn't seem to have the necessary properties
> to accomplish this.

> For example: I have a main form on which I have 3 buttons to activate 3
> different forms. Upon clicking button 1, it should activate Form1 and
> run it fullscreen on Monitor 1. Likewise for the other two. I don't want
> to have to force the user to start the forms, then manually drag them to
> a window.

> I notice that TMonitor has a MonitorNum property, but this is read-only.

> Likewise, TScreen has a Monitors property, but this is also read-only.

> TForm has read-only Monitor property, but it also has a DefaultMonitor
> property which can be set. Unfortunately, it can be set to one of only
> four enumerated values: dmDesktop, dmPrimary, dmMainForm, dmActiveForm.

> None of these help.. I need to have DefaultMonitor (or a property like
> it) to be set to an arbitrary number, like 2 or 5...

look in multimon.pas

or try something like the following:

[ old code cut.n.pasted, extraneous code cut out  ... ]

  TForm1 = class(TForm)
...
    procedure WMSYSCOMMAND(var Message: TWMSYSCOMMAND); message
WM_SYSCOMMAND;
    procedure WMDISPLAYCHANGE(var Message: TWMDISPLAYCHANGE); message
WM_DISPLAYCHANGE;
...
  end;

var myscreen:Tscreen=nil; //3200 bytes or therabouts

// find what monitor form is on
// usually interested in keeping title bar
// visible
// examine only topleft and topright
function whatmonitor(f:tform):integer;
var i:integer;
var p:tpoint;
var r:trect;
begin
  result:=-1;
  if myscreen.MonitorCount> 1 then
  begin
    if f.windowstate = wsmaximized then
      p:=point(f.left+4,f.top+4)
    else
      p:=point(f.left,f.top);
    for i:= 0 to myscreen.MonitorCount-1 do
    begin
      with myscreen.monitors[i] do
        r:=rect(left,top,left+width-1,top+height-1);
      if ptinrect(r,p)
      then begin result:=i; break end;
    end;
    // if topleft not on a monitor, try topright
    if result=-1 then
    begin
      p:=point(f.left+f.width-1,f.top);
      for i:= 0 to myscreen.MonitorCount-1 do
      begin
        with myscreen.monitors[i] do
          r:=rect(left,top,left+width-1,top+height-1);
        if ptinrect(r,p)
        then begin result:=i; break end;
      end;
    end;
  end
  else
    result:=0; // one monitor
end;

procedure sendtomonitor( f:tform; m:integer);
var cm:integer;
var offx,offy:integer;
var r:trect;
var ws:twindowstate;
begin
  if (m>=0) and (m <= myscreen.MonitorCount-1) then
  begin
    ws:=f.WindowState;
    if ws <> wsnormal then f.windowstate:=wsNormal;
    // some (all??) don't render maximised windows
    // properly when shifted
    cm:=whatmonitor(f);
    // if topleft or topright are on a monitor then
    // try and adjust to same co-ords on new monitor
    if (cm <>-1) and (m <> cm) then
    begin
      r:=f.boundsrect;
      offsetrect(r,
        myscreen.monitors[m].left-myscreen.monitors[cm].left,
        myscreen.monitors[m].top-myscreen.monitors[cm].top
        );
      f.boundsrect:=r;
    end;
    cm:=whatmonitor(f);
    // topleft or topright not on a monitor
    // or adjustment overlapped because
    // of size differences
    // abandon all neatness, just force topleft
    // to topleft of target monitor
    if (cm = -1) or (cm <> m ) then
    begin
      r:=f.boundsrect;
      offsetrect(r,
        myscreen.monitors[m].left-r.left,
        myscreen.monitors[m].top-r.top
        );
      f.boundsrect:=r;
    end;
    if f.WindowState<> ws then f.WindowState:=ws;
  end;
end;

procedure Tform1.WMSYSCOMMAND(var Message: TWMSYSCOMMAND);
//message WM_SYSCOMMAND;
var m:integer;
var s:string;
begin
   // if maximised change a restore into
   // 'switch form to next monitor',
   // use title bar doubleclicks to restore
   if message.CmdType = SC_RESTORE then
   begin
     if (WindowState = wsMaximized)
       and ( myscreen.MonitorCount > 1) then
     begin
       m:=whatmonitor(self );
       inc(m);
       if not((m>=0) and (m <= myscreen.MonitorCount-1)) then
         m:=0;
       sendtomonitor(self ,m);
     end
     else inherited;
   end
   else
    inherited;
end;

procedure Tform1.WMDISPLAYCHANGE(var Message: TWMDISPLAYCHANGE);
//message WM_DISPLAYCHANGE;
begin
  myscreen.free;
  myscreen:=tscreen.create(nil);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  myscreen:=tscreen.create(nil);
  sendtomonitor(self,1);  // try and position form on monitor 1
end;

...two questions

1)
Does screen update when your monitor configuration changes?
I had to add checks and use another Tscreen to notice changes. Otherwise
you use the configuration used when app started.
resolution chage, monitor activate/deactivate, position changed etc
don't get noticed, makes for fun window moves

2)
  say you have a maximised form on monitor 1 above monitor 0
  now move it to monitor 0
   rect is (-4,-4,1028,772)   (1024,768  + maximized)
   form2.caption:=recttostr(form1.boundsrect);
  now set topleft to say (-524,-758)

 ...is only the monitor 0 rectangle shown, not what should be seen in upper
monitor 1??
 .. seems to be [ probably a driver] problem on some (all?)
 not really a big problem, moving a maximised form is not really 'done'

tester code

procedure TForm1.WhatTheFeckButtonClick(Sender: TObject);
var i:integer;
begin
         memo1.lines.add(recttostr(form1.boundsrect));
   sendtomonitor(form1,1);
   form1.windowstate:=wsmaximized;
         memo1.lines.add(recttostr(form1.boundsrect));
   form1.left:= -524;
   form1.top:=-758;
         memo1.lines.add(recttostr(form1.boundsrect));
   for i:=1 to 3 do
   begin
     application.processmessages;
     beep; sleep(1000);
   end;
   sendtomonitor(form1,0);
         memo1.lines.add(recttostr(form1.boundsrect));

   application.processmessages;
   sleep(2000);
   form1.left:= -524;
   form1.top:=-758;  //not displayed properly
          memo1.lines.add(recttostr(form1.boundsrect));
   for i:=1 to 3 do
   begin
      application.processmessages;
     beep; sleep(1000);
   end;
   sendtomonitor(form1,1);
        memo1.lines.add(recttostr(form1.boundsrect));
end;

some utilities

function recttostr(r:Trect;
commasep:char=',';brackets:boolean=true):string;overload;
begin
  result:=inttostr(r.left)+commasep+
          inttostr(r.top)+commasep+
          inttostr(r.right)+commasep+
          inttostr(r.bottom);
  if brackets then result:='('+result+')';
end;

function recttostr(left,top,right,bottom:integer;
commasep:char=',';brackets:boolean=true):string;overload;
var s:string;
begin
  result:=inttostr(left)+commasep+
          inttostr(top)+commasep+
          inttostr(right)+commasep+
          inttostr(bottom);
  if brackets then result:='('+result+')';
end;

Re:Specifying which form to run on which monitor?


Thanks.. However, isn't

N := Form1.Monitor.MonitorNum;

an easier way to get the monitor number on which a form resides than the
20 lines of your WhatMonitor function?

All Forma have a TMonitor property, which in turn has a MonitorNum
property, which is unfortunately only read only.

As for the rest of your code, it looks impressive. I'll give it a try.
And hopefully it will help others as it is archived in Google.

THANKS!

Joseph

In article <3d816017$0$16694$afc38...@news.optusnet.com.au>,
trochi...@optusnet.com.au says...

Quote
> "Joseph" <s...@nothere.com> wrote in message
> news:MPG.17e8816c613c60479896a9@news-server.san.rr.com...
> > I am trying to figure out how to programmatically specify specific forms
> > to run on specific monitors.

> > The TScreen and TMonitor doesn't seem to have the necessary properties
> > to accomplish this.

> > For example: I have a main form on which I have 3 buttons to activate 3
> > different forms. Upon clicking button 1, it should activate Form1 and
> > run it fullscreen on Monitor 1. Likewise for the other two. I don't want
> > to have to force the user to start the forms, then manually drag them to
> > a window.

> > I notice that TMonitor has a MonitorNum property, but this is read-only.

> > Likewise, TScreen has a Monitors property, but this is also read-only.

> > TForm has read-only Monitor property, but it also has a DefaultMonitor
> > property which can be set. Unfortunately, it can be set to one of only
> > four enumerated values: dmDesktop, dmPrimary, dmMainForm, dmActiveForm.

> > None of these help.. I need to have DefaultMonitor (or a property like
> > it) to be set to an arbitrary number, like 2 or 5...

> look in multimon.pas

> or try something like the following:

> [ old code cut.n.pasted, extraneous code cut out  ... ]

>   TForm1 = class(TForm)
> ...
>     procedure WMSYSCOMMAND(var Message: TWMSYSCOMMAND); message
> WM_SYSCOMMAND;
>     procedure WMDISPLAYCHANGE(var Message: TWMDISPLAYCHANGE); message
> WM_DISPLAYCHANGE;
> ...
>   end;

> var myscreen:Tscreen=nil; //3200 bytes or therabouts

> // find what monitor form is on
> // usually interested in keeping title bar
> // visible
> // examine only topleft and topright
> function whatmonitor(f:tform):integer;
> var i:integer;
> var p:tpoint;
> var r:trect;
> begin
>   result:=-1;
>   if myscreen.MonitorCount> 1 then
>   begin
>     if f.windowstate = wsmaximized then
>       p:=point(f.left+4,f.top+4)
>     else
>       p:=point(f.left,f.top);
>     for i:= 0 to myscreen.MonitorCount-1 do
>     begin
>       with myscreen.monitors[i] do
>         r:=rect(left,top,left+width-1,top+height-1);
>       if ptinrect(r,p)
>       then begin result:=i; break end;
>     end;
>     // if topleft not on a monitor, try topright
>     if result=-1 then
>     begin
>       p:=point(f.left+f.width-1,f.top);
>       for i:= 0 to myscreen.MonitorCount-1 do
>       begin
>         with myscreen.monitors[i] do
>           r:=rect(left,top,left+width-1,top+height-1);
>         if ptinrect(r,p)
>         then begin result:=i; break end;
>       end;
>     end;
>   end
>   else
>     result:=0; // one monitor
> end;

> procedure sendtomonitor( f:tform; m:integer);
> var cm:integer;
> var offx,offy:integer;
> var r:trect;
> var ws:twindowstate;
> begin
>   if (m>=0) and (m <= myscreen.MonitorCount-1) then
>   begin
>     ws:=f.WindowState;
>     if ws <> wsnormal then f.windowstate:=wsNormal;
>     // some (all??) don't render maximised windows
>     // properly when shifted
>     cm:=whatmonitor(f);
>     // if topleft or topright are on a monitor then
>     // try and adjust to same co-ords on new monitor
>     if (cm <>-1) and (m <> cm) then
>     begin
>       r:=f.boundsrect;
>       offsetrect(r,
>         myscreen.monitors[m].left-myscreen.monitors[cm].left,
>         myscreen.monitors[m].top-myscreen.monitors[cm].top
>         );
>       f.boundsrect:=r;
>     end;
>     cm:=whatmonitor(f);
>     // topleft or topright not on a monitor
>     // or adjustment overlapped because
>     // of size differences
>     // abandon all neatness, just force topleft
>     // to topleft of target monitor
>     if (cm = -1) or (cm <> m ) then
>     begin
>       r:=f.boundsrect;
>       offsetrect(r,
>         myscreen.monitors[m].left-r.left,
>         myscreen.monitors[m].top-r.top
>         );
>       f.boundsrect:=r;
>     end;
>     if f.WindowState<> ws then f.WindowState:=ws;
>   end;
> end;

> procedure Tform1.WMSYSCOMMAND(var Message: TWMSYSCOMMAND);
> //message WM_SYSCOMMAND;
> var m:integer;
> var s:string;
> begin
>    // if maximised change a restore into
>    // 'switch form to next monitor',
>    // use title bar doubleclicks to restore
>    if message.CmdType = SC_RESTORE then
>    begin
>      if (WindowState = wsMaximized)
>        and ( myscreen.MonitorCount > 1) then
>      begin
>        m:=whatmonitor(self );
>        inc(m);
>        if not((m>=0) and (m <= myscreen.MonitorCount-1)) then
>          m:=0;
>        sendtomonitor(self ,m);
>      end
>      else inherited;
>    end
>    else
>     inherited;
> end;

> procedure Tform1.WMDISPLAYCHANGE(var Message: TWMDISPLAYCHANGE);
> //message WM_DISPLAYCHANGE;
> begin
>   myscreen.free;
>   myscreen:=tscreen.create(nil);
> end;

> procedure TForm1.FormCreate(Sender: TObject);
> begin
>   myscreen:=tscreen.create(nil);
>   sendtomonitor(self,1);  // try and position form on monitor 1
> end;

> ...two questions

> 1)
> Does screen update when your monitor configuration changes?
> I had to add checks and use another Tscreen to notice changes. Otherwise
> you use the configuration used when app started.
> resolution chage, monitor activate/deactivate, position changed etc
> don't get noticed, makes for fun window moves

> 2)
>   say you have a maximised form on monitor 1 above monitor 0
>   now move it to monitor 0
>    rect is (-4,-4,1028,772)   (1024,768  + maximized)
>    form2.caption:=recttostr(form1.boundsrect);
>   now set topleft to say (-524,-758)

>  ...is only the monitor 0 rectangle shown, not what should be seen in upper
> monitor 1??
>  .. seems to be [ probably a driver] problem on some (all?)
>  not really a big problem, moving a maximised form is not really 'done'

> tester code

> procedure TForm1.WhatTheFeckButtonClick(Sender: TObject);
> var i:integer;
> begin
>          memo1.lines.add(recttostr(form1.boundsrect));
>    sendtomonitor(form1,1);
>    form1.windowstate:=wsmaximized;
>          memo1.lines.add(recttostr(form1.boundsrect));
>    form1.left:= -524;
>    form1.top:=-758;
>          memo1.lines.add(recttostr(form1.boundsrect));
>    for i:=1 to 3 do
>    begin
>      application.processmessages;
>      beep; sleep(1000);
>    end;
>    sendtomonitor(form1,0);
>          memo1.lines.add(recttostr(form1.boundsrect));

>    application.processmessages;
>    sleep(2000);
>    form1.left:= -524;
>    form1.top:=-758;  //not displayed properly
>           memo1.lines.add(recttostr(form1.boundsrect));
>    for i:=1 to 3 do
>    begin
>       application.processmessages;
>      beep; sleep(1000);
>    end;
>    sendtomonitor(form1,1);
>         memo1.lines.add(recttostr(form1.boundsrect));
> end;

> some utilities

> function recttostr(r:Trect;
> commasep:char=',';brackets:boolean=true):string;overload;
> begin
>   result:=inttostr(r.left)+commasep+
>           inttostr(r.top)+commasep+
>           inttostr(r.right)+commasep+
>           inttostr(r.bottom);
>   if brackets then result:='('+result+')';
> end;

> function recttostr(left,top,right,bottom:integer;
> commasep:char=',';brackets:boolean=true):string;overload;
> var s:string;
> begin
>   result:=inttostr(left)+commasep+
>           inttostr(top)+commasep+
>           inttostr(right)+commasep+
>           inttostr(bottom);
>   if brackets then result:='('+result+')';
> end;

Re:Specifying which form to run on which monitor?


Quote
"Joseph" <s...@nothere.com> wrote in message

news:MPG.17eb23c62caf7fb19896ab@news-server.san.rr.com...

Quote
> Thanks.. However, isn't

> N := Form1.Monitor.MonitorNum;

> an easier way to get the monitor number on which a form resides than the
> 20 lines of your WhatMonitor function?

> All Forma have a TMonitor property, which in turn has a MonitorNum
> property, which is unfortunately only read only.

'something like' is a suggestion, 'this is the pile of fetid sheep droppings
I wrote late one night while sick with the 'flu, there may be better ways'
;-)

For all I know there is a nice windows API for it. Info often takes
longer to find than writing a utility function to do the same thing.

.. of course then you run into the following maintenance problem:
I think I cut the wrong things
comment out 3 lines

function whatmonitor(f:tform):integer;
begin
//  if myscreen.MonitorCount> 1 then
   ....
//  else
//    result:=0; // one monitor
end;

Monitor.MonitorNum
doesn't return quite what I wanted, I wanted to know where the
title bar was , and whether actually visible. monitornum
works on the form centre and gives the nearest monitor even if the
entire form is off desktop.
Moving a form could place the titlebar in limbo, I wanted
an easy check. whatmonitor=-1

(***!!!***)
I think the main reason to replace it was:
  (for example) 2 monitors, second disabled.
  run app
 enable second monitor, screen not updated
 move form to 'new' monitor'  form.monitor returns nil
 and trying to get monitornum causes AV .

Maybe this is system specific, maybe it is fixed in patches or new
versions, don't know. don't care , much ;-)

I looked at updating Screen but changes to it were mentioned in
a few places, so went with a seperate copy for convenience.

Quote
> And hopefully it will help others as it is archived in Google.

thats the idea ;-), a couple of hits and things people find a
common problem soon emerge from the clutter
  or faulty data gets used more often , if google says it 30 times
it must be true ;-)

Re:Specifying which form to run on which monitor?


In article <3d829c4f$0$22176$afc38...@news.optusnet.com.au>, "Terry Russell"

Quote
<trochi...@optusnet.com.au> writes:
>For all I know there is a nice windows API for it. Info often takes
>longer to find than writing a utility function to do the same thing.

Like ...

EnumDisplayMonitors
Enumerates display monitors that intersect a region formed by the intersection
of a specified clipping rectangle and the visible region of a device context.

GetMonitorInfo
Retrieves information about a display monitor.

MonitorEnumProc
An application-defined callback function that is called by the
EnumDisplayMonitors function
.
MonitorFromPoint
Retrieves a handle to the display monitor that contains a specified point.

MonitorFromRect
Retrieves a handle to the display monitor that has the largest area of
intersection with a specified rectangle
.
MonitorFromWindow
Retrieves a handle to the display monitor that has the largest area of
intersection with the bounding rectangle of a specified window.

In MSDN these are under "Multiple Display Monitors Reference"

Alan Lloyd
alangll...@aol.com

Re:Specifying which form to run on which monitor?


Quote
"AlanGLLoyd" <alangll...@aol.com> wrote in message

news:20020914063923.22814.00000322@mb-co.aol.com...
Quote
> In article <3d829c4f$0$22176$afc38...@news.optusnet.com.au>, "Terry
Russell"
> <trochi...@optusnet.com.au> writes:

> >For all I know there is a nice windows API for it. Info often takes
> >longer to find than writing a utility function to do the same thing.

> Like ...

> EnumDisplayMonitors
> Enumerates display monitors that intersect a region formed by the
intersection
> of a specified clipping rectangle and the visible region of a device

context.

I know, found in multimon.pas, you still have to convert the results to
monitors,
I think it seemed easier to let a new Tscreen do most of the work, and was
originally trying to get around the Screen. AV when monitorcount changed.
Now I see no point in rewriting it.

I meant an API to do like customform.setwindowtomonitor but with
numbers. Couldn't find one in 5 minutes, {*word*76}y poor eyesight makes
scanning docs and websites a pain, easier to write a few lines of code
and use another 0.05 cents worth  of virtual memory.

Other Threads