1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HSPでDPI変更時やモニターごとに高DPIに対応させる方法

Last updated at Posted at 2024-11-10

HSPでDPI変更時やモニターごとに高DPIに対応させる方法について

HSPで高DPIに対応させるには、Windows APIのSetProcessDpiAwareを呼び出すか、マニフェストを書き換えるのが一般的のようですが、今回はDPIの設定の変更時やモニターごとのDPIに対応させる方法についてです。

DPI.hsp
#packopt name "High_DPI_Test"
#include "gdi32.as"
#include "user32.as"
#func global SetProcessDpiAwarenessContext "SetProcessDpiAwarenessContext" int
#func global SetProcessDPIAware "SetProcessDPIAware"
#func global AdjustWindowRectExForDpi "AdjustWindowRectExForDpi" int,int,int,int,int
#cfunc global GetDpiForSystem "GetDpiForSystem"
//#cfunc global GetDpiForWindow "GetDpiForWindow" int
#func global _GetSystemMetricsForDpi "GetSystemMetricsForDpi" int,int

//未使用だが、DPIが異なる別のモニターにウィンドウを作成する場合に必要。
/*
#uselib "Shcore.dll"
#func GetDpiForMonitor "GetDpiForMonitor" int,int,var,var
#func SetProcessDpiAwareness "SetProcessDpiAwareness" var
*/

#define SWP_NOSIZE 0x0001
#define SWP_NOMOVE 0x0002

#define DPI_AWARENESS_CONTEXT_UNAWARE	-1
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE	-2
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE	-3
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2	-4
#define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED	-5

#const SM_CYCAPTION		0x00000004
#const SM_CXFIXEDFRAME	0x00000007
#const SM_CYFIXEDFRAME	0x00000008
#const SM_CXSIZEFRAME	0x00000020
#const SM_CYSIZEFRAME	0x00000021
#const SM_CYSMCAPTION	0x00000033

#define WM_DPICHANGED 0x02E0
#define LOGPIXELSX	0x00000058
#define LOGPIXELSY	0x0000005A

#packopt manifest "DPI.manifest"
#packopt hide 1
#ifdef _debug
gsel 0,-1
//デバッグ時(F5実行)にもDPIを反映させる
opt = DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2:SetProcessDpiAwarenessContext opt
//デバッグ時(F5実行)にマニフェストファイルを保存
exist "DPI.manifest"
if strsize <= 0{
mani = {"
<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>
<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\">
<assemblyIdentity
version=\"1.0.0.0\"
processorArchitecture=\"*\"
name=\"OnionSoftware.hsp3.exe\"
type=\"win32\"
/>

<application xmlns=\"urn:schemas-microsoft-com:asm.v3\">
<windowsSettings>
<dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true</dpiAware>
<dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>

<description>XPStyle</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type=\"win32\"
name=\"Microsoft.Windows.Common-Controls\"
version=\"6.0.0.0\"
processorArchitecture=\"*\"
publicKeyToken=\"6595b64144ccf1df\"
language=\"*\"
/>
</dependentAssembly>
</dependency>
<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">
  <application>
    <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" />
    <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" />
    <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" />
    <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" />
  </application>
</compatibility>
</assembly>
"}
notesel mani
notesave "DPI.manifest"
}
#endif

//DPIの計算処理のマクロ
#define global ctype DPI_SIZ(%1) (%1*DPI/96)
#define global ctype SIZ_DPI(%1) (%1*96/DPI)

#define global ctype HIWORD(%1) (%1 >> 16 & $FFFF)

//旧バージョンのWindows10やそれ以前のOSではGetDpiForSystem関数が存在しないため、varptrでその関数が存在するかチェックし、存在しなければ別の関数でDPIの値を取得。
if varptr(GetDpiForSystem){DPI=GetDpiForSystem()}else{DPI=GetDeviceCaps(GetDC(0),LOGPIXELSY)}

screen 0,640*8,480*8/*あらかじめ大きめの初期ウィンドウサイズを確保*/, , , ,DPI_SIZ(640),DPI_SIZ(480)

oncmd gosub *chgdpi,WM_DPICHANGED//DPI変更時の割り込み
title "高DPI対応テスト"
	cls
	font "Meiryo",DPI_SIZ(16)

	color 0,0,0
	mes "現在のDPI : " + DPI
	mes "倍率: "+int((double(DPI)/96)*100)+"%"
	mes "ディスプレイのサイズ : "+GetSystemMetricsForDpi(0,DPI)+"×"+GetSystemMetricsForDpi(1,DPI)+" ディスプレイスケーリング :"+SIZ_DPI(GetSystemMetricsForDpi(0,DPI))+"×"+SIZ_DPI(GetSystemMetricsForDpi(1,DPI))
	mes "\n640×480 : "+DPI_SIZ(640)+"×"+DPI_SIZ(480)+" スケーリング"
	mes "ウィンドウのクライアント領域のサイズ : "+ginfo_winx+"×"+ginfo_winy 
	mes "\nウィンドウ全体サイズ : " + ginfo(10)+"×"+ginfo(11)
	
	color 0,0,0
	mes "\nタイトルバーの高さ : " + GetSystemMetricsForDpi(SM_CYCAPTION,DPI)
	mes "小さなタイトルバーの高さ : " + GetSystemMetricsForDpi(SM_CYSMCAPTION,DPI)
	mes "サイズを変更できないウィンドウの枠の幅と高さ : " + GetSystemMetricsForDpi(SM_CXFIXEDFRAME,DPI) + ", " + GetSystemMetricsForDpi(SM_CYFIXEDFRAME,DPI)
	mes "サイズを変更できるウィンドウの枠の幅と高さ : " + GetSystemMetricsForDpi(SM_CXSIZEFRAME,DPI) + ", " + GetSystemMetricsForDpi(SM_CYSIZEFRAME,DPI)
stop
//DPI設定変更後の処理
*chgdpi
//wparamに新しいDPIが返ってくる
DPI = HIWORD(wparam)
//ウィンドウを表示する位置を取得(Rect構造体)
dupptr x_pos,lparam,  4 : dupptr y_pos,lparam+4,4
//dupptr x_size,  lparam+8,  4:dupptr y_size, lparam+12, 4//未使用

	WidthDPI DPI_SIZ(640),DPI_SIZ(480),x_pos,y_pos,DPI,1//ウィンドウのサイズの変更や位置を移動させる
 x_pos=0:y_pos=0
	
	cls
	font "Meiryo",DPI_SIZ(16)

	color 0,0,0
	mes "現在のDPI : " + DPI
	mes "倍率: "+int((double(DPI)/96)*100)+"%"
	mes "ディスプレイのサイズ : "+GetSystemMetricsForDpi(0,DPI)+"×"+GetSystemMetricsForDpi(1,DPI)+" ディスプレイスケーリング :"+SIZ_DPI(GetSystemMetricsForDpi(0,DPI))+"×"+SIZ_DPI(GetSystemMetricsForDpi(1,DPI))
	mes "\n640×480 : "+DPI_SIZ(640)+"×"+DPI_SIZ(480)+" スケーリング"
	mes "ウィンドウのクライアント領域のサイズ : "+ginfo_winx+"×"+ginfo_winy 
	mes "\nウィンドウ全体サイズ : " + ginfo(10)+"×"+ginfo(11)
	
	color 0,0,0
	mes "\nタイトルバーの高さ : " + GetSystemMetricsForDpi(SM_CYCAPTION,DPI)
	mes "小さなタイトルバーの高さ : " + GetSystemMetricsForDpi(SM_CYSMCAPTION,DPI)
	mes "サイズを変更できないウィンドウの枠の幅と高さ : " + GetSystemMetricsForDpi(SM_CXFIXEDFRAME,DPI) + ", " + GetSystemMetricsForDpi(SM_CYFIXEDFRAME,DPI)
	mes "サイズを変更できるウィンドウの枠の幅と高さ : " + GetSystemMetricsForDpi(SM_CXSIZEFRAME,DPI) + ", " + GetSystemMetricsForDpi(SM_CYSIZEFRAME,DPI)

return 0
//高DPIで正確なウィンドウサイズを変更する命令
//WidthDPI命令 wx 変更Xサイズ ,wy 変更Yサイズ , psx ウィンドウのX位置, psy ウィンドウのY位置 _DPI DPIの値(初期は96), _opt  ウィンドウの表示位置オプション(0=位置を移動しない 1=位置を移動する)
#deffunc WidthDPI int wx,int wy,int psx ,int psy ,int _DPI,int _opt
	st = GetWindowLong(hwnd,-16)
	est=GetWindowLong(hwnd,-20)
	if wx >= 0 | wy >= 0{
		dim rect,4 : rect=0,0,wx,wy
		//旧バージョンのWindows10やそれ以前のOSではAdjustWindowRectExForDpi関数が存在しないため、varptrでその関数が存在するかチェックする。
		if varptr(AdjustWindowRectExForDpi){AdjustWindowRectExForDpi varptr(rect),st,0,est,_DPI}else{AdjustWindowRectEx varptr(rect),st,0,est}
		w=rect(2)-rect(0) : h=rect(3)-rect(1)
		SizeOpt = 0
	}else{
		SizeOpt =  SWP_NOSIZE
	}
	if _opt = 0:SizeOpt = SizeOpt | SWP_NOMOVE
	SetWindowPos hwnd,0,psx,psy,w,h,SizeOpt
return

#defcfunc GetSystemMetricsForDpi int p_1,int p_2
if varptr(_GetSystemMetricsForDpi){_GetSystemMetricsForDpi p_1,p_2}else{GetSystemMetrics p_1}
return stat
return

基本的に高DPIに対応させる方法として、GetDeviceCaps関数でDPIを取得し、そのDPIを96を割ることで1ドットの単位が求められます。

今回はマクロで定義します。

DPI=GetDeviceCaps(GetDC(0),LOGPIXELSY)
#define global ctype DPI_SIZ(%1) (%1*DPI/96)
#define global ctype SIZ_DPI(%1) (%1*96/DPI)

ソースコードの流れ

F5デバッグ時にuser32.dllのSetProcessDpiAwarenessContext関数を呼び出しDPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2を指定します。
どうやらこの関数は、Windows 10 バージョン1703以降らしい?

opt = DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2:SetProcessDpiAwarenessContext opt

そのあと、デバッグ時にマニフェストのファイルを保存します。

exist "DPI.manifest"
if strsize <= 0{
mani = {"
<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>
<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\">
<assemblyIdentity
version=\"1.0.0.0\"
processorArchitecture=\"*\"
name=\"OnionSoftware.hsp3.exe\"
type=\"win32\"
/>

<application xmlns=\"urn:schemas-microsoft-com:asm.v3\">
<windowsSettings>
<dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true</dpiAware>
<dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>

<description>XPStyle</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type=\"win32\"
name=\"Microsoft.Windows.Common-Controls\"
version=\"6.0.0.0\"
processorArchitecture=\"*\"
publicKeyToken=\"6595b64144ccf1df\"
language=\"*\"
/>
</dependentAssembly>
</dependency>
<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">
  <application>
    <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" />
    <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" />
    <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" />
    <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" />
  </application>
</compatibility>
</assembly>
"}
notesel mani
notesave "DPI.manifest"
}

APIやマニフェストでDPI対応を宣言して、次にuser32.dllのGetDpiForSystem関数でDPIを取得します。
ただし、古いWindows10のバージョンやそれ以前のOSのバージョンではこの関数が存在しないため、gdi32.dllのGetDeviceCaps関数で取得するようにしています。

//旧バージョンのWindows10やそれ以前のOSではGetDpiForSystem関数が存在しないため、varptrでその関数が存在するかチェックし、存在しなければ別の関数でDPIの値を取得。
if varptr(GetDpiForSystem){DPI=GetDpiForSystem()}else{DPI=GetDeviceCaps(GetDC(0),LOGPIXELSY)}

oncmdにWM_DPICHANGEDを指定します。DPIが変更された際に割り込んできます。
wparamのHIWORDに新しいDPIが返ってきます。
lparamにも値が返ってくるので、dupptrでrect構造体を取得します。

oncmd gosub *chgdpi,WM_DPICHANGED//DPI変更時の割り込み
DPI = HIWORD(wparam)
//ウィンドウを表示する位置を取得(Rect構造体)
dupptr x_pos,lparam,  4 : dupptr y_pos,lparam+4,4
//dupptr x_size,  lparam+8,  4:dupptr y_size, lparam+12, 4//未使用

新しいDPIとlparamのrect構造体に表示するウィンドウの位置を取得し、WidthDPIでウィンドウサイズを変更します。
流れとしては大体こんな感じです。

高DPIのWidthでは、正確にサイズが変更できないようなので、新たにWidthDPI命令というのを作っています。

#deffunc WidthDPI int wx,int wy,int psx ,int psy ,int _DPI,int _opt
	st = GetWindowLong(hwnd,-16)
	est=GetWindowLong(hwnd,-20)
	if wx >= 0 | wy >= 0{
		dim rect,4 : rect=0,0,wx,wy
		//旧バージョンのWindows10やそれ以前のOSではAdjustWindowRectExForDpi関数が存在しないため、varptrでその関数が存在するかチェックする。
		if varptr(AdjustWindowRectExForDpi){AdjustWindowRectExForDpi varptr(rect),st,0,est,_DPI}else{AdjustWindowRectEx varptr(rect),st,0,est}
		w=rect(2)-rect(0) : h=rect(3)-rect(1)
		SizeOpt = 0
	}else{
		SizeOpt =  SWP_NOSIZE
	}
	if _opt = 0:SizeOpt = SizeOpt | SWP_NOMOVE
	SetWindowPos hwnd,0,psx,psy,w,h,SizeOpt
return

WidthDPIはAdjustWindowRectEx関数のDPIに拡張したAdjustWindowRectExForDpi関数で正確なサイズを取得しています。

また、GetSystemMetrics関数をDPIに拡張したGetSystemMetricsForDpi関数でタスクバーの高さなどを取得しています。

GetDpiForSystem関数と同様、Windows10の古いバージョンやそれ以前のOSだとAdjustWindowRectExForDpi関数GetSystemMetricsForDpi関数が存在しないようなのでvarptrでチェックしています。

注意点

この方法の注意点として、実行中にDPIを変更した場合、screenでウィンドウを初期化すると、Y(縦方向)のサイズがずれてしまうため、screenでウィンドウ初期化した後、ginfo_winyでYのサイズの誤差を求め、その後screenで初期化する必要があります。

		screen 0,640*8,480*8/*あらかじめ大きめの初期ウィンドウサイズを確保*/, screen_hide , , ,DPI_SIZ(640),DPI_SIZ(480)
		if DPI_SIZ(480) < ginfo_winy{
			gosa = ginfo_winy - DPI_SIZ(480)
			screen 0,640*8,480*8/*あらかじめ大きめの初期ウィンドウサイズを確保*/, screen_hide , , ,DPI_SIZ(640),DPI_SIZ(480) - gosa
			dialog "Large"
		}if  DPI_SIZ(480) > ginfo_winy{
			gosa = DPI_SIZ(480) - ginfo_winy
			screen 0,640*8,480*8/*あらかじめ大きめの初期ウィンドウサイズを確保*/, screen_hide , , ,DPI_SIZ(640),DPI_SIZ(480) + gosa
			dialog "Small"
		}
			/*WidthDPI DPI_SIZ(640),DPI_SIZ(480),0,0,DPI,0*/
		gsel 0,1

ぜひ参考になれば幸いです。

HSPでWM_DPICHANGEDを使う方法の記事がなかったので書き込みました。

参考にした記事
Windows ディスプレイ拡大率の反映と実DPIの取得

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?