Использование TScreen в Delphi 7

Мое приложение Delphi-7 отображает:

Screen.DesktopWidth  
Screen.DesktopHeight  
Screen.Monitors[0].Width  
Screen.Monitors[0].Height  

и, если выбран второй монитор, также:

Screen.Monitors[1].Width  
Screen.Monitors[1].Height  

Когда приложение запущено на моем ПК с WinXP-Pro, я захожу в Панель управления/Дисплей/Настройки и меняю настройки второго монитора (добавляю или удаляю его).

Затем я нажимаю кнопку «Обновить», чтобы отобразить новые значения 4 (или 6) параметров, и происходит что-то неожиданное: Screen.DesktopWidth и Screen.DesktopHeight показывают правильные новые значения, но значения других 2 (или 4) параметров параметры очень неправильные.

Вроде Screen.Monitors[0].Width = 5586935 , а должно быть 1680 .

Существуют ли особые правила использования TScreen в Delphi 7?


person Ruud Schmeitz    schedule 23.06.2012    source источник
comment
Я не могу смоделировать это, так как у меня есть один монитор и Delphi 2009, но я предполагаю, что проблема может быть связана с обновлением списка мониторов (в Delphi 2009 это делается с помощью частной процедуры Screen.GetMonitors). Я предполагаю, что вы получаете правильные значения при перезапуске приложения, не так ли? И если я правильно это помню, возможно, Сертак где-то написал, что можно безопасно уничтожить экземпляр Screen и создать его снова. А раз так, то следующее должно обновить эти данные Screen.Free; Screen := TScreen.Create(nil);, но я действительно не знаю, насколько безопасно это действие.   -  person TLama    schedule 23.06.2012
comment
Вы берете ссылку на экземпляр TMonitor Screen.Monitors[0] или каждый раз получаете Screen.Monitors[0]?   -  person David Heffernan    schedule 24.06.2012
comment
@TLama Я думаю, вы получаете правильные значения при перезапуске приложения, не так ли? Вот так . И я также получаю правильные значения, когда есть оператор ShowMessage непосредственно перед операторами, которые отображают 4 (или 6) параметров.   -  person Ruud Schmeitz    schedule 24.06.2012
comment
ShowMessage приведет к перекачиванию очереди сообщений. Но я бы не ожидал, что сообщения в очереди сыграют здесь роль.   -  person David Heffernan    schedule 24.06.2012
comment
Кстати, значения в окне ShowMessage по-прежнему неверны. Но после нажатия на кнопку OK в этом окне окно приложения отображает правильные значения.   -  person Ruud Schmeitz    schedule 24.06.2012
comment
@ Дэвид Хеффернан Нет ссылки. Я всегда получаю Screen.Monitors[0] и т.д.   -  person Ruud Schmeitz    schedule 24.06.2012
comment
Кстати 2: я только что скомпилировал код в Delphi 2010, и что касается проблемы с неправильными значениями, результирующее приложение ведет себя точно так же, как и приложение, скомпилированное в Delphi 7.   -  person Ruud Schmeitz    schedule 24.06.2012
comment
@TLama - На самом деле я добавляю/удаляю дополнительный монитор на Screen.Desktop . Первоначальное (быстрое и грязное) приложение было написано для проверки вторичного монитора на наличие дефектных (битых/горячих/застрявших) пикселей.   -  person Ruud Schmeitz    schedule 24.06.2012
comment
Итак, я думаю, что ваш основной монитор изменил свой дескриптор, и функция (которая внутренне вызывает GetMonitorInfo) не работает из-за не фактического дескриптора (и возвращает случайные значения). Похоже, проблема связана со списком мониторов (TScreen.FMonitors), который кэшируется и не меняется (в любое время? Я должен посмотреть...). А пока попробуйте проверить значение Monitor.Width (без Screen раньше, только Monitor.Width). Это должно быть между прочим. обновить кэшированный список Screen.Monitors, если монитор, полученный вызовом MonitorFromWindow, не содержится в этом списке.   -  person TLama    schedule 24.06.2012
comment
Кстати, ShowMessage проверяет информацию монитора (которая автоматически обновляет ее), чтобы определить, где показывать окно сообщения, поэтому он работает с ShowMessage.   -  person    schedule 24.06.2012
comment
@hvd, да, TTaskMessageDialog.DoOnDialogCreated, вызываемый внутри цепочки вызовов ShowMessage, получает информацию о мониторе, но у него есть два недостатка. Во-первых, он ничего не делает со списком Screen.FMonitors, который необходимо обновить, чтобы отразить изменения монитора (он просто локально получает информацию для себя), а во-вторых, будет слишком поздно (как бы вы хотели передать эти значения в функцию ShowMessage , даже если он будет обновлять список мониторов, вам придется вызывать его один раз для обновления изменений и второй раз для отображения текста с метриками).   -  person TLama    schedule 24.06.2012
comment
@TLama Я не предлагал это как исправление, я просто пытался объяснить поведение, упомянутое в предыдущем комментарии.   -  person    schedule 25.06.2012


Ответы (3)


Пришел сюда из-за проблемы обновления (ошибки) TScreen при подключении или отключении монитора или USB-устройства отображения. Ответ @Dave82 у меня не работает. Результат функции MonitorFromWindow должен возвращать другое значение (неизвестное/недопустимое значение), чтобы вызвать обновление объекта TScreen.

Этот чит ниже делает свое дело:

Убедитесь, что multimon находится в разделе uses:

uses
 multimon;

Добавьте это в часть interface (формы)

protected
procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE;

Добавьте это в часть реализация (формы)

    function cheatMonitorFromWindow(hWnd: HWND; dwFlags: DWORD): HMONITOR; stdcall;
    begin
      // Does nothing, returns zero to force invalidate
     Result:=0;
    end;

    procedure TForm1.WMDeviceChange(var Msg: TMessage);
    var
     iCurrDisplayCount    : LongInt;
     iNewDisplayCount     : LongInt;
     pMonitorFromWinProc  : TMonitorFromWindow;

    begin
     iCurrDisplayCount:=Screen.MonitorCount;
     // Force monitor update, fix bug in customform, won't update at display change.
     // This a hack/cheat to multimon MonitorFromWindow func, it's fakes the result.
     // This is required to tell customform.getMonitor() to update the TScreen object.
     pMonitorFromWinProc:=MonitorFromWindow;      // Backup pointer to dynamic assigned DLL func  
     MonitorFromWindow:=cheatMonitorFromWindow;   // Assign cheat func 
     monitor;                                     // call the monitor property that calls customform.getMonitor and cheatfunc
     MonitorFromWindow:=pMonitorFromWinProc;      // restore the original func
     // ==========
     iNewDisplayCount:=Screen.MonitorCount;
     if( iCurrDisplayCount <> iNewDisplayCount ) then
     begin
       // Display count change!
     end;  
end;

Что происходит внутри пользовательской формы (код в Forms.pas)?

function TCustomForm.GetMonitor: TMonitor;
var
  HM: HMonitor;
  I: Integer;
begin
  Result := nil;
  HM := MonitorFromWindow(Handle, MONITOR_DEFAULTTONEAREST);
  for I := 0 to Screen.MonitorCount - 1 do
    if Screen.Monitors[I].Handle = HM then
    begin
      Result := Screen.Monitors[I];
      Exit;
    end;

  //if we get here, the Monitors array has changed, so we need to clear and reinitialize it
  for i := 0 to Screen.MonitorCount-1 do
    TMonitor(Screen.FMonitors[i]).Free;
  Screen.FMonitors.Clear;
  EnumDisplayMonitors(0, nil, @EnumMonitorsProc, LongInt(Screen.FMonitors));
  for I := 0 to Screen.MonitorCount - 1 do
    if Screen.Monitors[I].Handle = HM then
    begin
      Result := Screen.Monitors[I];
      Exit;
    end;    
end;

Надеюсь, это поможет, когда кто-то ищет это. Если вы хотите обнаружить изменения настроек устройства отображения (разрешение и ориентация), вместо этого перехватывайте событие WM_DISPLAYCHANGE.

person Codebeat    schedule 14.09.2015
comment
Спасибо Кодбит! Я искал решение этой ошибки более 20 лет :-) Я до сих пор использую Delphi 5 для старых проектов. Но мне пришлось переместить функцию EnumMonitorsProc в начало исходного кода. - person Sylvio Ruiz Neto; 26.08.2019

Массив Screen.Monitors содержит недопустимые значения, если вы переключаете пользователя во время работы вашей программы. Мы используем эту строку кода, чтобы заставить объект Screen обновлять списки:

Screen.MonitorFromWindow(0, mdNull);
person Dave82    schedule 08.08.2014
comment
Это сработало для меня. Давайте посмотрим, работает ли это также для USB-мониторов.... - person Z80; 27.08.2020

Благодаря TLama я нашел обходной путь для проблемы с TScreen в Delphi 7.

Исходный код, который «вызвал» проблему:

LabMon1.Caption := ' Mon 1: ' + IntToStr (Screen.Monitors[0].Width) +
                   ' x ' + IntToStr (Screen.Monitors[0].Height);

if (Screen.MonitorCount = 1)
then LabMon2.Caption := ' Mon 2: -'
else LabMon2.Caption := ' Mon 2: ' + IntToStr (Screen.Monitors[1].Width) +
                        ' x ' + IntToStr (Screen.Monitors[1].Height);

Мне нужно было только добавить 1 строку кода, чтобы решить эту проблему:

LabMon1.Caption := ' Mon 1: ' + IntToStr (Monitor.Width) +
                   ' x ' + IntToStr (Monitor.Height) ;

LabMon1.Caption := ' Mon 1: ' + IntToStr (Screen.Monitors[0].Width) +
                   ' x ' + IntToStr (Screen.Monitors[0].Height);

if (Screen.MonitorCount = 1)
then LabMon2.Caption := ' Mon 2: -'
else LabMon2.Caption := ' Mon 2: ' + IntToStr (Screen.Monitors[1].Width) +
                        ' x ' + IntToStr (Screen.Monitors[1].Height);

Итак, еще раз спасибо, TLama, за ваш большой вклад в эту ветку вопросов!

person Ruud Schmeitz    schedule 24.06.2012