views:

1192

answers:

3

I'm trying to use ChangeDisplaySettingsEx in Delphi 7 to set a specific monitor as Primary. In Windows.pas, it is defined as

function ChangeDisplaySettingsEx(lpszDeviceName: PChar; var lpDevMode: TDeviceMode;
        wnd: HWND; dwFlags: DWORD; lParam: Pointer): Longint; stdcall;

In MSDN, the documentation for ChangeDisplaySettingsEx has the following comment for lpDevMode: "If lpDevMode is NULL, all the values currently in the registry will be used for the display setting."

My objective is to change the Primary monitor on a system with two active monitors, without changing anything else - resolution, bit depth, etc, should all remain the same. It seems like passing lpDevMode as null (nil) is the method that is provided to accomplish this.

However, lpDevMode is defined as a packed record type (TDeviceMode), not a pointer type, in Delphi's Windows.pas. Apparently, the Delphi interface to the Windows API performs the translation to the pointers used by Windows API 'behind the scenes'.

I tried calling it like this:

var
   alldevs : array[0..maxdev] of TDisplayDevice;
   lpDevMode : pointer;

begin
   lpDevMode := nil;
   lparam := nil;
   my_hwnd := nil;

  {... snip....}

  with alldevs[NewPri] do
      ChangeDisplaySettingsEx(devicename,TDeviceMode(lpDevMode),my_hwnd,CDS_SET_PRIMARY,lparam);

That gives me an invalid typecast error on "TDeviceMode(lpDevMode)". How can I pass a null pointer to ChangeDisplaySettingsEx? Or is there a better way to do this?

+1  A: 

You could try

ChangeDisplaySettingsEx(devicename, PDeviceMode(0)^, my_hwnd, CDS_SET_PRIMARY,
  lparam);

it does at least compile on Delphi 2009. I can't test it though.

Edit:

According to the scarce information on the net (this is the most detailed I could find) changing the primary display isn't a simple process, so you may be missing a step along the way. I have two monitors, but can't change the primary device at all, not even with the control panel - looks like the dual-head display card driver doesn't allow it. The following therefore isn't tested, but maybe it will help you:

In order to set a new primary display you have to move the current primary display away from the (0, 0) position first. This is more tricky than it needs to be, because the Delphi Windows.pas file has an incomplete TDeviceMode type. It is given as

_devicemodeA = record
  dmDeviceName: array[0..CCHDEVICENAME - 1] of AnsiChar;
  ...
  dmOrientation: SHORT;
  dmPaperSize: SHORT;
  dmPaperLength: SHORT;
  dmPaperWidth: SHORT;
  dmScale: SHORT;
  dmCopies: SHORT;
  dmDefaultSource: SHORT;
  dmPrintQuality: SHORT;
  dmColor: SHORT;
  ...
end;

when it should instead be

_devicemodeA = record
  dmDeviceName: array[0..CCHDEVICENAME - 1] of AnsiChar;
  ...
  case boolean of
    FALSE: (
      dmOrientation: SHORT;
      dmPaperSize: SHORT;
      dmPaperLength: SHORT;
      dmPaperWidth: SHORT;
      dmScale: SHORT;
      dmCopies: SHORT;
      dmDefaultSource: SHORT;
      dmPrintQuality: SHORT;
    );
    TRUE: (
      dmPosition: TPoint;
      dmDisplayOrientation: DWORD;
      dmDisplayFixedOutput: DWORD;
    );
    dmColor: SHORT;
  ...
end;

You should add the fixed record type to your sources, as you need dmPosition to adjust the origin of the displays. It should go something like this:

// get current display settings
EnumDisplaySettings(PChar(AOldPrimaryDevice), ENUM_REGISTRY_SETTINGS, DevMode1);
EnumDisplaySettings(PChar(ANewPrimaryDevice), ENUM_REGISTRY_SETTINGS, DevMode2);

// move old primary display to new position
DevMode1.dmFields := DM_POSITION;
DevMode1.dmPosition.x := DevMode2.dmPelsWidth;
DevMode1.dmPosition.y := 0;
Win32Check(ChangeDisplaySettingsEx(PChar(AOldPrimaryDevice), DevMode1, 0,
  CDS_UPDATEREGISTRY or CDS_NORESET, nil)):

// move old secondary display to (0, 0) and make the primary display
DevMode2.dmFields := DM_POSITION;
DevMode2.dmPosition.x := 0;
DevMode2.dmPosition.y := 0;
Win32Check(ChangeDisplaySettingsEx(PChar(ANewPrimaryDevice), DevMode2, 0,
  CDS_SET_PRIMARY or CDS_UPDATEREGISTRY or CDS_NORESET or DM_DISPLAYFLAGS, nil)):

// magic ???
Win32Check(ChangeDisplaySettingsEx(nil, PDeviceMode(0)^, 0, 0, nil));
mghie
Thanks for the suggestion. That does compile, but it doesn't actually work. ChangeDisplaySettingsEx returns 0 (success), but the primary monitor remains unchanged.
tim11g
mghie - thanks for the detailed information. I didn't know about having to adjust the display origins before setting the primary. I can't get back to solving this issue right now, but when I do, I'll let you know and post any working code here.
tim11g
A: 

I haven't verified it, but there's a post in the MSDN forums that covers this and includes C++ code. The "primary" monitor is the one at position 0,0, so you need to rearrange the positions of the monitors to make that happen.

Craig Peterson
A: 

Did someone has found a solution for this? It will be nice if the finally code will be published here, because I also search for a solution to make the primary to secondary....

Martin

martin