Results 1 to 18 of 18

Thread: [VB6] Tutorial: Being DPI Aware

  1. #1

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    [VB6] Tutorial: Being DPI Aware

    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
    Last edited by LaVolpe; Jul 18th, 2017 at 02:36 PM. Reason: Fixed MSDN dead URL

  2. #2

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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.

    Quote Originally Posted by Microsoft (MSDN)
    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

    Name:  DPI96.jpg
Views: 13161
Size:  24.1 KB
    Name:  DPI96virtualized.jpg
Views: 12982
Size:  36.5 KB
    Name:  DPI175broken.jpg
Views: 12678
Size:  36.2 KB

    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.
    Last edited by LaVolpe; Dec 22nd, 2015 at 09:03 PM.

  3. #3

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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
    Code:
        <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: dpiAwareness. 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.
    Code:
        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 control, 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.
    Code:
    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
    Code:
    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:
    Name:  dpi96.png
Views: 11558
Size:  103.9 KB
    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

    Name:  DPI175broken.jpg
Views: 12838
Size:  36.2 KB
    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

    Name:  DPI175.jpg
Views: 12716
Size:  40.1 KB
    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
    Last edited by LaVolpe; Jul 29th, 2017 at 01:11 PM.

  4. #4

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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
    Code:
    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:
    Code:
    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:
    Code:
    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.
    Code:
    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:
    Code:
    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.
    Code:
    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.
    Last edited by LaVolpe; Apr 30th, 2017 at 02:36 PM.

  5. #5

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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
    Code:
    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
    Code:
    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:
    Code:
    hIcon = LoadImage(0, filename, IMAGE_ICON, _
        32& * (1440 / Screen.TwipsPerPixelX) / 96, 32& * (1440 / Screen.TwipsPerPixelY) / 96, _
        LR_LOADFROMFILE)
    Last edited by LaVolpe; Jul 29th, 2017 at 04:37 PM.

  6. #6

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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:
    Code:
    ' 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.
    Last edited by LaVolpe; Jul 29th, 2017 at 02:14 PM.

  7. #7
    PowerPoster Arnoutdv's Avatar
    Join Date
    Oct 2013
    Posts
    5,854

    Re: [VB6] Tutorial: Being DPI Aware

    Question about the following
    2. Do not use UserControl.Width,Height to retrieve dimensions, instead:

    Code:
    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?

  8. #8

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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).
    Code:
    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:
    Code:
    If ScreenDPI(True) <> ScreenDPI(False) Then ...
    or the one in post #4
    Code:
    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%
    Last edited by LaVolpe; May 3rd, 2017 at 01:21 PM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  9. #9

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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 example
    Replace 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.
    Last edited by LaVolpe; May 6th, 2017 at 08:33 AM. Reason: changed formula to divide then multiply to prevent chance of overflow
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  10. #10

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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:
    Code:
        <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.
    Attached Files Attached Files
    Last edited by LaVolpe; Jul 19th, 2017 at 09:14 AM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  11. #11
    Addicted Member
    Join Date
    May 2011
    Posts
    230

    Re: [VB6] Tutorial: Being DPI Aware

    Hi Lavolpe!

    I'm interested in joining the 'Force' (you 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!

  12. #12

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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.
    Attached Files Attached Files
    Last edited by LaVolpe; Sep 1st, 2017 at 06:56 PM. Reason: typo
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  13. #13
    Addicted Member
    Join Date
    May 2011
    Posts
    230

    Re: [VB6] Tutorial: Being DPI Aware

    Quote Originally Posted by LaVolpe View Post
    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

  14. #14
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,094

    Re: [VB6] Tutorial: Being DPI Aware

    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>

  15. #15

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6] Tutorial: Being DPI Aware

    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?
    Last edited by LaVolpe; Apr 6th, 2018 at 12:25 PM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  16. #16
    Addicted Member
    Join Date
    May 2011
    Posts
    230

    Re: [VB6] Tutorial: Being DPI Aware

    Quote Originally Posted by VbNetMatrix View Post
    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...

  17. #17
    New Member
    Join Date
    Apr 2018
    Posts
    1

    Re: [VB6] Tutorial: Being DPI Aware - Additional control handling

    I have used this information to successfully handle DPI ratios other than 100%.
    Here are a few points:
    1. 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:

      Code:
      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.

      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:
      Code:
      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
    2. 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"?
    Last edited by Seradex Dev; Apr 18th, 2018 at 10:59 PM. Reason: Be careful to not apply DPISizeAdjustRatio multiple times

  18. #18
    Addicted Member
    Join Date
    Feb 2004
    Posts
    145

    Re: [VB6] Tutorial: Being DPI Aware

    [sorry, posted in wrong location, plse delete post]
    Last edited by Jimboat; Sep 18th, 2020 at 06:18 AM.
    /Jimboat

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width