# VBForums UtilityBank > UtilityBank - Tutorials >  [VB6] Tutorial: Being DPI Aware

## LaVolpe

*DPI AWARENESS AND YOUR VB6 APPLICATION*

The intent of this thread is to have it be a VB6 community resource for DPI related issues and solutions.

I started it by providing lots of information that I've discovered over the past several months. I do not have every operating system to play on, nor do I have multi-monitor systems nor rotating monitors to play with. This is where you, the VB6 community comes in. Sharing solutions is what makes this forum such a great resource. Sharing DPI-related solutions can make this thread an invaluable resource.

I ask that you do not post questions to this thread. I ask that you post issues and solutions related to DPI. You may contradict or caveat remarks I or others have made to clarify or correct. *If you have specific DPI questions, I ask you to post them in the appropriate part of the questions-related forums*, and if a solution is discovered, then I ask that you come back here and share your discoveries. I will do the same.

The references below will be updated for some time to come. As you post your discoveries, I will add them here so that people can quickly jump to a topic/area they are particularly interested in.

If you wish for me to add a 'jump-to' link, like the ones below, for any submissions you make to this tutorial, please include the following tags around the 1st word(s) or sentence in your submission/post:
[aTarget=abc] [/aTarget] Change abc to a somewhat unique name/marker.

*References*

Writing High-DPI Win32 Applications
Post #2 Overview
Post #3 shows a DPI-aware manifest entry & discusses VB controls that have issues with DPI
Post #4 discusses issues with VB usercontrols at some DPIs
Post #5 offers a rather simple method of scaling VB picture objects
Post #6 discusses saving/caching values as twips vs. pixels
Post #9 discusses incorrect values returned by ScaleX/Y when converting vbHimetric
Post #10 discusses gdiScaling option as viable vs. no scaling options

----------


## LaVolpe

*DPI AWARENESS AND YOUR VB6 APPLICATION*

*Terms Used*

Dots per inch (DPI). DPI = 1440 / Twips Per Pixel
Virtualized DPI: Your application thinks it is running at 96 DPI but really isn't
Manifest: Internal or external application manifest associated with your application
TWIP: DPI independent value. TWIP = 1440th of an inch 
Twips Per Pixel (TPP): DPI dependent value. TPP = 1440 / DPI

Notes whether virtualized DPI applies or not: 
a. DPI and TPP are inversely proportional. As one increases, the other decreases
b. DPI can be considered as VB DPI and System DPI. They can be different values
c. TPP can be considered as VB TPP and System TPP. They can be different values

*Background*. Following are generic comments, not VB-specific. VB is addressed later.

Should you be concerned with a user's DPI setting? The answer is a resounding "Yes" if you want your app to maintain a professional appearance. Without making your app DPI-aware, some controls will not resize with the form and other controls. Text and graphics may appear blurred after resizing. 




> Writing a DPI-aware application is the key to making a UI look consistently good across a wide variety of high-DPI display settings. An application that is not DPI-aware but is running on a high-DPI display setting can suffer from many visual artifacts, including incorrect scaling of UI elements, clipped text, and blurry images. By adding support in your application for DPI awareness, you guarantee that the presentation of your application's UI is more predictable, making it more visually appealing to users.


A "normal" setting will be 96 DPI. Without scaling for DPI, at larger resolutions, fonts, icons, and applications in general appear smaller. With monitors nowadays, screen real estate and resolution is much greater than in recent years past. Users may opt to increase the DPI settings at higher resolutions so that these items appear larger, easier to read. Windows 7 may opt to automatically increase DPI based on the monitor's size and resolution. Previous operating systems require users to make that decision.

Most references to DPI you will see are either a percentage or actual DPI value. 
- Convert from percent to DPI value:  96 * PercentValue / 100, i.e., 125% = 120 DPI
- Convert from DPI value to percent:  DPI / 96 * 100, i.e., 144 DPI = 150%

Samples of various DPI scaling. In order from top to bottom: 1) 100% DPI, 2) 200% DPI virtualized, 3) 200% DPI XP-Scaling or DPI-aware
The failure of the controls to scale/position correctly are addressed in the next posting. The option button & checkbox are crystal clear & sharp in #3. Forum scaled uploaded image & looks a tad fuzzy here





*DPI Scaling*

There are 2 basic types of automatic DPI scaling used by Windows, since Windows XP.

1) XP. Fonts are scaled, they are not simply stretched but a larger font is used for display purposes. The font size in your VB controls is unchanged. This presents a much more pleasing output than say stretching the output 1.25  to 2 times its original size

2) Vista/Win7. There are two scaling modes used that apply to all applications except DPI-aware ones

a) XP-Style scaling. This is a system-wide setting and disables DPI virtualization. No longer available as of Win8. After Win7, your only option is virtualization if the app is not declared DPI-aware.

b) DPI Virtualization. This is probably the worst option and applies to applications that are not DPI-aware. What happens is that your application runs under a 96 DPI environment, regardless of real DPI setting. Anything that is displayed on screen is rendered to an off-screen bitmap and stretched to the DPI scale. For an easy visualization, screen capture your app into MS Paint. Now stretch that image by 125% to 200%. That is exactly what your app will look like in DPI virtualization if it is not declared as DPI-aware or DPI vritualization is not disabled.

DPI-aware applications can only be self-identified via application manifests or, on Vista and above, the SetProcessDPIAware API. When an application is declared DPI-aware, fonts are scaled but controls are not.

How do you tell Windows that your application is DPI-aware? Well, on XP you simply cannot. On higher operating systems, your app can do this two ways. The customer/user can also do this two ways....

1) If your app contains an embedded or external manifest file declaring it as DPI-aware, then DPI-virtualization will never apply to your app.

2) Supposedly, the SetProcessDPIAware API can declare your app as DPI-aware. However, I have not tested it. Per MSDN, use of this API is highly discouraged. It has the potential to affect the various dependencies your app is using. I am also unsure as to whether this must be called before your app starts (i.e., placed in Public Sub Main) or whether it can be called at any time. Bottom line, avoid this API.

Whether or not your app is declared as DPI-aware, the user can force it to behave in the XP style of DPI scaling. This prevents DPI virtualization. Though it is an option, I doubt many typical people would opt for this. The user can do it two ways....

1) Right click on your app and in the compatibility tab of the properties window, opt to disable DPI scaling

2) Open the Personalization/Screen properties window and change the DPI to a custom setting. In that window, is an option to force all apps to use XP style DPI scaling. This option disappeared in Win8.


*The Problem*. More work to be DPI-aware.

When an app declares itself DPI-aware, only fonts are scaled by the system. Controls are not and neither is the app window itself. This holds true for any window that is created manually via APIs or languages that do not have any internal scaling options. *VB, on the other hand, is, more or less, DPI aware*. Its control dimensions and positions are stored in TWIPs. Twips are DPI independent. How many Twips per pixel is DPI-dependent. So when the DPI changes, twips per pixel changes, and the control's size and position changes also. Most VB controls will be scaled based on the DPI setting. You have no choice in the matter, other than trying to painstakingly undo the scaling. 

So, you might ask, "where's the problem? All I need is a manifest file and good to go." Unfortunately, not all VB6 controls will be scaled. I would suspect the content of many custom usercontrols also would not be scaled correctly. Images assigned to properties are never scaled. The problem consists of four main areas: 1) let Windows know your app is DPI-aware so it doesn't put it in DPI virtualization, 2) deal with controls that do not scale, 3) graphics/fonts, and 4) API usage. The next few posts talk about these issues

*Per-Monitor DPI Awareness*. This is a topic left for the adventurous. Using a manifest, Win8 or higher, you can declare your app as per-monitor DPI aware. By default, VB will use the DPI and screen dimensions of the primary monitor. Since many setups can include multiple monitors (using different DPIs) and more recent operating systems allow real-time DPI changes to the monitors (without rebooting), DPI can change while your app is running. Via manifests, you can opt to adjust your application's layout and scale based on DPI changes. This does require subclassing to receive the WM_DPICHANGED message, but also requires a whole lot more. Since VB is only DPI-aware, if you will, of the primary monitor and only at startup, not if changed later, you will have to handle scaling changes all on your own. The system will take care of the non-client 'graphics' for the most part. If you opt out of per-monitor DPI awareness, the system places your app in DPI virtualization when DPI changes.

----------


## LaVolpe

Here we will discuss some DPI-related issues with emphasis on VB.

*I. Manifests*. To include DPI-awareness in your manifest, include a section like the following


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

To add per-monitor awareness change <dpiAware>true</dpiAware> to <dpiAware>True/PM</dpiAware>
Note: Win10 offers a new setting, titled: dpiAware*ness*. It overrides dpiAware if dpiAwareness exists and only overrides in Win10 else ignored for lower O/S.

*II: Controls negatively affected by DPI scaling*. Screenshot at bottom of post highlights these issues.

Without addressing the following issues, you can expect these problems when DPI-aware. Suggest always saving objects with ScaleMode of twips. You can change the ScaleMode during Form_Load if necessary. But you want to ensure measurements in twips & nothing else is saved in the VB project files (frm, ctl, etc).

Image controls that have Stretch=False and Picboxes that have Autosize=True, can experience these issues because they will NOT scale.

a. Moving from lower to higher DPI. Other controls to the right & bottom of the affected control will appear proportionally larger & further away from the affected control's edges at higher DPI settings

b. Moving from higher to lower DPI. Worse. Controls to the right and bottom of the affected control will appear proportionally smaller & closer to the affected control. In fact, they can even overlap the affected control because its size does not scale, but the other control's positions are decreasing. Though this should never be a problem if you always design your form at 96 DPI. Don't think anyone nowadays will be running in 72 DPI (75%).

1. _VB Image Control_ 

Problem: When the Stretch property is false (default), the image & control will not scale to DPI. Other controls will change their size & position based on DPI. The image itself will not scale with the rest of the objects on your form. Exception: Metafiles loaded into an image control, where Stretch property is false, will scale to the screen's DPI

Fix: Always set the image control's Stretch property to True even if actual size is desired. 

2. _VB Picture Control_

Problem: When the AutoSize property is True, the control will not scale to DPI. Other controls will change their size & position based on DPI. Exception: Metafiles loaded into a picturebox control, where AutoSize property is True, will scale to the screen's DPI

Fix: Always set the control's AutoSize property to False after you add an image. During Form_Load, resize the image to the new picturebox dimensions. The code below will honor GIF/ICO transparency, however, the picbox image is no longer GIF/ICO format after the following is executed.


```
    Dim tPic As StdPicture
    With Picture1
        Set tPic = .Picture
        Set .Picture = Nothing
        .Cls
        .AutoRedraw = True
        tPic.Render (.hDC), 0, 0, _
            .ScaleX(.ScaleWidth, .ScaleMode, vbPixels), .ScaleY(.ScaleHeight, .ScaleMode, vbPixels), _
            0, tPic.Height, tPic.Width, -tPic.Height, ByVal 0&
        Set .Picture = .Image
        Set tPic = Nothing
        .AutoRedraw = False     ' remove line if not applicable
    End With
```

3. _Line Controls_. Simply ensure the form or other object, that the line is contained on, is saved with a ScaleMode of Twips.

4. _Label Controls_. If the AutoSize property is set, during Form_Load, toggle the AutoSize from True to False and back to True.

5. _ListBox Control_. The IntegralHeight property default value is True. After scaling, that property can result in the listbox scaling with other controls, then snapping back to a smaller height. The IntegralHeight property restricts the height due to item font similar to how AutoSize restricts pictureboxes due to image size. Might want to consider setting this property to false.

6. _Any other contro_l, especially non-standard VB controls. Suggest running your project at 200% DPI and see if that control is DPI aware enough. If it appears to scale ok but looks like it's not using all of its inside dimensions, see post #2 regarding resizing usercontrols. If it won't scale at all, you have 3 basic choices at that point: a) find a substitute (API version maybe) that will scale, b) create your own control, c) live with DPI virtualization; don't manifest for DPI awareness.

*III. Graphics and Fonts*. 
Fonts. Use TrueType or OpenType fonts for everything. They scale much better.
Graphics. Not much to say here. You will need to scale most graphics yourself unless a control will do it for you, i.e., Stretch property. A general purpose algorithm for scaling images. See post #5 for another option.
NewSize = OriginalSize * CurrentDPI / 96
Note about fonts. Whenever creating a form when DPI-awareness is applicable, the first thing you do is assign the form a TrueType font. Since many controls inherit the form's font, this can prevent you from having to individually set the font for many controls.

*IV. APIs*.  Many GDI APIs return virtualized settings if VB is running in virtualized DPI. If not running virtualized, you should have no real issues with GDI APIs. A common problem with some code used to get a screen capture, while virtualized, is that the screen capture is truncated; it doesn't include the entire screen. This is because when virtualized, VB reports Screen.Width,Height scaled from the current DPI to 96 DPI. Thankfully for some, not all GDI APIs report virtualized settings. The screen's actual size can be retrieved from the code shown a little bit further down the posting.

GDI+ has some functions that are DPI related. 

GdipDrawImage will render the image using the following formula. This is the only image rendering function I know of that is DPI aware. Per MSDN, whenever the width & height of the image are not provided to the rendering function, DPI awareness applies. This is the only image rendering function that doesn't have width & height parameters:
Formula: ScaledSize = (ImageRawSize * ScreenDPI) / EmbeddedImageDPI
GdipGetImageFlags will return a Long value that will tell you whether or not embedded DPI exists in the image. That Long value must be bitwise compared with &H1000. If that bit is set, embedded DPI exists. Note this fails on metafiles. 

GdipGetImageHorizontalResolution returns the horizontal DPI of the image. If no embedded DPI, then the screen's DPI is returned

GdipGetImageVerticalResolution returns the vertical DPI of the image. If no embedded DPI, then the screen's DPI is returned

GdipBitmapSetResolution will embed DPI information into an image (except metafiles). The image must later be saved to persist the setting. Not all image formats persist that setting.

*V. VB's Screen object*. This can be broken in a couple of different scenarios.

1. I do not have a screen that can be rotated 90 degrees. From what I've read is that after a VB project loads, the Screen.Width and Screen.Height do not reflect the change after rotation. If anyone can verify this, please do. In any case, I would then assume that querying GetDeviceCaps with HORZRES  & VERTRES should return the correct values, relative to being in virtualized DPI.

2. When VB is run in virtual DPI, the Screen.Width,Height properties return virtual values. This is not normally an issue except if needing the actual screen dimensions for API related stuff.

3. Screen.TwipsPerPixelX,TwipsPerPixelY may be incorrect. See next post for more information. The key here is that VB's TwipsPerPixel are always whole numbers. The real twips per pixel may be a fraction as seen with 200% DPI.

To address the Screen.Width,Height problems above, one can use the following code and get dimensions via APIs. Pass the 'Actual' parameter as True if you need the actual screen dimensions, whether virtualized or not. 


```
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, ByVal hDC As Long) As Long

' Note: When DPI virtualization exists, HORZRES,VERTRES return virtualized values

Public Property Get ScreenWidth(Optional ByVal Actual As Boolean) As Single
    ' returned as Twips
    If Actual Then
        Dim hDC As Long: hDC = GetDC(0)
        ScreenWidth = GetDeviceCaps(hDC, 118) * Screen.TwipsPerPixelX ' 118=DESKTOPHORZRES
        ReleaseDC 0, hDC
    Else
        ScreenWidth = Screen.Width
    End If
    
End Property

Public Property Get ScreenHeight(Optional ByVal Actual As Boolean) As Single
    ' returned as Twips
    If Actual Then
        Dim hDC As Long: hDC = GetDC(0)
        ScreenHeight = GetDeviceCaps(hDC, 117) * Screen.TwipsPerPixelY ' 117=DESKTOPVERTRES
        ReleaseDC 0, hDC
    Else
        ScreenHeight = Screen.Height
    End If
End Property

Public Property Get ScreenDPI(Optional ByVal Actual As Boolean) As Single
    If Actual Then
        Dim hDC As Long: hDC = GetDC(0)
        ScreenDPI = GetDeviceCaps(hDC, 118) / (Screen.Width / Screen.TwipsPerPixelX)
        ReleaseDC 0, hDC
        If ScreenDPI = 1 Then
            ScreenDPI = 1440! / Screen.TwipsPerPixelX
        Else
            ScreenDPI = ScreenDPI * 96!
        End If
    Else
        ScreenDPI = 1440! / Screen.TwipsPerPixelX
    End If
End Property
```

The following code can also be used to determine if you are in DPI virtualization or not


```
If ScreenWidth(False) <> ScreenWidth(True) Then MsgBox "Virtualized DPI"
```

================================================================================

The following screenshots highlight some VB control issues. Compare setting changes listed after the images. 
The 96 DPI form follows with some notes:

About the controls.
The blue line is a VB line control
The lion image is a metafile contained in a picturebox with AutoSize=True
The 4 controls above the option button are:
-- Image control Stretch=False, PicBox AutoSize=True, Image control Stretch=True, PicBox AutoSize=False
The option button & checkbox below are crystal clear & sharp. Forum scaled uploaded image & looks a tad fuzzy here


Image above is same form at 200% DPI, manifested as DPI aware. Notice a few things:
- Line size & position is hosed. This is because the form was saved with a ScaleMode of pixels
- The icon images did not scale, only 1 of the image & picbox controls scaled because of the Stretch & AutoSize property settings
- The label dimensions are larger than the label text
- The button's icon did not scale
- The lion scaled with AutoSize=True. Metafiles scale with DPI
- Fonts are VB defaults: not TrueType


Image above is same, but with some tweaks made
- Line control scaled correctly because its form was saved with ScaleMode of Twips
- Label control's AutoSize was toggled during Form_Load
- All 4 icon images scaled. Image controls set Stretch=True, Picbox images were manually scaled during Form_Load, AutoSize=False
- Button's icon scaled with code in post #5
- Lion picbox AutoSize left as True
- Fonts are TrueType

----------


## LaVolpe

DPI and VB created UserControls. Potential for lots of scaling issues. The following applies to DPI-aware VB apps.

Even if you are not writing a usercontrol, this information is nice to know if you are having problems sizing a usercontrol from your form via code. It may just apply.

Since you have no control whether the host of your usercontrol (*UC*) will be in a DPI-aware environment or not, you should write your UC to be DPI aware. Not suggesting it be per-monitor DPI aware. Besides the effort of making your control DPI aware, you have to be alert to the fact that VB may lie to you regarding your UC's actual size. Huh?

When VB is running in an untypical DPI, its Twips Per Pixel (*TPP*) value may not be accurate. And since UC sizes are twips-based, this could be an issue. UCs are sized internally via the UserControl.Size command, along with the UC's Width & Height properties, twips are used. Here is when VB breaks. TPP is calculated simply as: 1440/DPI. Typical DPIs like 100%,125%,150% do not cause a problem, because the TPP calculation results in a whole number: 1440/96, 1440/120, 1440/144, respectively. At 200% (192 DPI), TPP would be expected to be 7.5: 1440/192 = 7.5. However, VB reports it as 7. 

*The rest of this post pertains to the scenario where real TPP is not a whole number*
Here is link to a thread that discusses this issue when I first discovered it

Unfortunately, this issue results in the host thinking the UC is one size while the UC is thinking it is another size. If both the UC and the form it is hosted on are in scalemode of Twips, then the Form's value for the UC's width should be the same as the UserControl.Width from within the UC, but they are not. The UC will render mostly fine, except that it will not fill out the dimensions you are setting from the host. In other words, when you set the Width and/or Height of the usercontrol, it is drawing smaller than it should.

1. Getting the UC's internal dimensions same as reported by its host


```
Private Sub UserControl_Resize()
    Static isResizing As Boolean
    If Not isResizing Then
        isResizing = True
        Extender.Move Extender.Left - Screen.TwipsPerPixelX, Extender.Top - Screen.TwipsPerPixelY
        Extender.Move Extender.Left + Screen.TwipsPerPixelX, Extender.Top + Screen.TwipsPerPixelY
        isResizing = False
    End If
End Sub
```

2. Do not use UserControl.Width,Height to retrieve dimensions, instead:


```
Private Sub GetSize(WidthTwips As Single, HeightTwips As Single)
    WidthTwips = ScaleX(Extender.Width, vbContainerSize, vbTwips)
    HeightTwips = ScaleY(Extender.Height, vbContainerSize, vbTwips)
End Sub
```

3. Resizing...
a.Internally. Do not use UserControl.Size to resize your control. It will not work correctly for untypical DPIs. Rather use something like this:


```
Private Sub SetSize(newWidthTwips As Single, newHeightTwips As Single)
    Extender.Move Extender.Left, Extender.Top, _
        ScaleX(newWidthTwips, vbTwips, vbContainerSize), _
        ScaleY(newHeightTwips, vbTwips, vbContainerSize)
End Sub
```

b.Externally. If the usercontrol appears to be faltering when rendered as described above, you can attempt to use this code to correct it. If any graphics within the control are not scaling, it is not written to be DPI aware.


```
WIth UserControl1
    .Move .Left + Screen.TwipsPerPixelX, .Top + Screen.TwipsPerPixelY, newWidth, newHeight
    .Move .Left - Screen.TwipsPerPixelX, .Top - Screen.TwipsPerPixelY
End With
```

To determine if the DPI is an issue here, a simple calculation can be performed:


```
If (1440! \ Screen.TwipsPerPixelX) < (1440! / Screen.TwipsPerPixelX) Then ' non-whole number DPI
```

*Edited*: Do not use the above shortcut. It fails at 175% DPI. A solution like the one in previous post is more reliable.

The following function could also be used if just wanting to know if DPI could be a problem. Since this function is bound to virtualization, it should always return True if VB is virtualized. Maybe could be named better since virutalized DPI and system DPI are only the same when system DPI is 96. However, if this function returns FALSE, you are likely to have some DPI-related problems to address.


```
Private Declare Function GetDC Lib "user32.dll" (ByVal hwnd As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32.dll" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function ReleaseDC Lib "user32.dll" (ByVal hwnd As Long, ByVal hdc As Long) As Long

Public Function VbDpiSameAsOS(Optional DPI As Long) As Boolean
    Const LOGPIXELSX As Long = 88
    Dim dDC As Long
    dDC = GetDC(0)
    DPI = GetDeviceCaps(dDC, LOGPIXELSX)
    ReleaseDC 0, dDC
    VbDpiSameAsOS = ((1440 / DPI) = (1440 \ DPI))
End Function
```

To get your UC in sync with the host form, you should resize your UC from outside of your UC, not from within. In other words, you should forever abandon usage of UserControl's properties: Width, Height,  ScaleWidth, ScaleHeight and the Size method. Instead, you will want to use the UserControl.Extender object to get and set dimensions.

The Extender object exists for every VB UC. However, its properties and methods can vary in different hosts: IE, Access, Word, VB, etc. The Extender's dimensions are what your user is seeing, not necessarily what VB is reporting to your UC. The above 'fixes' can be used when VB is the host for the UC. Other non-VB hosts will have to be handled differently, if they do not support these properties, and no one solution may work for them all.

So how does this mismatch between VB and real TPP effect me? After you keep your UC in sync with the host, it shouldn't effect you or anything you may have read from your UC's property bag. VB appears to adjust all of its measurements based on the VB TPP, not the real TPP. For example, a 34 pixel (510 twips) object, saved at 96 DPI, displayed on 200% DPI, reflects the following: 510 twips still, but not the 68 pixels one would expect at twice the DPI. What happens is that VB does not recognize 192 (200%) as the DPI, rather it uses 205.7143 (214%). That 34 pixel object is now 72.857 pixels vs. 68.
- 1440/7.5 real TPP = 192 DPI, but VB divides by 7: 1440/7 VB TPP = 205.7143
- 510 twips / 7.5 real TPP = 68 pixels as expected, but 510 twips / VB 7 TPP = 72.857 pixels

Therefore if you saved a value of say 34 to your property bag in pixels or twips, using that value within your project should pose no problems because you are likely using TwipsPerPixel for scaling or ScaleX,ScaleY. Since you are using the same scale VB is using, all should be well. Take away point: Don't use real DPI, nor real TPP, in your scaling.

----------


## LaVolpe

Here is a rather simple API solution to scaling VB picture objects. 
- This will not support GIF transparency.
- Not the 'best' solution because scaling up doesn't produce best quality
- Wouldn't suggest scaling image result, over & over again, if running as per-monitor DPI aware
-- cache original image (i.e., resource file) and scale that each time it is needed
- This should not be called for metafiles. If called, it returns the passed picture object. Metafiles scale correctly.

For better quality scaling, you should have multiple-size images available either via file, within your resource file or in a resource-only DLL.

Note: The routine requires passing the form, usercontrol, or anything with a ScaleX,ScaleY method as the ParentForm parameter. This is simply to enable usage of ScaleX,ScaleY should the code be placed in a class or module.

API declarations


```
Private Declare Function OleCreatePictureIndirect Lib "OLEPRO32.DLL" (lpPictDesc As Any, riid As Any, ByVal fPictureOwnsHandle As Long, ipic As IPicture) As Long
Private Declare Function CopyImage Lib "user32.dll" (ByVal handle As Long, ByVal uType As Long, ByVal cx As Long, ByVal cy As Long, ByVal flags As Long) As Long
```

The Routine


```
Public Function ScaleStdPicture(ByVal thePic As StdPicture, ParentForm As Object, TwipsPerPixel As Long) As IPicture
' note: TwipsPerPixel parameter value is relative to the desired DPI to scale to.

    'Private Type PictDesc
    '    Size As Long
    '    Type As Long
    '    hHandle As Long
    '    lParam1 As Long      for bitmaps/WMF only
    '                         WMF = extentX, BMP = Palette handle
    '    lParam2 As Long      for WMF only: extentY

    'End Type
    
    Dim lpPictDesc(0 To 3) As Long  ' equivalent to a PictDesc structure
    Dim aGUID(0 To 3) As Long       ' equivalent to GUID
    Dim hImage As Long
    Dim cx As Long, cy As Long
    Const LR_COPYFROMRESOURCE As Long = &H4000
    Const LR_COPYRETURNORG As Long = &H4
    
    If thePic Is Nothing Then Exit Function
    
    On Error Resume Next
    cx = ParentForm.ScaleX(thePic.Width, vbHimetric, vbPixels)
        cx = cx * (1440 / TwipsPerPixel) / 96
    cy = ParentForm.ScaleY(thePic.Height, vbHimetric, vbPixels)
        cy = cy * (1440 / TwipsPerPixel) / 96
    If Err Then ' something's wrong, passed invalid Object?
        Err.Clear
    Else    
        Select Case thePic.Type
            Case vbPicTypeBitmap
                hImage = CopyImage(thePic.handle, 0&, cx, cy, LR_COPYRETURNORG)
            Case vbPicTypeIcon
                hImage = CopyImage(thePic.handle, 1&, cx, cy, LR_COPYFROMRESOURCE Or LR_COPYRETURNORG)
                If hImage = 0& Then
                    hImage = CopyImage(thePic.handle, 1&, cx, cy, LR_COPYRETURNORG)
                End If
            Case Else
        End Select
    End If
    On Error GoTo 0

    If hImage = 0& Or hImage = thePic.handle Then
        Set ScaleStdPicture = thePic
    Else
        ' fill in PictDesc structure
        lpPictDesc(0) = 16&
        lpPictDesc(1) = thePic.Type
        lpPictDesc(2) = hImage
        ' IPicture GUID {7BF80980-BF32-101A-8BBB-00AA00300CAB}
        aGUID(0) = &H7BF80980
        aGUID(1) = &H101ABF32
        aGUID(2) = &HAA00BB8B
        aGUID(3) = &HAB0C3000
    
        ' create stdPicture
        Call OleCreatePictureIndirect(lpPictDesc(0), aGUID(0), True, ScaleStdPicture)
    End If
    
End Function
```

Sample usage: Set Image1.Picture =  ScaleStdPicture(Image1.Picture, Me, Screen.TwipsPerPixelX)

Edited: If you are loading 32bpp alpha blended icons via the API LoadImage, you could use the formula above for calculating the scaled dimensions and pass those to the API also. Example of asking for a 32x32 icon scaled to VB's current DPI:


```
hIcon = LoadImage(0, filename, IMAGE_ICON, _
    32& * (1440 / Screen.TwipsPerPixelX) / 96, 32& * (1440 / Screen.TwipsPerPixelY) / 96, _
    LR_LOADFROMFILE)
```

----------


## LaVolpe

Let's talk a bit about caching coordinates and dimensions.

1. The first question people may ask, "Do I save these as pixels or twips?" The answer is, "It's up to you". But keep these in mind... I believe the best solution is to save these values in Twips. The reason I feel this way is that it can prevent you from having to rescale your saved settings on app startup. 

For example, if your app is at position 300x300 (pixels) when it was closed and the DPI is 150% (10 TPP). The Left/Top position of the app would be 3000x3000 in Twips (300 pixels * 10 TPP = 3000 Twips). If the app is later run in 100% DPI (96 DPI and 15 TPP), the left/top position of the app is automatically scaled for you: 3000 Twips / 15 TPP = 200x200 which is 300x300 scaled  from 150% DPI to 100% DPI.

And the reverse is true because Twips are independent of DPI. If at 100% DPI, your app was last seen at coordinates 200x200 (pixels), the twips value is still 3000x3000 (200 pixels * 15 TPP). If your app was next run at 150% DPI, those twips would result in pixel values of 300x300 (3000 Twips / 10 TPP).

From the posts above, we know that at 200% DPI, VB doesn't scale everything by 2x. VB actually scales it a bit bigger. It isn't technically correct, but to compensate for this would require manually scaling nearly everything yourself. That being said, storing values as twips is far easier in the long run since they are DPI-independent. 

However, above  being said... If you want to save values relative to the current DPI, then save suggest saving the current DPI, along with the coordinate/position values. When needed, you will have both the values and the DPI they refer to.

2. Never hardcode any conversion in your code. You can see code all over the net that converts twips to pixels by dividing the twips by hardcoded 15. This only works if your app is run at 96 DPI or virtualized DPI. 

3. Never hardcode control offsets/sizes as pixels, unless you want to scale them. 10 pixels at 96 DPI is not the same proportionally at 144 DPI unless you scale the pixel value up. Likewise, never hardcode offsets as twips in 15 twips per pixel increments, unless you take into account the current DPI that the IDE is running in. Yes, VB6 IDE can be run in DPIs other than 96.

When all said and done, there will be exceptions depending on how you plan to use the measurements you are caching/saving. The above is to get you to think about "what ifs" pertaining to potentially different DPI environments your app is designed in and/or run in.

Tip: If you need to scale between two DPI values:


```
' the standard formula is: newValue = oldValue * CurrentDPI / OldDPI
    oldValue = 100: CurrentDPI = 144: OldDPI = 96
    ' So, 100 * 144 / 96 = 150

' another way of doing the same thing: newValue = oldValue * OldTwipsPerPixel / NewTwipsPerPixel
    oldValue = 100: NewTwipsPerPixel = 10: OldTwipsPerPixel = 15
    ' So, 100 * 15 / 10 = 150
```

At the form level, keep in mind that the screen DPI and VB's DPI may not be the same even if your project is declared DPI aware. As shown in posts #3 & #4, you can determine if this true. When this is the case, VB will not scale by the actual DPI, but its DPI (1440 / Screen.TwipsPerPixel). At 200% DPI, for example, and your form was designed at 96 DPI to show as 500x500, it will actually show at 200% DPI as 1071x1071 not 1000x1000. This is because real DPI is 200% but VB DPI is 214%. Under most circumstances this isn't an issue, but in your specific case, it just may be.

----------


## Arnoutdv

Question about the following



> 2. Do not use UserControl.Width,Height to retrieve dimensions, instead:
> 
> 
> 
> ```
> Private Sub GetSize(WidthTwips As Single, HeightTwips As Single)
>     WidthTwips = ScaleX(Extender.Width, vbContainerSize, vbTwips)
>     HeightTwips = ScaleY(Extender.Height, vbContainerSize, vbTwips)
> End Sub
> ```


Is there also an alternative needed when using UserControl.ScaleWidth,ScaleHeight?

----------


## LaVolpe

The common controls in your VB IDE toolbox may not scale properly in 200% DPI. The common controls (version 5) treeview, for example, did not. Using the double move solution in post #4 above worked. Here is a reusable, simple routine that can be added to projects for the purpose of resizing controls. Obviously only applies for controls that have the Move method (i.e., not for Line controls).


```
Private Sub pvRescaleUserControl(TheControl As Control, TwipsWidth As Single, TwipsHeight As Single, _
                            Optional TwipsLeft As Variant, Optional TwipsTop As Variant)
                            
    ' passed control's container scalemode must be in TWIPS; else modify below using ScaleX,ScaleY as needed
    With TheControl
        If IsMissing(TwipsLeft) Then TwipsLeft = .Left
        If IsMissing(TwipsTop) Then TwipsTop = .Top
        If (1440! \ Screen.TwipsPerPixelX) = (1440! / Screen.TwipsPerPixelX) Then
            .Move TwipsLeft, TwipsTop, TwipsWidth, TwipsHeight
        Else
            .Move TwipsLeft + Screen.TwipsPerPixelX, TwipsTop + Screen.TwipsPerPixelY, TwipsWidth, TwipsHeight
            .Move TwipsLeft, TwipsTop 
        End If
    End With

End Sub
```

sample call: pvRescaleUserControl TreeView1, newWidth, newHeight

Edited: FYI. The 1440 & Screen.TwipsPerPixelX ratio above will fail at 175% DPI. This is because VB reports TwipsPerPixel As 8 and real TwipsPerPixel would be 8.57. But 1440 divided by 8 (both integer & real division) result in same value -- failure. Unfortunately, this means there is no fool-proof shortcut to determine when VB & System TwipsPerPixel don't match each other than comparing VB DPI to real DPI. Using a function like the one in post #3 can be useful here:


```
If ScreenDPI(True) <> ScreenDPI(False) Then ...
```

or the one in post #4


```
If VbDpiSameAsOS() = False Then ...
```

FYI: Following system DPI percentages should mathematically match VB's reported TPP and 175% is not in the list: 50%,75%,100%,125%,150%,250%,300%,375%

----------


## LaVolpe

Another oddity to report regarding using ScaleX/ScaleY to return the actual size of stdPicture images.

Typically, we are used to converting himetric to pixels/twips like so: ScaleX(Me.Picture.Width, vbHimetric, vbPixels)
However, in atypical DPIs like 175%, 200%, etc, that breaks too. The function still works, but does not return the actual size of the image. For example, on a 256x256 image @ 175% DPI, you'd expect the function to return 256 as the width, correct? Not a trick question. Sure, the DPI increased, but himetric units adjust with DPI. Remember, real TPP is 1440/DPI, so at 175% DPI (168 DPI), 1440/168 is 8.57 twips per pixel and VB reports 8. This means VB's DPI is not same as system DPI, and himetric units reflect real DPI. VB's ScaleX/Y is broken in this scenario. Therefore, your image dimensions via ScaleX/Y are off too. In the above case, 274 is returned at 175% DPI. That miscalculation is a big deal if you are getting image dimensions this way.

A workaround is to forego VB's ScaleX/Y when attempting to get the image dimensions from vbHimetric units. You have a few options: 
1. Manually parse the different image formats to get the dimensions from the raw source -- not fun for most
2. Use APIs that can give you dimensions based on the image handle.
3. Or the simplest method: a well known formula for converting himetric to pixels. This method does require real DPI, not VB DPI. In other words, do not get the DPI like so: 1440/Screen.TwipsPerPixelX. Since VB's TPP is likely off in these atypical DPIs, so will your DPI value. If you did that, you'd get the same bad value returned by ScaleX/Y. Use GetDeviceCaps API to get the real DPI. Then apply this substitute:

Instead of ScaleX(Me.Picture.Width, vbHimetric, vbPixels)
Use: CLng(Me.Picture.Width / 2540! * [RealDPI]) -- [RealDPI] = 168 in this exampleReplace Width for Height to get the picture height. The above formula should return actual image dimensions. Of course, since you are running at a higher DPI, you'll want to scale them. But at least you know their real sizes to apply it to a scaling factor.

----------


## LaVolpe

With the Win10 Creators Update, the optional gdiScaling option can be used as a better option than nothing. That option when included in a manifest offers better rendering of control borders, fonts and metafiles than if no manifest scaling options were used. However, the gdiScaling option does override any other manifest scaling options that may exist. This new option is only available in Win10 v1703 and higher. This option also results in a 'better' result during dynamic DPI changes, i.e., moving to other monitors using a different DPI or when user changes their DPI without logging off.

Ensure you use TrueType fonts for best results.

How this works is briefly described. When the current DPI is not 100% (96 DPI), the system will have the app draw everything at the next highest DPI that is a multiple of 96, i.e., 200%, 300%, etc. Then the result is scaled down to the current DPI. Scaling something twice its size and scaling down produces better results, on average, than scaling up to a non-100% multiple. Non-font and non-metafile graphics will not be crisp, but should be better than standard virtualization. VB will be virtualized when this option is used, it will report itself in 100% DPI. This option applies whether multiple monitor scenarios exist or not.

The manifest entry will look a bit like this:


```
    <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
            <gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>
        </windowsSettings>
    </application>
```

Edited: Here are 3 screenshots you can view for differences in quality and results. The screenshots include an app where 1) DPI scaling is performed within code, manifested as DPI-aware, 2) No manual scaling, manifested with gdiScaling option, and 3) No scaling options at all. The screenshots were taken from a desktop set at 150% DPI. The screenshots are zipped; otherwise; the rescaling by this site of the uploaded images would not provide for a true comparison between them.

----------


## VbNetMatrix

Hi Lavolpe!

I'm interested in joining the 'Force' (you  :Smilie:  to understand more about DPI

up until now, I have used personal solution to fix that problem, I would like to submit how I do it but I'm not sure I got the same problem you're talking/facing...

I know in Win10 (not sure if it's a bug) you can turn up to 400% fonts...  first turn up to 200% font, then reboot, then you get new option to turn up to 400%

so if you could post an example project that doesn't turn out good when system DPI is changed.  with a picture of what a good result would be, I would take your project and apply my own fix to test if that hold.

I made some subroutine that fix the program in the Form_Load Event and I'm not using manifest... but so far I got no complain from custommer...

tk for your help!

----------


## LaVolpe

Not sure exactly what you are asking for, so I guessed a little bit. Attached PNG is snapshot of forms with 2 labels, each using a font of 12pt size. One font is not True-Type and the other is. The PNG shows 3 examples at 400% DPI.

1) No manifest used
2) Manifested using "DPI Awareness"
3) Manifested using only "GDI Scaling" (only available on Win10 v1703 and later). No DPI awareness was set.

#1 is ugliest result. #2 and #3 appear to be very close for the scaled fonts, nearly identical. However, the titlebar is drawn better with #2.

----------


## VbNetMatrix

> Attached PNG is snapshot of forms


I'll look at it to understand more the issue and I'll come back.
kind of busy this week but I'll come back to benefit your knowledge on the problem soon.

thanks

----------


## wqweto

FYI, the Move with only Left and Top arguments trick does not fix custom usercontrol containers. These still clip contained controls to the smaller (inacurate) dimensions in 200% and similar non-integer twips/pixel DPIs.

Never tried setting ClipBehavior to None or ClipControls to false at design-time, which might alleviate the situation with control containers.

cheers,
</wqw>

----------


## LaVolpe

If you find a solution please post back. Haven't really messed with UC with ControlContainer property set to True. When I have time, I'll take a look-see and maybe might happen on a solution too. Sounds like this could be a major deal -- so far, all known VB-related problems seem to have workarounds. Thanx for the information.

Follow-up. If you find the time, maybe you can post an example and how to reproduce the issue?

----------


## VbNetMatrix

> I'll look at it to understand more the issue and I'll come back.
> kind of busy this week but I'll come back to benefit FROM your knowledge on the problem soon.
> 
> thanks


Edit: to benefit FROM your knowledge
I'm still interested at the subject I'm currently developping something I want to make DPI compliant... so I'll come back soon when I fix my own bug...

----------


## Seradex Dev

I have used this information to successfully handle DPI ratios other than 100%.
Here are a few points:
*Some controls such as the MSComctlLib.Toolbar, SSTab, and the ListView control are not resized properly.*
I found that for the last two that this can be adjusted using the ratio ((1440 / DPI) / (1440 \ DPI)) 'DPISizeAdjustRatio in the method below.
To accomplish this I used a modified version of a function you provided:



```
Public DPISizeAdjustRatio As Double
Public SystemDPI As Long

Public Function VbDpiSameAsOS() As Boolean
    Const LOGPIXELSX As Long = 88
    Dim dDC As Long

    dDC = GetDC(0)
    SystemDPI = GetDeviceCaps(dDC, LOGPIXELSX)
    ReleaseDC 0, dDC
    DPISizeAdjustRatio = ((1440 / SystemDPI) / (1440 \ SystemDPI))
    VbDpiSameAsOS = (DPISizeAdjustRatio = 1)
End Function
```

Edit: I thought I had found that using DPISizeAdjustRatio to manually resize certain controls does not always work.  It appeared to over adjust when the controls are too big.
It turned out that I was accidentally applying the ratio two times, once in Form_Activate, and once in Form_Resize, which caused it to over adjust the size.  The lesson: be careful not to apply the ratio multiple times.  :Mad: 

In addition, a child SSTab control placed on a parent SSTab, will resize when returning to the tab page it is on.  The parent will not though, unless it is a child itself.
Example: Child SSTab is on main page of parent SSTab.  When the form is loaded, the child SSTab is not sized correctly.  When going to a secondary page on the parent SSTab, and then back to the main page, the child SSTab is now sized correctly.
I am addressing this currently by resetting the adjusted size of the child SSTab back when leaving the main page.
If anyone has any better ideas, I would be grateful.

To adjust the toolbar, you can use multiple image lists for different DPI sizes, but if you are not concerned with how well the images look, you can use the following to rescale the images:


```
Public Type ImageListItemType
    Index As Integer
    Key As String
    Picture As IPictureDisp
    Tag As Variant
End Type

Public Sub AdjustToolbarImagesForDPIChange(Tlbr As MSComctlLib.Toolbar)
    Dim TlbrImgList(2) As MSComctlLib.ImageList
    Dim TlbrNdx As Long
    Dim ImgNdx As Long
    Dim ImgCount As Long
    Dim ImgHeight As Long
    Dim ImgList() As ImageListItemType
    Dim BtnImgIDs() As Variant

    ReDim BtnImgIDs(1 To Tlbr.Buttons.Count)

    For ImgNdx = 1 To Tlbr.Buttons.Count
        BtnImgIDs(ImgNdx) = Tlbr.Buttons(ImgNdx).Image
    Next

    Set TlbrImgList(0) = Tlbr.ImageList
    Set TlbrImgList(1) = Tlbr.HotImageList
    Set TlbrImgList(2) = Tlbr.DisabledImageList

    Set Tlbr.HotImageList = Nothing
    Set Tlbr.DisabledImageList = Nothing
    Set Tlbr.ImageList = Nothing

    With TlbrImgList(0)
        ImgHeight = .ImgHeight * 15 / Screen.TwipsPerPixelX
        ImgCount = .ListImages.Count
    End With

    For TlbrNdx = 0 To 2
        If Not TlbrImgList(TlbrNdx) Is Nothing Then
            With TlbrImgList(TlbrNdx)
                With .ListImages
                    ReDim ImgList(1 To ImgCount)

                    For ImgNdx = 1 To ImgCount
                        With .Item(ImgNdx)
                            ImgList(ImgNdx).Index = .Index
                            ImgList(ImgNdx).Key = .Key
                            Set ImgList(ImgNdx).Picture = .Picture
                            ImgList(ImgNdx).Tag = .Tag
                        End With
                    Next

                    .Clear
                End With

                .ImgHeight = ImgHeight
                .ImageWidth = ImgHeight

                With .ListImages
                    For ImgNdx = 1 To ImgCount
                        .Add(ImgList(ImgNdx).Index, ImgList(ImgNdx).Key, ImgList(ImgNdx).Picture).Tag = ImgList(ImgNdx).Tag
                    Next
                End With
            End With
        End If
    Next

    Set Tlbr.ImageList = TlbrImgList(0)
    Set Tlbr.HotImageList = TlbrImgList(1)
    Set Tlbr.DisabledImageList = TlbrImgList(2)

    For ImgNdx = 1 To Tlbr.Buttons.Count
        Tlbr.Buttons(ImgNdx).Image = BtnImgIDs(ImgNdx)
    Next
End Sub
```

*UserControls do not seem to resize some of the controls inside them until they obtain focus.*
To deal with this, in the Form Activate event I used ctl.SetFocus for each UserControl and then SetFocus on the correct starting control.
I had to temporarily enable the control that contains the UserControl to be able to SetFocus to the UserControl.
This was noticed specifically with the 3rd party Farpoint Spread control, but I have not investigated any other controls as yet.

Does anyone know a way to accomplish this without using ".SetFocus"?

----------


## Jimboat

[sorry, posted in wrong location, plse delete post]

----------

