FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin para Harbour/xHarbour Monitores 4K, programas FiveWin borrosos
Posts: 230
Joined: Sat Apr 19, 2008 10:28 PM
Monitores 4K, programas FiveWin borrosos
Posted: Thu Nov 13, 2025 04:25 PM

Buenas tardes

Los monitores 4k se venden cada vez más. Pronto todos los monitores serán 4k. Estoy haciendo pruebas con un monitor de 27 pulgadas 4K 3840x2160 pixeles. El escalado en la configuración de Windows 11 lo he puesto en 175%. En programas hechos con FWH 64 bits, los textos se ven bastante borrosos, prácticamente inusable. He probado a activar dpiAwareness como se explica en otro hilo añadiendo esto al manifiesto.

<asmv3:application>
	<asmv3:windowsSettings>
		<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
		<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
	</asmv3:windowsSettings>
</asmv3:application>

Con dpiAwareness activado la nitidez es excelente, pero el problema entonces es que las ventanas y los textos se ven muy pequeños. Nítidos pero muy pequeños. Sin embargo los diálogos se ven bien de tamaño y resolución. Los diálogos (resource dialogs) sí se escalan, porque Windows los reescala, pero las ventanas FiveWin puras (definidas en código) no.

Tengo FWH64 versión 2310. ¿En versiones más modernas está solucionado esto? ¿Hay alguna otra forma de solucionarlo?

Muchas gracias y un saludo,

Alvaro

Posts: 8515
Joined: Tue Dec 20, 2005 07:36 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Thu Nov 13, 2025 05:53 PM

Buenas tardes, ¿sería posible mostrar imágenes de cómo se ven las pantallas y cómo lo están haciendo? Puede que me equivoque, pero esto no tiene nada que ver con el archivo MANIFEST, sino con el tamaño de la fuente y del diálogo para cada resolución. Es solo una idea.

Gracias, tks.

Reagards, saludos.

João Santos - São Paulo - Brasil - Phone: +55(11)95150-7341
Posts: 230
Joined: Sat Apr 19, 2008 10:28 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Fri Nov 14, 2025 12:39 PM

Hola Karinha,

Gracias por contestar. Cualquier ventana que se crea en five win se ve muy pequeña si se activa el dpiAwareness. Por ejemplo esta es la ventana de error que lanza fivewin. Detrás puedes ver las letra tamaño normal y leíble de vscode. Es decir, los textos de la ventana de error deberían ser del tamaño de los de vscode que se ve detrás (configuración de font 14 en vscode). Fivewin también está a font 14 y ves la diferencia de tamaño cuando se activa dpiAwareness.

Así salen las ventanas con dpiAwareness desactivado, el tamaño del font es el correcto pero se ve borroso. Es decir con dpiAwareness activado se ve nítido, pero las ventanas no se escalan, hay que escalarlas manualmente en el código. Sin embargo los diálogos sí que se escalan en ambos casos.

Con código se puedo solucionar. Ya lo estoy corrigiendo haciendo una rutina que detecte la resolución y la escala y aplicando un factor al tamaño de las fuentes, barra de botones, tamaños de ventanas, etc. Pero en sistemas con dos monitores, si arrastras la app de un monitor 4k a otro HD deja de verse bien. ¿Hay previsión de que fivewin lo gestione?

Gracias.

Alvaro

Posts: 8515
Joined: Tue Dec 20, 2005 07:36 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Fri Nov 14, 2025 04:05 PM

Sugerencia:

https://forums.fivetechsupport.com/viewtopic.php?p=206593#p206593

FUNCTION My_Dialogo_Pinta( hDC )

   LOCAL nColor
   LOCAL nLeft   := 75 // 80 //290
   LOCAL nRight  := 220
   LOCAL cTexto  := "AMBIENTY PAISAGISMO"
   LOCAL oFont
   LOCAL hFont
   LOCAL nLen    := LEN( cTexto )
   LOCAL cLetra, n
   LOCAL nResHoriz, nResVert

   nResHoriz := oWnd:nHorzRes() // retorna a resolucao horizontal
   nResVert  := oWnd:nVertRes() // retorna a resolucao vertical

   IF nResHoriz = 1366 .AND. nResVert = 768

  DEFINE FONT oFont NAME "Segoe UI Symbol" SIZE 0, -90

  hFont := SelectObject( hDC, oFont:hFont )

   ELSEIF nResHoriz = 1360 .AND. nResVert = 768

  DEFINE FONT oFont NAME "Segoe UI Symbol" SIZE 0, -80

  hFont := SelectObject( hDC, oFont:hFont )

   ELSEIF nResHoriz = 1280 .AND. nResVert = 1024

  DEFINE FONT oFont NAME "Segoe UI Symbol" SIZE 0, -70

  hFont := SelectObject( hDC, oFont:hFont )

   ELSEIF nResHoriz = 1280 .AND. nResVert = 768

  DEFINE FONT oFont NAME "Segoe UI Symbol" SIZE 0, -60

  hFont := SelectObject( hDC, oFont:hFont )

   ELSEIF nResHoriz = 1280 .AND. nResVert = 720

  DEFINE FONT oFont NAME "Segoe UI Symbol" SIZE 0, -50

  hFont := SelectObject( hDC, oFont:hFont )


   ELSEIF nResHoriz = 1024 .AND. nResVert = 768

  DEFINE FONT oFont NAME "Segoe UI Symbol" SIZE 0, -40

  hFont := SelectObject( hDC, oFont:hFont )

   ELSE

  DEFINE FONT oFont NAME "Segoe UI Symbol" SIZE 0, -30

  hFont := SelectObject( hDC, oFont:hFont )

   ENDIF

   DEFINE DIALOGO oDlg... FONT oFont...

RETURN NIL

/*
F GetSysMetrics(0) == 800 // Largura em pixels
   // Executa ações para resolução de 800
ENDIF
*/

/*
IF GetSysMetrics(1) == 600 // Altura em pixels
   // Executa ações para resolução de 600
ENDIF
*/

/*
nLarguraAreaTrabalho := GetSysMetrics(2) // SM_CXFULLSCREEN
nAlturaAreaTrabalho  := GetSysMetrics(3) // SM_CYFULLSCREEN
*/

/*
aRes:=ScrResolution()
  ? aRes[1]  

  ? aRes[2]  

*/

Regards, saludos.

João Santos - São Paulo - Brasil - Phone: +55(11)95150-7341
Posts: 230
Joined: Sat Apr 19, 2008 10:28 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Sat Nov 15, 2025 10:06 AM

Hola

Creo que el problema lo tengo porque estoy compilando con BCC. Voy a intentar migrar a MSVC

Gracias

Álvaro

Posts: 1789
Joined: Tue Oct 11, 2005 05:01 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Mon Nov 17, 2025 03:43 AM

Para hacer de nuevo los cálculos en caso de múltiples monitores con múltiples resoluciones, creo que debe haber un evento en handleevent que se dispare cuando se realiza el cambio, esto en la clase twindow.

Salu2

Carlos Vargas

Desde Managua, Nicaragua (CA)
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Mon Nov 17, 2025 07:44 AM

Respuesta

FWH proporciona soporte completo para múltiples monitores a través de la clase TMonitor y varias funciones auxiliares ubicadas en source/function/getsysin.prg. 0-cite-0

Funciones Principales

FW_ActiveMonitors() - Retorna el número de monitores activos en el sistema. 0-cite-1

FW_VirtualScreen() - Retorna un objeto TRect que representa el área total de la pantalla virtual cubierta por todos los monitores. 0-cite-2

FW_GetMonitor() - Función versátil que retorna un objeto TMonitor según diferentes parámetros: 0-cite-3

  • Sin parámetros: retorna el monitor primario
  • Con número n: retorna el n-ésimo monitor
  • Con oWnd/hWnd: retorna el monitor que contiene la ventana
  • Con coordenadas nRow, nCol: retorna el monitor que contiene ese punto

FW_GetAllMonitors() - Retorna un array con todos los objetos TMonitor del sistema. 0-cite-4

FW_SetMonitor() - Establece un monitor por defecto para todas las ventanas/diálogos que se abran posteriormente. 0-cite-5 0-cite-6

Clase TMonitor

La clase TMonitor hereda de TRect y proporciona: 0-cite-0

Datos:

  • lPrimary - Indica si es el monitor primario
  • ClientRect - Área de trabajo del monitor (excluyendo barra de tareas)
  • nTop, nLeft, nBottom, nRight, nWidth, nHeight - Coordenadas y dimensiones (heredadas de TRect)

Métodos:

  • Row(r) - Convierte coordenada de fila relativa al monitor a coordenada de pantalla virtual 0-cite-7
  • Col(c) - Convierte coordenada de columna relativa al monitor a coordenada de pantalla virtual 0-cite-8
  • Center(oWnd) - Centra una ventana dentro del monitor 0-cite-9
  • Move(oWnd) - Mueve una ventana al monitor manteniendo sus coordenadas relativas 0-cite-10

Integración con Ventanas y Diálogos

Las ventanas y diálogos soportan la cláusula IN para especificar el monitor: 0-cite-11

Code (clipper): Select all Collapse
ACTIVATE WINDOW oWnd IN nMonitor/oMonitor
ACTIVATE DIALOG oDlg IN oWnd/nMonitor/oMonitor

El método Activate() de TWindow procesa el parámetro oMonitor para posicionar la ventana en el monitor especificado. 0-cite-12

Notas

Estas funcionalidades fueron introducidas en diciembre de 2018 según el historial de cambios. 0-cite-13 En septiembre de 2024 se corrigieron los métodos Row() y Col() de la clase TMonitor. 0-cite-14

Wiki pages you might want to explore:

Citations

File: source/function/getsysin.prg (L427-428)

Code (text): Select all Collapse
function FW_ActiveMonitors()
return GetSysMetrics( SM_CMONITORS )

File: source/function/getsysin.prg (L432-434)

Code (text): Select all Collapse
function FW_VirtualScreen()
return TRect():New( GetSysMetrics( SM_YVIRTUALSCREEN ),  GetSysMetrics( SM_XVIRTUALSCREEN ),,, ;
                    GetSysMetrics( SM_CXVIRTUALSCREEN ), GetSysMetrics( SM_CYVIRTUALSCREEN ) )

File: source/function/getsysin.prg (L438-452)

Code (text): Select all Collapse
function FW_SetMonitor(...)

   static oMonitor

   local oRet

   oRet  := oMonitor
   if PCOUNT() > 0
      oMonitor := HB_ExecFromArray( "FW_GETMONITOR", HB_AParams() )
      if oMonitor:lPrimary
         oMonitor  := nil
      endif
   endif

return oRet

File: source/function/getsysin.prg (L456-486)

Code (text): Select all Collapse
function FW_GetMonitor( nRow, nCol )  // POINT r,c [OR] nMonitor, [or] oWnd/hWnd No params: primary monitor

   local aInfo, oMonitor, nMax

   if nRow == nil .and. nCol == nil
      oMonitor   := TMonitor():New( MonitorInfoFromRC( 20, 20 ) )
   elseif HB_ISNUMERIC( nRow ) .and. HB_ISNUMERIC( nCol )
      aInfo       := MonitorInfoFromRC( nRow, nCol )
      if !Empty( aInfo )
         oMonitor := TMonitor():New( aInfo )
      endif
   elseif PCount() == 1
      if HB_ISOBJECT( nRow ) .and. nRow:IsKindOf( "TWINDOW" )
         nRow     := nRow:hWnd
      endif
      if ValType( nRow ) != "N"
         nRow     := 1
      endif
      if IsWindow( nRow )
         aInfo    := MonitorInfoFromRC( nRow )
         if aInfo != nil
            oMonitor := TMonitor():New( aInfo )
         endif
      else
         nMax  := Max( 1, Min( nRow, FW_ActiveMonitors() ) )
         oMonitor := FW_GetAllMonitors()[ nMax ]
      endif

   endif

return oMonitor

File: source/function/getsysin.prg (L490-527)

Code (text): Select all Collapse
function FW_GetAllMonitors()

   static caRect
   static aMonitors   := {}

   local aRect, nRow, nCol, aInfo, caInfo, nMonitors, c, nMonitor := 0

   aRect       := FW_VirtualScreen()
   if ( c := FW_ArrayAsList( aRect ) ) == caRect
      return aMonitors
   endif

   caRect      := c
   aMonitors   := {}
   nMonitors   := FW_ActiveMonitors()

   for nRow := aRect[ 1 ] + 100 to aRect[ 3 ] step 100
      for nCol := aRect[ 2 ] + 100 to aRect[ 4 ] step 100
         aInfo := MonitorInfoFromRC( nRow, nCol )
         IF HB_IsArray( aInfo )
            caInfo := Str( aInfo[ 1 ] ) + ", " + Str( aInfo[ 2 ] ) + ", " + Str( aInfo[ 3 ] ) + ", " + Str( aInfo[ 4 ] )
            IF AScan( aMonitors, { | x | x[ 2 ] == caInfo } ) == 0
               nMonitor ++
               AAdd( aMonitors, { nMonitor, caInfo, aInfo, TMonitor():New( aInfo ) } )
            ENDIF
         ENDIF
         IF nMonitor >= nMonitors
            EXIT
         ENDIF
      next nCol
      IF nMonitor >= nMonitors
         EXIT
      ENDIF
   next nRow

   AEval( aMonitors, { |a,i| aMonitors[ i ] := a[ 4 ] } )

return aMonitors

File: source/function/getsysin.prg (L563-574)

Code (text): Select all Collapse
CLASS TMonitor STATIC FROM TRect

   DATA ClientRect
   DATA lPrimary

   METHOD New( aInfo ) CONSTRUCTOR
   METHOD Move( oWnd )
   METHOD Center( oWnd )
   METHOD Row( r )   INLINE ( ::nTop  + r )
   METHOD Col( c )   INLINE ( ::nLeft + c )

ENDCLASS

File: source/function/getsysin.prg (L588-618)

Code (text): Select all Collapse
METHOD Move( oWnd ) CLASS TMonitor

   local hWnd, nTop, nLeft, w, h, aRect
   local oMonitor

   if HB_ISOBJECT( oWnd ) .and. oWnd:IsKindOf( "TWINDOW" )
      hWnd  := oWnd:hWnd
   elseif HB_ISNUMERIC( oWnd )
      hWnd  := oWnd
   endif
   if Empty( hWnd ) .or. !IsWindow( hWnd )
      return nil
   endif

//   aRect    := GetCoors( hWnd ) // not correct for dialogs
   aRect    := GetWndRect( hWnd )
   nTop     := aRect[ 1 ]
   nLeft    := aRect[ 2 ]
   w        := aRect[ 4 ] - aRect[ 2 ]
   h        := aRect[ 3 ] - aRect[ 1 ]
   oMonitor := FW_GetMonitor( hWnd )
   if !oMonitor:lPrimary
      nTop  -= oMonitor:nTop
      nLeft -= oMonitor:nLeft
   endif
   nTop     += ::nTop
   nLeft    += ::nLeft

   MoveWindow( hWnd, nTop, nLeft, w, h, .t. )

return nil

File: source/function/getsysin.prg (L622-647)

Code (text): Select all Collapse
METHOD Center( oWnd ) CLASS TMonitor

   local aPt   := ::CenterPt
   local hWnd, w, h, aRect

   if HB_ISNUMERIC( oWnd ) .and. IsWindow( oWnd )
      hWnd     := oWnd
      aRect    := GetCoors( hWnd )
      w        := aRect[ 4 ] - aRect[ 2 ]
      h        := aRect[ 3 ] - aRect[ 1 ]
      aPt[ 1 ] := Max( ::nTop,  aPt[ 1 ] - Int( h / 2 ) )
      aPt[ 2 ] := Max( ::nLeft, aPt[ 2 ] - Int( w / 2 ) )
      MoveWindow( hWnd, aPt[ 1 ], aPt[ 2 ], w, h, .t. )

   elseif HB_ISOBJECT( oWnd ) .and. oWnd:IsKindOf( "TWINDOW" )

      aPt[ 1 ] -= Int( oWnd:nHeight / 2 )
      aPt[ 2 ] -= Int( oWnd:nWidth  / 2 )
      aPt[ 1 ] := Max( ::nTop,  aPt[ 1 ] )
      aPt[ 2 ] := Max( ::nLeft, aPt[ 2 ] )

      oWnd:Move( aPt[ 1 ], aPt[ 2 ], nil, nil, .t. )

   endif

return aPt

File: whatsnew.txt (L591-594)

Code (text): Select all Collapse
* Fix: Fixed methods Row,Col of TMonitor class in getsysin.prg by
  Modified methods  
  METHOD Row(r) INLINE ( ::nTop + r )
  METHOD Col(c) INLINE ( ::nLeft + c )

File: whatsnew.txt (L596-598)

Code (text): Select all Collapse
* Enhanced: Multi-monitor friendly Activate commands for Windows/Dialogs:
  ACTIVATE WINDOW [<clauses,...] IN nMonitor/oMonitor
  ACTIVATE DIALOG [<clauses,...] IN oWnd/nMonitor/oMonitor

File: whatsnew.txt (L600-602)

Code (text): Select all Collapse
* New function FW_SetMonitor(…) -> oPrevMonitor
  All windows/dialogs will open in the new monitor set.
  Params: nMonitor, oMonitor, oWnd, (nrow,col)

File: whatsnew.txt (L4863-4934)

Code (text): Select all Collapse
* Multiple monitor support functions:

- New function FW_GetAllMonitors() --> Array of TMonitor objects
- Fixes and enhancement to FW_GetMonitor( nthMonitor )


DECEMBER 2018
=============

* New methods in TWindow, applicable to dialogs also, but not controls,
  mdichild windows and child windows.
  
- METHOD SaveState() --> cState //text string of 88 chars containing
  encoded information of the postion ans state of window, for saving and
  later using with RestoreState

- METHOD RestoreState( cState ) //Restores window to the previusly saved
  state.

  Usage:
  ACTIVATE WINDOW oWnd ;
     ON INIT oWnd:RestoreState( MemoRead( "wndstate.txt" ) ) ;
     VALID ( MemoWrit( "wndstate.txt", oWnd:SaveState() ), .t. )

- METHOD Arrow( r1, c1, r2, c2, [nClr], [nPenSize] )
  Draws an arrow from point r1,c1 to r2,c2 with arrow head pointing to
  r2,c2.

* Multiple Monitors support functions:
  fwh\source\function\getsysin.prg

- FW_ActiveMonitors() --> nMonitors
  Number of extended monitors currently active.

- FW_VirtualScreen() --> oRect
  TRect object representing the area of virtual screen covered by all the
  extended monitors. (Note: The object has datas nTop, nLeft, nBottom,
  nRight, nWidth, nHeight)
  The total width of the rectangle is the sum of horizontal resolutions of
  all the monitors and height is the maximum of the vertical resolutions
  of all the monitors. 
  Recommended reading for better understanding
  https://docs.microsoft.com/en-us/windows/desktop/gdi/the-virtual-screen
  https://docs.microsoft.com/en-us/windows/desktop/gdi/multiple-display-monitors

- FW_GetMonitor(params) --> oMonitor (TMonitor object)
  params:
  FW_GetMonitor() --> oPrimaryMonitor
  FW_GetMonitor( n ) --> nth Monitor object. Defaults to primary monitor
     if there is only one active monitor.
  FW_GetMonitor( oWnd/hWnd ) --> oMonitor containing max area of the window
  FW_GetMonitor( nRow, nCol ) --> oMonitor containing the coordinates

- TMonitor class datas and methods
  DATAS:
  lPrimary // Is primary monitor
  nTop, nLeft, nBottom, nRight, nWidth, nHeight
  CenterPt --> { nRow, nCol }

  METHODS:
  Row( nMonitorRow ) --> nRow on Virtual Screen
  Col( nMonitorCol ) --> nCol on Virtual Screen
  Center( oWnd/hWnd ) --> Centers the window inside the monitor
  Move( oWnd/hWnd ) --> Sets the window in the monitor with the same 
                        relative coordinates

  Example Usage:
  ACTIVATE WINDOW oWnd ON INIT FW_GetMonitor( 2 ):Center( oWnd )
  ACTIVATE WINDOW oWnd ON INIT FW_GetMonitor( 2 ):Move( oWnd )
  If the program is run on a single monitor the display defaults to the 
  primary monitor

File: source/classes/window.prg (L1052-1059)

Code (text): Select all Collapse
   DEFAULT oMonitor := FW_SetMonitor()
   if oMonitor != nil
      if HB_ISNUMERIC( oMonitor )
         oMonitor := FW_GetMonitor( oMonitor )
      elseif !( HB_ISOBJECT( oMonitor ) .and. ( oMonitor:IsKindOf( "TMONITOR" ) .or. oMonitor:IsKindOf( "TWINDOW" ) ) )
         oMonitor := nil
      endif
   endif
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Mon Nov 17, 2025 07:49 AM
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Mon Nov 17, 2025 08:08 AM

The full class definition for TMonitor is located in the source/function/getsysin.prg file in the FiveTechSoft/fwh repository.

Class Definition Location

The TMonitor class definition begins at line 563 and extends through line 648: 2-cite-0

Class Structure

The TMonitor class inherits from TRect and includes the following members:

DATA Properties:

  • ClientRect - A TRect object representing the client area (work area) of the monitor
  • lPrimary - Logical value indicating if this is the primary monitor
  • Inherited from TRect: nTop, nLeft, nBottom, nRight, nWidth, nHeight

METHODS:

  • New(aInfo) - Constructor that takes a monitor info array
  • Move(oWnd) - Moves a window to this monitor while maintaining its relative position
  • Center(oWnd) - Centers a window on this monitor
  • Row(r) - Converts monitor-relative row to virtual screen coordinates (inline method)
  • Col(c) - Converts monitor-relative column to virtual screen coordinates (inline method)

Documentation

Additional documentation about TMonitor and multi-monitor support can be found in:

  1. Reference Wiki Page - Documents the TMonitor class data members and methods: 2-cite-1

  2. Modern Extensions Wiki Page - Provides information on multiple monitor support features and TMonitor usage patterns

Related Functions

The TMonitor class is used in conjunction with several supporting functions defined in the same file:

  • FW_ActiveMonitors() - Returns the number of active monitors
  • FW_VirtualScreen() - Returns the virtual screen dimensions as a TRect
  • FW_GetMonitor(nRow, nCol) - Gets the monitor at a specific point or by monitor number
  • FW_GetAllMonitors() - Returns an array of all TMonitor objects
  • FW_SetMonitor() - Sets/gets the default monitor for window placement 2-cite-2

Notes

The TMonitor class is specifically designed for multi-monitor support in FiveWin applications, allowing windows and dialogs to be positioned and centered on specific monitors in multi-monitor setups. It's integrated into dialog and window activation through the oMonitor parameter in ACTIVATE DIALOG and ACTIVATE WINDOW commands.

Citations

File: source/function/getsysin.prg (L427-527)

Code (text): Select all Collapse
function FW_ActiveMonitors()
return GetSysMetrics( SM_CMONITORS )

//----------------------------------------------------------------------------//

function FW_VirtualScreen()
return TRect():New( GetSysMetrics( SM_YVIRTUALSCREEN ),  GetSysMetrics( SM_XVIRTUALSCREEN ),,, ;
                    GetSysMetrics( SM_CXVIRTUALSCREEN ), GetSysMetrics( SM_CYVIRTUALSCREEN ) )

//----------------------------------------------------------------------------//

function FW_SetMonitor(...)

   static oMonitor

   local oRet

   oRet  := oMonitor
   if PCOUNT() > 0
      oMonitor := HB_ExecFromArray( "FW_GETMONITOR", HB_AParams() )
      if oMonitor:lPrimary
         oMonitor  := nil
      endif
   endif

return oRet

//----------------------------------------------------------------------------//

function FW_GetMonitor( nRow, nCol )  // POINT r,c [OR] nMonitor, [or] oWnd/hWnd No params: primary monitor

   local aInfo, oMonitor, nMax

   if nRow == nil .and. nCol == nil
      oMonitor   := TMonitor():New( MonitorInfoFromRC( 20, 20 ) )
   elseif HB_ISNUMERIC( nRow ) .and. HB_ISNUMERIC( nCol )
      aInfo       := MonitorInfoFromRC( nRow, nCol )
      if !Empty( aInfo )
         oMonitor := TMonitor():New( aInfo )
      endif
   elseif PCount() == 1
      if HB_ISOBJECT( nRow ) .and. nRow:IsKindOf( "TWINDOW" )
         nRow     := nRow:hWnd
      endif
      if ValType( nRow ) != "N"
         nRow     := 1
      endif
      if IsWindow( nRow )
         aInfo    := MonitorInfoFromRC( nRow )
         if aInfo != nil
            oMonitor := TMonitor():New( aInfo )
         endif
      else
         nMax  := Max( 1, Min( nRow, FW_ActiveMonitors() ) )
         oMonitor := FW_GetAllMonitors()[ nMax ]
      endif

   endif

return oMonitor

//----------------------------------------------------------------------------//

function FW_GetAllMonitors()

   static caRect
   static aMonitors   := {}

   local aRect, nRow, nCol, aInfo, caInfo, nMonitors, c, nMonitor := 0

   aRect       := FW_VirtualScreen()
   if ( c := FW_ArrayAsList( aRect ) ) == caRect
      return aMonitors
   endif

   caRect      := c
   aMonitors   := {}
   nMonitors   := FW_ActiveMonitors()

   for nRow := aRect[ 1 ] + 100 to aRect[ 3 ] step 100
      for nCol := aRect[ 2 ] + 100 to aRect[ 4 ] step 100
         aInfo := MonitorInfoFromRC( nRow, nCol )
         IF HB_IsArray( aInfo )
            caInfo := Str( aInfo[ 1 ] ) + ", " + Str( aInfo[ 2 ] ) + ", " + Str( aInfo[ 3 ] ) + ", " + Str( aInfo[ 4 ] )
            IF AScan( aMonitors, { | x | x[ 2 ] == caInfo } ) == 0
               nMonitor ++
               AAdd( aMonitors, { nMonitor, caInfo, aInfo, TMonitor():New( aInfo ) } )
            ENDIF
         ENDIF
         IF nMonitor >= nMonitors
            EXIT
         ENDIF
      next nCol
      IF nMonitor >= nMonitors
         EXIT
      ENDIF
   next nRow

   AEval( aMonitors, { |a,i| aMonitors[ i ] := a[ 4 ] } )

return aMonitors

File: source/function/getsysin.prg (L563-648)

Code (text): Select all Collapse
CLASS TMonitor STATIC FROM TRect

   DATA ClientRect
   DATA lPrimary

   METHOD New( aInfo ) CONSTRUCTOR
   METHOD Move( oWnd )
   METHOD Center( oWnd )
   METHOD Row( r )   INLINE ( ::nTop  + r )
   METHOD Col( c )   INLINE ( ::nLeft + c )

ENDCLASS

//----------------------------------------------------------------------------//

METHOD New( aInfo ) CLASS TMonitor

   ::Super:New( aInfo[ 1 ], aInfo[ 2 ], aInfo[ 3 ], aInfo[ 4 ] )
   ::ClientRect:= TRect():New( aInfo[ 5 ], aInfo[ 6 ], aInfo[ 7 ], aInfo[ 8 ] )
   ::lPrimary  := aInfo[ 9 ]

return Self

//----------------------------------------------------------------------------//

METHOD Move( oWnd ) CLASS TMonitor

   local hWnd, nTop, nLeft, w, h, aRect
   local oMonitor

   if HB_ISOBJECT( oWnd ) .and. oWnd:IsKindOf( "TWINDOW" )
      hWnd  := oWnd:hWnd
   elseif HB_ISNUMERIC( oWnd )
      hWnd  := oWnd
   endif
   if Empty( hWnd ) .or. !IsWindow( hWnd )
      return nil
   endif

//   aRect    := GetCoors( hWnd ) // not correct for dialogs
   aRect    := GetWndRect( hWnd )
   nTop     := aRect[ 1 ]
   nLeft    := aRect[ 2 ]
   w        := aRect[ 4 ] - aRect[ 2 ]
   h        := aRect[ 3 ] - aRect[ 1 ]
   oMonitor := FW_GetMonitor( hWnd )
   if !oMonitor:lPrimary
      nTop  -= oMonitor:nTop
      nLeft -= oMonitor:nLeft
   endif
   nTop     += ::nTop
   nLeft    += ::nLeft

   MoveWindow( hWnd, nTop, nLeft, w, h, .t. )

return nil

//----------------------------------------------------------------------------//

METHOD Center( oWnd ) CLASS TMonitor

   local aPt   := ::CenterPt
   local hWnd, w, h, aRect

   if HB_ISNUMERIC( oWnd ) .and. IsWindow( oWnd )
      hWnd     := oWnd
      aRect    := GetCoors( hWnd )
      w        := aRect[ 4 ] - aRect[ 2 ]
      h        := aRect[ 3 ] - aRect[ 1 ]
      aPt[ 1 ] := Max( ::nTop,  aPt[ 1 ] - Int( h / 2 ) )
      aPt[ 2 ] := Max( ::nLeft, aPt[ 2 ] - Int( w / 2 ) )
      MoveWindow( hWnd, aPt[ 1 ], aPt[ 2 ], w, h, .t. )

   elseif HB_ISOBJECT( oWnd ) .and. oWnd:IsKindOf( "TWINDOW" )

      aPt[ 1 ] -= Int( oWnd:nHeight / 2 )
      aPt[ 2 ] -= Int( oWnd:nWidth  / 2 )
      aPt[ 1 ] := Max( ::nTop,  aPt[ 1 ] )
      aPt[ 2 ] := Max( ::nLeft, aPt[ 2 ] )

      oWnd:Move( aPt[ 1 ], aPt[ 2 ], nil, nil, .t. )

   endif

return aPt
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Mon Nov 17, 2025 08:10 AM

La función MonitorFromWindow() nos dice en que monitor está una ventana

#include <windows.h>
#include <iostream>

// Callback para EnumDisplayMonitors (opcional, para enumerar monitores)
BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
    MONITORINFO mi = { sizeof(mi) };
    if (GetMonitorInfo(hMonitor, &mi)) {
        std::cout << "Monitor: " << mi.szDevice << " (Pos: " << mi.rcMonitor.left << "," << mi.rcMonitor.top << ")" << std::endl;
    }
    return TRUE;
}

int main() {
    HWND hwnd = FindWindow(NULL, L"Título de la Ventana");  // Reemplaza con el HWND real
    if (hwnd == NULL) {
        std::cout << "Ventana no encontrada." << std::endl;
        return 1;
    }

HMONITOR hMon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (hMon) {
    MONITORINFO mi = { sizeof(mi) };
    if (GetMonitorInfo(hMon, &mi)) {
        std::cout << "La ventana está en el monitor: " << mi.szDevice 
                  << " (Primario: " << (mi.dwFlags & MONITORINFOF_PRIMARY ? "Sí" : "No") << ")" << std::endl;
    }
} else {
    std::cout << "No se pudo determinar el monitor." << std::endl;
}

// Opcional: Enumerar todos los monitores para contexto
EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, 0);

return 0;
}
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Mon Nov 17, 2025 10:33 AM

¿Qué es DPI Awareness en Windows?

DPI (Dots Per Inch) mide la densidad de píxeles de una pantalla. El valor estándar histórico es 96 DPI (100% de escala). Las pantallas modernas (Full HD en portátiles pequeños, 4K, Surface, etc.) suelen tener 125%, 150%, 200% o más (144 DPI, 192 DPI, etc.).

Windows aplica escalado DPI para que los elementos de la interfaz (texto, botones, ventanas) mantengan un tamaño físico legible en pantallas de alta densidad. Sin escalado, todo se vería diminuto en una pantalla 4K.

El problema es que las aplicaciones antiguas fueron diseñadas asumiendo siempre 96 DPI. Por eso Windows introdujo DPI Awareness: le dice al sistema operativo cuánto sabe tu aplicación sobre pantallas de alta densidad y quién se encarga del escalado (la app o Windows).

Modos de DPI Awareness (de peor a mejor)

Modo¿Qué hace Windows?¿Qué ve el usuario al mover la ventana a otro monitor?¿Recomendado?
DPI Unaware (predeterminado antiguo)Estira el bitmap de toda la aplicación como si fuera una imagen (virtualización)Se ve borrosa (blurry) en monitores de DPI distintoNo, horrible en pantallas modernas
System AwareEscala la app según el DPI del monitor principal al iniciar WindowsNítida solo en el monitor principal. Borrosa en los demásSolo para apps muy simples
Per-Monitor v1 (Windows 8.1)No escala nada. Solo avisa a la ventana principal cuando cambia el DPINítida si la manejas bien, pero el área no-client (título, bordes) puede quedar malObsoleto
Per-Monitor v2 (Windows 10 1703+, recomendado hoy)Escala automáticamente título, scrollbars, menús, iconos del sistema, diálogos y controles comunes. Avisa a TODAS las ventanas (incluidos controles hijos)Perfecta, nítida y con tamaño correcto en cualquier monitor, sin intervención del usuarioSÍ, este es el que debes usar

¿Qué pasa realmente cuando cambias de monitor o conectas/desconectas uno?

  • Si tu app es Unaware o System Aware → Windows estira automáticamente → se ve borrosa, pero no tienes que redimensionar nada manualmente. El tamaño físico de la ventana se mantiene más o menos.

  • Si app es Per-Monitor v2 y está bien programada → cuando arrastras la ventana a otro monitor, Windows envía inmediatamente el mensaje WM_DPICHANGED con:

  • El nuevo DPI.

  • Un rectángulo sugerido con el nuevo tamaño y posición que mantiene el tamaño físico de la ventana.

    La aplicación responde automáticamente (en milisegundos):

  • Redimensiona la ventana al tamaño sugerido.

  • Escala fuentes, imágenes, márgenes, layouts, etc.

    El usuario no tiene que tocar nada. La ventana se adapta sola y se ve perfecta. Esto lo hacen Chrome, Edge, Firefox, Visual Studio, Discord, etc.

Solo si la aplicación NO maneja WM_DPICHANGED correctamente, la ventana quedará muy pequeña o muy grande al cambiar de monitor y el usuario tendrá que redimensionarla manualmente (esto pasa en muchas apps antiguas o mal hechas).

Cómo afecta esto a un programador de C++

Si haces aplicaciones Win32/MFC/WinAPI puro en C++, el impacto es grande:

  1. Debes declarar explícitamente Per-Monitor v2, si no Windows te tratará como Unaware y tu app se verá borrosa en 2025 (todos los portátiles nuevos son high-DPI).

    Formas de declararlo (elige una):

    Opción preferida: Manifest (más fiable)

    Code (xml): Select all Collapse
       <!-- En el archivo .manifest o integrado en el recurso -->
       <assembly xmlns="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
         <application xmlns="urn:schemas-microsoft-com:compatibility.v1">
           <application>
             <!-- Windows 10+ -->
             <windowsSettings>
               <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">perMonitorV2</dpiAwareness>
               <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
             </windowsSettings>
           </application>
         </application>
       </assembly>

    Opción alternativa: Código (antes de cualquier ventana)

    Code (cpp): Select all Collapse
       #include <ShellScalingApi.h>
       // Colocar muy al principio de main() o WinMain()
       SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
  2. Debes manejar el mensaje WM_DPICHANGED en tu WndProc:

    Code (cpp): Select all Collapse
       case WM_DPICHANGED:
       {
           UINT newDpi = HIWORD(wParam);
           RECT* const prcNewWindow = (RECT*)lParam;
    
           SetWindowPos(hWnd,
                        nullptr,
                        prcNewWindow->left,
                        prcNewWindow->top,
                        prcNewWindow->right - prcNewWindow->left,
                        prcNewWindow->bottom - prcNewWindow->top,
                        SWP_NOZORDER | SWP_NOACTIVATE);
    
           // Aquí escalas todo tu contenido:
           // - Fuentes: CreateFont con tamaño en puntos físicos o escalar con factor newDpi/96.0f
           // - Layouts: volver a calcular posiciones y tamaños de controles
           // - Imágenes: cargar versión de mayor resolución o usar SVG/vectoriales
           // - Etc.
    
           return 0;
       }
  3. Usa siempre APIs DPI-aware:
    GetDpiForWindow(hWnd),
    GetSystemMetricsForDpi(SM_CXSCREEN, dpi),
    AdjustWindowRectExForDpi, etc.

  4. Evita valores hardcodeados en píxeles. Usa layouts relativos, grids, o multiplica todo por (currentDpi / 96.0f).

Si usas frameworks:

  • Qt: Activa AA_EnableHighDpiScaling y AA_UseHighDpiPixmaps → funciona casi perfecto automáticamente.
  • MFC: Desde VS2015+ soporta bien Per-Monitor v2 con manifest.
  • WPF: Es nativamente DPI-aware y perfecto.
  • Dear ImGui, SDL, etc.: Tienen sus propias opciones de high-DPI.

Resumen para ti como programador C++ en 2025:

  • Pon el manifest con perMonitorV2.
  • Maneja WM_DPICHANGED y usa el rectángulo sugerido.
  • Escala tu UI en ese handler.
    → Con eso tu aplicación se verá nítida y se adaptará automáticamente al cambiar de monitor, conectar un 4K externo, usar Remote Desktop, etc., sin que el usuario tenga que redimensionar nada nunca más.

Si no lo haces, tu aplicación parecerá de los 90 en cualquier pantalla moderna. Hoy en día es obligatorio.

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 1789
Joined: Tue Oct 11, 2005 05:01 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Tue Nov 18, 2025 04:06 PM

Antonio, exelente recopilación de información, no podía ser más claro.

Salu2

Carlos Vargas

Desde Managua, Nicaragua (CA)
Posts: 230
Joined: Sat Apr 19, 2008 10:28 PM
Re: Monitores 4K, programas FiveWin borrosos
Posted: Wed Nov 19, 2025 11:19 PM

Buenas tardes,

Estoy avanzando en esto del escalado. Uso dpi awareness "Per-Monitor v2", y se ve totalmente nítido. Podéis ver en este video como al pasar de un monitor una app hecha con FWH64, las ventanas, los fonts, los browses, la msgbar, la buttonbar y otros controles se escalan automáticamente. A continuación arrastro el explorador de windows y se ve que se comporta igual que la app. También se ve en vídeo que si se cambia el escalado en la configuración de windows, la app de fwh se ajusta automáticamente sin necesidad de tener que reiniciar la app.

https://youtu.be/NWXY72O9qq8

Mi configuración es:
Monitor 1 3840x2160 (4K) escalado al 175%
Monitor 2 1920x1080 (HD) escalado al 100%

Un saludo
Alvaro

Continue the discussion