ListIconGadget - resize content to fit perfectly

Just starting out? Need help? Post your questions and find answers here.
User avatar
Michael Vogel
Addict
Addict
Posts: 2819
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

ListIconGadget - resize content to fit perfectly

Post by Michael Vogel »

I'd like to adjust the height of a listicon gadget to show exactly five rows without showing a scrollbar. The column widths have to be adjusted as well to fill the whole space of the gadget.

Actually I am using something like the following code, which is not perfect (the left gap seems to be larger than right one).
I am also unsure if it fails completely on different PC's, especially for screen scaling.

Code: Select all


; Define

	Enumeration
		#Win
		#ListSource
		#Font
	EndEnumeration

	#PB_DpiBits=		16
	#PB_DpiScale=		1<<#PB_DpiBits

	#ViewSourceLines=5

	Global DpiScale.i
	Global ListWidth.i
	Global ListHeight.i
	Global ListBorder.i
	Global ListScroll.i

	Macro ScaleUp(value)
		(((value)*DpiScale)>>#PB_DpiBits)
	EndMacro
	Macro ScaleDown(value)
		(((value)<<#PB_DpiBits)/DpiScale)
	EndMacro

; EndDefine
Procedure Main()

	Protected Metrics.NONCLIENTMETRICS

	Metrics\cbSize=SizeOf(Metrics)
	SystemParametersInfo_(#SPI_GETNONCLIENTMETRICS,SizeOf(Metrics),@Metrics,0)
	LoadFont(#Font,PeekS(@Metrics\lfMenuFont\lfFaceName[0]),Metrics\lfMenuFont\lfHeight+2)

	DpiScale=GetDeviceCaps_(GetDC_(0),#LOGPIXELSX)<<#PB_DpiBits/96

	Width=600
	Height=200

	If OpenWindow(#Win,X,Y,Width,Height,"List",#PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_ScreenCentered)

		ListIconGadget(#ListSource,20,262,500,130,"Line",60,#PB_ListIcon_FullRowSelect|#LVS_NOCOLUMNHEADER)
		AddGadgetColumn(#ListSource,1,"Code",400)
		WindowBounds(#Win,400,420,2000,1200)

		SetGadgetFont(#ListSource,FontID(#Font))

		; ***********************************************************************************************
		ListBorder=GetSystemMetrics_(#SM_CXEDGE)<<1;+20
		ListScroll=GetSystemMetrics_(#SM_CXVSCROLL)
		ListWidth=WindowWidth(#Win)-40
		ListHeight=90;
		; ***********************************************************************************************

		Rect.rect
		Id=GadgetID(#ListSource)
		AddGadgetItem(#ListSource,0,#LF$)
		
		; same as #SM_EDGE (see above)?
		GetWindowRect_(Id,Rect)
		ListBorder=rect\bottom-rect\top
		GetClientRect_(Id,Rect)
		ListBorder+rect\top-rect\bottom
		
		; could this be done easier?
		Rect\left=#LVIR_LABEL
		SendMessage_(Id,#LVM_GETITEMRECT,0,Rect)
		ListHeight=(Rect\Bottom-Rect\Top)*#ViewSourceLines+ListBorder

		ClearGadgetItems(#ListSource)
		For x=0 To #ViewSourceLines-1
			AddGadgetItem(#ListSource,x,#LF$+" - "+Str(x+1)+" -")
		Next x
		SetGadgetItemColor(#ListSource,#ViewSourceLines/2,#PB_Gadget_BackColor,$ffe0e0)

		ResizeGadget(#ListSource,#PB_Ignore,#PB_Ignore,ListWidth,ListHeight)

		n=GetGadgetItemAttribute(#ListSource,#Null,#PB_ListIcon_ColumnWidth,0)
		SetGadgetItemAttribute(#ListSource,#Null,#PB_ListIcon_ColumnWidth,ListWidth-n-ListBorder*2,1)

		Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow

	EndIf

EndProcedure
Main()

breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: ListIconGadget - resize content to fit perfectly

Post by breeze4me »

Your code has the same left and right margins at 100% screen scale.
But, to make it look right at screen scales greater than 100%, you need to handle the size in pixels, which is a bit complicated.

To ensure that there is not even a single pixel of error, it is not recommended to use PB native functions that use scaled values. This is because they can be off by as much as one pixel depending on screen scale factors.
I know this issue well, as I struggled to compensate for the 1-pixel error of splitter gadgets based on screen scale factors in my recent project. :evil:

Tested on Windows 10 and 11 at 100%, 125%, and 150% screen scales.

Code: Select all

; Define

	Enumeration
		#Win
		#ListSource
		#Font
	EndEnumeration

	#PB_DpiBits=		16
	#PB_DpiScale=		1<<#PB_DpiBits

	#ViewSourceLines=5

	Global DpiScale.i
	Global ListWidth.i
	Global ListHeight.i
	Global ListBorder.i
	Global ListScroll.i

	Macro ScaleUp(value)
		(((value)*DpiScale)>>#PB_DpiBits)
	EndMacro
	Macro ScaleDown(value)
		(((value)<<#PB_DpiBits)/DpiScale)
	EndMacro

; EndDefine
Procedure Main()

	Protected Metrics.NONCLIENTMETRICS

	Metrics\cbSize=SizeOf(Metrics)
	SystemParametersInfo_(#SPI_GETNONCLIENTMETRICS,SizeOf(Metrics),@Metrics,0)
	LoadFont(#Font,PeekS(@Metrics\lfMenuFont\lfFaceName[0]),Metrics\lfMenuFont\lfHeight+2)

	DpiScale=GetDeviceCaps_(GetDC_(0),#LOGPIXELSX)<<#PB_DpiBits/96

	Width=600
	Height=200

	If OpenWindow(#Win,X,Y,Width,Height,"List",#PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_ScreenCentered)

		ListIconGadget(#ListSource,20,262,500,130,"Line",60,#PB_ListIcon_FullRowSelect|#LVS_NOCOLUMNHEADER)
		AddGadgetColumn(#ListSource,1,"Code",400)
		WindowBounds(#Win,400,420,2000,1200)

		SetGadgetFont(#ListSource,FontID(#Font))

		; ***********************************************************************************************
		ListBorder=GetSystemMetrics_(#SM_CXEDGE)<<1;+20
		ListScroll=GetSystemMetrics_(#SM_CXVSCROLL)
		ListWidth=WindowWidth(#Win)-40
		ListHeight=90;
		; ***********************************************************************************************

		Rect.rect
		Id=GadgetID(#ListSource)
		AddGadgetItem(#ListSource,0,#LF$)
		
		; Resize the gadget to the scaled values.
		ResizeGadget(#ListSource,#PB_Ignore,#PB_Ignore,ListWidth,ListHeight)
		
		GetWindowRect_(Id,Rect)
		; Get the size of the gadget in pixels.
		ListWidthPx = rect\right - rect\left
		ListBorder = rect\bottom - rect\top
		GetClientRect_(Id,Rect)
		; Get the size of the gadget's client area in pixels.
		ListClientWidthPx = rect\right - rect\left
		ListBorder+rect\top-rect\bottom
		
		Rect\left=#LVIR_LABEL
		SendMessage_(Id,#LVM_GETITEMRECT,0,Rect)
		ListHeightPx=(Rect\Bottom-Rect\Top)*#ViewSourceLines+ListBorder
		; Get the item's left margin in pixels.
		ItemLeftPx = Rect\left
		
		ClearGadgetItems(#ListSource)
		
		For x=0 To #ViewSourceLines-1
			AddGadgetItem(#ListSource,x,#LF$+" - "+Str(x+1)+" -")
		Next x
		SetGadgetItemColor(#ListSource,#ViewSourceLines/2,#PB_Gadget_BackColor,$ffe0e0)
		
		; Change the height of the gadget again in pixels.
		SetWindowPos_(Id, 0, 0, 0, ListWidthPx, ListHeightPx, #SWP_NOMOVE | #SWP_NOZORDER | #SWP_FRAMECHANGED)
		SendMessage_(Id, #LVM_SETCOLUMNWIDTH, 1, ListClientWidthPx - SendMessage_(Id, #LVM_GETCOLUMNWIDTH, 0, 0) - ItemLeftPx)
		

		Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow

	EndIf

EndProcedure
Main()
User avatar
Michael Vogel
Addict
Addict
Posts: 2819
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Re: ListIconGadget - resize content to fit perfectly

Post by Michael Vogel »

Great, seems to work fine here as well :wink:

Maybe the following shorter version will also work (needs to be tested on different PC's):

Code: Select all

; Define

	Enumeration
		#Win
		#ListSource
		#Font
	EndEnumeration

	#PB_DpiBits=		16
	#PB_DpiScale=		1<<#PB_DpiBits

	#ViewSourceLines=5

	Global DpiScale.i
	Global ListWidth.i
	Global ListHeight.i
	Global ListBorder.i
	Global ListScroll.i

	Macro ScaleUp(value)
		(((value)*DpiScale)>>#PB_DpiBits)
	EndMacro
	Macro ScaleDown(value)
		(((value)<<#PB_DpiBits)/DpiScale)
	EndMacro

; EndDefine
Procedure Main()

	Protected Metrics.NONCLIENTMETRICS

	Metrics\cbSize=SizeOf(Metrics)
	SystemParametersInfo_(#SPI_GETNONCLIENTMETRICS,SizeOf(Metrics),@Metrics,0)
	LoadFont(#Font,PeekS(@Metrics\lfMenuFont\lfFaceName[0]),Metrics\lfMenuFont\lfHeight-4)

	DpiScale=GetDeviceCaps_(GetDC_(0),#LOGPIXELSX)<<#PB_DpiBits/96

	Width=600
	Height=200

	If OpenWindow(#Win,X,Y,Width,Height,"List",#PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_ScreenCentered)

		ListIconGadget(#ListSource,20,262,500,130,"Line",60,#PB_ListIcon_FullRowSelect|#LVS_NOCOLUMNHEADER)
		AddGadgetColumn(#ListSource,1,"Code",400)
		WindowBounds(#Win,400,420,2000,1200)

		SetGadgetFont(#ListSource,FontID(#Font))
		AddGadgetItem(#ListSource,0,#LF$)

		; ***********************************************************************************************
		; ListBorder=GetSystemMetrics_(#SM_CXEDGE)<<1
		ListScroll=GetSystemMetrics_(#SM_CXVSCROLL)
		ListWidth=WindowWidth(#Win)-40
		
		Rect.rect
		x=GadgetID(#ListSource)
		Rect\left=#LVIR_LABEL
		SendMessage_(x,#LVM_GETITEMRECT,0,Rect)
		ListBorder=ScaleDown(Rect\left)
		ListHeight=(Rect\Bottom-Rect\Top)*#ViewSourceLines

		GetWindowRect_(x,Rect)
		ListHeight+Rect\bottom-Rect\top
		GetClientRect_(x,Rect)
		ListHeight-Rect\bottom+Rect\top
		ListHeight=ScaleDown(ListHeight)
		
		; ***********************************************************************************************

		ClearGadgetItems(#ListSource)
		For x=0 To #ViewSourceLines-1
			AddGadgetItem(#ListSource,x,#LF$+" - "+Str(x+1)+" -")
		Next x
		SetGadgetItemColor(#ListSource,#ViewSourceLines/2,#PB_Gadget_BackColor,$ffe0e0)

		ResizeGadget(#ListSource,#PB_Ignore,#PB_Ignore,ListWidth,ListHeight)

		n=GetGadgetItemAttribute(#ListSource,#Null,#PB_ListIcon_ColumnWidth,0)
		SetGadgetItemAttribute(#ListSource,#Null,#PB_ListIcon_ColumnWidth,ListWidth-n-ListBorder*2,1)

		Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow

	EndIf

EndProcedure
Main()
breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: ListIconGadget - resize content to fit perfectly

Post by breeze4me »

Michael Vogel wrote: Tue Apr 16, 2024 7:28 am

Code: Select all

	Macro ScaleUp(value)
		(((value)*DpiScale)>>#PB_DpiBits)
	EndMacro
	Macro ScaleDown(value)
		(((value)<<#PB_DpiBits)/DpiScale)
	EndMacro
No.
That one pixel error is due to rounding when scaling up and down, so your scaling up and down macros have the same error. At 125%, the left margin is 5 pixels and the right is 4 pixels.
Also, the DesktopScaledX/Y and DesktopUnscaledX/Y functions have the same problem, so I don't use them where I need an exact number of pixels.
Post Reply