Results 1 to 18 of 18

Thread: Getting DPI

  1. #1

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Getting DPI

    Hi All,

    Okay, the following code will return the registry value for pixels-per-inch (DPI) for Windows 8.0 and below. It may (or may not) return it for Windows 8.1 and above.

    Code:
    
    Option Explicit
    '
    Public Enum HKeyEnum
        HKEY_CLASSES_ROOT = &H80000000
        HKEY_CURRENT_USER = &H80000001
        HKEY_LOCAL_MACHINE = &H80000002
        HKEY_USERS = &H80000003
        HKEY_PERFORMANCE_DATA = &H80000004
        HKEY_CURRENT_CONFIG = &H80000005
        HKEY_DYN_DATA = &H80000006
    End Enum
    Private Const READ_CONTROL = &H20000
    Private Const SYNCHRONIZE = &H100000
    Private Const STANDARD_RIGHTS_READ = (READ_CONTROL)
    Private Const KEY_QUERY_VALUE = &H1
    Private Const KEY_ENUMERATE_SUB_KEYS = &H8
    Private Const KEY_NOTIFY = &H10
    Private Const KEY_READ = ((STANDARD_RIGHTS_READ Or KEY_QUERY_VALUE Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY) And (Not SYNCHRONIZE))
    Private Const ERROR_SUCCESS = 0&
    Private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, phkResult As Long) As Long
    Private Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, lpData As Any, lpcbData As Long) As Long         ' Note that if you declare the lpData parameter as String, you must pass it By Value.
    Private Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
    '
    
    Private Sub Form_Load()
        MsgBox DpiSetting
        Unload Me
    End Sub
    
    Public Function DpiSetting() As Long
        ' This uses the registry, so it circumvents any/all manifest settings.
        ' 96 is the default value for this.
        DpiSetting = GetRegistryDword(HKEY_CURRENT_USER, "Control Panel\Desktop", "LogPixels")
    End Function
    
    Public Function GetRegistryDword(ByVal hKey As HKeyEnum, ByVal Section As String, Optional ByVal Key As String = "*", Optional ByVal Default As Long = 0) As Long
        ' Section   Required. String expression containing the name of the section where the key setting is found.
        ' Key       Required. String expression containing the name of the key setting to return. "*" = default
        ' Default   Optional. Expression containing the value to return if no value is set in the key setting.
        Dim nRet As Long
        Dim hKey2 As Long
        Dim nType As Long
        Dim nBytes As Long
        Dim Buffer As Long
        '
        ' Assume failure and set return to Default.
        GetRegistryDword = Default
         '
        ' Open key
        nRet = RegOpenKeyEx(hKey, Section, 0&, KEY_READ, hKey2)
        If nRet = ERROR_SUCCESS Then
            ' Set appropriate value for default query.
            If Key = "*" Then Key = vbNullString
            ' Get data
            nRet = RegQueryValueEx(hKey2, Key, 0&, nType, Buffer, 4)
            If nRet = ERROR_SUCCESS Then GetRegistryDword = Buffer
            Call RegCloseKey(hKey2)
        End If
    End Function
    
    Here's a page that discusses much of this.


    Now this all gets quite confusing because, with monitors, an inch isn't really an inch. It all depends on the size of the monitor.

    However, we must "think" in terms of inches (or something related) because there are 1440 twips per inch (by definition), and we must very often think in twips.

    So, as the above linked webpage states, for Windows 8.0 and prior, there was a convention adopted that 96 DPI = 100%. Or, said a bit more conveniently for programmers, 15 twips per dot(pixel) = 100%.

    Here's what I want. I want a procedure (regardless of how my program is manifested) that tells me my DPI percentage (or possibly twips-per-dot) for each monitor on a Windows 8.1 or greater, multi-monitor system. I can probably work it out, and I may be working on it, but I just thought that someone might have it.

    At the present time, I'm not sure if I should explore around in the monitor EDID, or possibly study that link above more, possibly digging deeper into that "HKCU\Control Panel\Desktop" area.

    Any help will be appreciated.

    Regards,
    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  2. #2
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: Getting DPI

    I have never tried doing this without a manifest, but my first test would be GetDpiForMonitor:

    https://msdn.microsoft.com/en-us/lib.../dn302058.aspx
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  3. #3

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Getting DPI

    Doh, I love the name of the API. I'll play around with it. It certainly sounds promising.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  4. #4
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: Getting DPI

    A number of nice DPI-related functions were added in Windows 8.1, but they're a bit tricky to track down on MSDN. This link is probably the best "one-stop" reference:

    https://msdn.microsoft.com/en-us/lib...(v=vs.85).aspx

    Some, like AdjustWindowRectExForDpi(), are limited to Windows 10, but hopefully there aren't too many users still stuck on Windows 8/8.1...
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  5. #5

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Getting DPI

    Thanks Tanner. That looks like a nice resource. As I get VB6 procedures developed for making the calls, I'll be sure to post them. Also, I'm finding that I must use the following call (in code window) to make various decisions regarding how to use these things. I've got to keep my software compatible with at least Windows 7. I could probably make that a cut-off without too much consternation.

    All The Best,
    Elroy

    Code:
    
    Option Explicit
    '
    Private Declare Function GetModuleHandle Lib "kernel32.dll" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
    Private Declare Function LoadLibraryEx Lib "kernel32.dll" Alias "LoadLibraryExA" (ByVal lpFileName As String, ByVal hFile As Long, ByVal dwFlags As Long) As Long
    Private Declare Function GetProcAddress Lib "kernel32.dll" (ByVal hModule As Long, ByVal lpProcName As String) As Long
    Private Declare Function FreeLibrary Lib "kernel32.dll" (ByVal hLibModule As Long) As Boolean
    '
    
    Public Function DllFunctionExists(ByVal sModule As String, ByVal sFunction As String) As Boolean
        ' DllExists <--- just so we can find it.
        ' This can test for the existence of any DLL's function, no API declaration required.
        Dim hHandle As Long
        '
        hHandle = GetModuleHandle(sModule)
        If hHandle = 0& Then
            hHandle = LoadLibraryEx(sModule, 0&, 0&)
            If hHandle <> 0& Then
                If GetProcAddress(hHandle, sFunction) <> 0& Then DllFunctionExists = True
            End If
        Else
            If GetProcAddress(hHandle, sFunction) <> 0& Then DllFunctionExists = True
        End If
        FreeLibrary hHandle
    End Function
    
    
    
    EDIT1: I suppose I could check the Windows version, but the above seems more bullet-proof, and also easier (no need to keep track of which version has what).
    Last edited by Elroy; Feb 21st, 2017 at 11:44 AM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  6. #6
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: Getting DPI

    Yep, I like that approach. Ever since the move to "rolling" Windows 10 releases -- and the proliferation of technical previews, insider builds, etc -- version-checking has been problematic. Like you, I think probing for specific function handles is less error-prone in the long run.

    Stuff like this is why I keep advocating for VB6 developers to get comfortable using pixels to address high-DPI issues. WAPI provides a ton of helpful functions for dealing with high-DPI situations, and window move/size APIs like SetWindowPos provide much more control than VB's internal .Move functions. When you factor in twips being broken at 200+% DPIs (which is no longer an esoteric possibility -- that's the *default* setting on new Surface Pros), going all-in on an API-based, pixel-centric solution is simply easier.* The gap between twip- and pixel-based approaches is only going to widen as Microsoft keeps adding new screen settings to Windows 10.

    /end rant

    Best of luck with your ongoing project.

    (*Windowless controls are a tricky exception, obviously. They're going to be a headache no matter what.)
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  7. #7
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: Getting DPI

    Do you need that "DllFunctionExists" at all?

    If you are merely using Declare calls you can just wrap one of your calls in exception handling code. An "Error in loading DLL (Error 48)" occurs if the DLL doesn't exist, a "Specified DLL function not found (Error 453)" if the entrypoint doesn't exist.

    So it is easy enough to just trap for these or any exceptions and set some DownLevelOS flag = True and use that subsequently to choose fallback behavior. The VB6 runtime is already doing what your function duplicates.

    Similar exceptions occur if you are using typelib references to bind calls instead of using Declare-wrapped calls.

  8. #8

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Getting DPI

    Hi Dilettante,

    Well, a couple of reasons I'll probably keep it:

    1) I've just always found error trapping a bit sloppy. I certainly do enough of it. But, if there's a clean way around it, I tend to go in favor of not using it.

    2) I haven't really looked closely at it, but it seems to take 2 or 3 seconds to generate an error when the DLL doesn't exist. And the above "DllFunctionExists" function seems to execute quite rapidly. For instance, the following line executes virtually instantaneously:
    Code:
    
    debug.Print DllFunctionExists("shcore.dll", "GetDpiForMonitor")
    
    However, if you just declare the API and then call it, waiting on an error on Windows 7, there's a pause. Again, I'm not clear on what's happening here, but that's my experience.


    I will admit that I've written myself a comment in that "DllFunctionExists" function to possibly set a static flag in the calling procedure if it's going to be called a great deal. Once the answer is known, there's certainly no need to repeatedly call it.

    Best Regards,
    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  9. #9
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: Getting DPI

    I'm not sure where you are getting that "pause" from but I've never seen it. I just tried it again and there is certainly no delay on the order of seconds, if I try to use a DLL that doesn't exist it gets an exception almost immediately. This makes sense because the runtime does almost the same thing you have duplicated.

  10. #10

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Getting DPI

    Dilettante, I tend to agree. You would think that GetModuleHandle and/or LoadLibraryEx would search around in the environment path just like declaring an API and then calling it would do. I'm telling ya though, I sometimes get a pause before the error is generated when I call a non-existent DLL that's been declared with an API declaration. I just tested and it doesn't always seem to do it, so maybe I'm just "catching the pause" when calling the API, and not "catching it" when testing the DllFunctionExists function.

    Either way, I think the DllFunctionExists function is a bit cleaner than error trapping (and a bit more explicit as well). Bottom line it's really six-of-one, and half-a-dozen of the other.

    Regards,
    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  11. #11
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,651

    Re: Getting DPI

    I was just looking into DPI for my TaskDialog project, and according to this MSDN article, to establish the scale for pixels, you can use GetDeviceCaps, which is available on at least xp through 10:
    Code:
    void InitializeDPIScale(HWND hwnd)
    {
        HDC hdc = GetDC(hwnd);
        g_DPIScaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0f;
        g_DPIScaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0f;
        ReleaseDC(hwnd, hdc);
    }
    This should work for establishing x,y and cx,cy for a control, or no? DPI adjustments are new to me.

  12. #12

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Getting DPI

    Hi Fafalone,

    Yes, I've had precisely those GetDeviceCaps LOGPIXELSX/LOGPIXELSY routines in my general purpose library forever. However, it's my understanding that they can return different results under different conditions, possibly depending on whether or not the application has been manifested as DPI aware. I'm not entirely clear on this, and am in the process of coming up to speed on it now.

    In my attempt to "come up to speed", I'd like to start by just developing a set of functions, that gives me the DPI settings for the system just as they'd appear in Control Panel. And then, I'll work from there. It's just a bit of an obsession I have (to be able to get things just as they appear in "settings").

    I know that this DPI thing is a freight train coming at me. 4k monitors are virtually here, and tablets running Windows are coming out with resolutions of 2736x1824 or higher. It's inevitable, and, if we want our applications to continue running on these screens, we'd better learn to embrace them.

    Also, just as an FYI, probably half or more of the people using my application are doing so on multi-monitor workstations. One client has several workstations with one monitor in landscape and the other in portrait. They primarily use the portrait monitors for typing reports, but my software is often running on these machines. So far, they're all running at 100% DPI, but I know it won't stay that way forever, and I'm trying to be pro-active.

    Regards,
    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  13. #13
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: Getting DPI

    The problem with GetDeviceCaps and LOGPIXELSX/Y is this, per MSDN:

    Number of pixels per logical inch along the screen width/height. In a system with multiple display monitors, this value is the same for all monitors.
    So even if you manifest your application as per-monitor DPI aware, you can't use GetDeviceCaps on any given monitor - just the primary one.

    The tough thing for VB developers is that a lot of DPI settings are tied to the way your application is manifested. If you're trying to develop standalone classes or user controls, this gets messy as manifests are obviously out of your hands. (If you control the full app, though, there's no reason *not* to manifest, especially if you want correct high-DPI behavior.)
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  14. #14

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Getting DPI

    @Tanner: Just as an FYI, other than a few of the typical OCX ActiveX files (e.g., RichTx32.ocx, etc.) , I'm in complete control of my application (including all source code). As part of it, there are several ActiveX.DLL files, but I've got control of those as well (including the source code). And everything is SxS, and the ActiveX.DLL files are loaded without being registered. In fact, nothing I do touches the registry except for some settings I specifically set (wanting to save them on a per-machine-per-user basis), and that has nothing to do with "registering" anything.

    If I can figure this DPI stuff out, I do believe I'll be able to appropriately address being DPI aware. I know that I'll have to give some special attention to about a dozen user-designed-controls I've got, but even those are just compiled with the main project (and aren't stand-alone OCX files).

    There are a couple of areas I make some use of the GDI and GDI+, but they're not terribly involved. The main thing that's used for is scaling and rotation of images that are in a PictureBox. I might switch over to Direct2D for that stuff. Here's my working list of potential problem areas:

    * Get rid of MS Sans Serif font.
    * Check for auto-size labels.
    * Make sure nothing has ScaleMode set to Pixels.
    * Check Image controls for Stretch.
    * Check PictureBoxes for Autosize.
    * Check all my User-Controls.
    * Check my use of GDI and GDI+.
    * Check my "canned" images (which are primarily on "help" forms).

    This is not a trivial change, given the size of my application. But, as with everything else, one-step-at-a-time. And my first step is "knowing" what the DPI settings are for whatever monitor one of my forms is sitting on. (At least, that's the first step in my mind.)

    All The Best,
    Elroy

    p.s. I've read all the pertinent DPI aware tutorials for VB6.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  15. #15
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: Getting DPI

    Quote Originally Posted by Elroy View Post
    * Make sure nothing has ScaleMode set to Pixels.
    The only place such a change might be helpful is Forms that contain a Line control (and the Line control's position is not manually calculated at run-time).

    If such a form is saved with ScaleMode = Twips, the Line control's position will be auto-scaled when the Form is loaded. (With the caveat that it will be "wrongly" scaled at 200+% DPI, just like all other VB auto-scaling.)

    For ScaleMode = Pixels, all controls (except Line controls) are still auto-scaled, so you don't gain anything by switching ScaleMode.

    You also shouldn't run into any trouble with GDI or GDI+ calls if the application is manifested as DPI-aware. If it isn't, I'm not sure what will happen, especially with GDI+ (which does have some DPI-contingent behavior "built-in", and which runs its own background thread, making manifest-avoidance APIs like SetProcessDPIAware problematic). But in my experience, you definitely don't need to use Direct2D to solve any DPI-related issues.
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  16. #16

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: Getting DPI

    Hi Tanner,

    Yeah, something like this form?

    Name:  Image1.jpg
Views: 676
Size:  93.1 KB

    However, I appreciate your comment. I've made a note that this only (possibly) matters for those light-weight line/drawing controls. I've clearly got lots of testing to do, but the more focused I can get on this, the better.

    Regards,
    Elroy

    EDIT1: Also, I'll take your comments to heart regarding the GDI and GDI+. I wasn't looking forward to changing that all around to Direct2D. Also, as always, I'll be sure to do lots of testing.
    Last edited by Elroy; Feb 22nd, 2017 at 11:52 AM.
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  17. #17
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    806

    Re: Getting DPI

    If every line in that screenshot is a Line control, you have my sincere condolences.

    Honestly, before migrating a form like that to a high-DPI setup, I'd first kill off all line/shape controls and instead render them directly onto the form (using basic GDI or GDI+ calls). That would make the form much lighter on resources, while also making it trivial to implement nice UI features like, "highlight the lines around a cell when the mouse moves over it". It would also be much easier to add "user resize" support to the form.

    While you were at it, you could even go a step further and render the labels manually using basic GDI text calls. That makes it trivial to modify things like fonts and colors at run-time. Just think of the fun you could have!

    I'm chatty on this subject because I fear that LaVolpe's high-DPI tutorial - while very comprehensive - risks scaring people away from tackling high-DPI support. The complexity involved varies greatly by project, and for many VB projects, I don't think the migration is as scary as developers might think, given the size and complexity of LaVolpe's tutorial. (For basic high-DPI support, anyway; per-monitor is a whole other monster.)

    LaVolpe's tutorial also pre-dates Windows 10, and with new DPI-related features and APIs appearing in each new Windows 10 release, his advice for DPI handling may need to be revisited. (For example, that AdjustWindowRectExForDpi() API I mentioned earlier provides a huge shortcut for resizing a form to some specific client size, independent of DPI, but I don't think it existed when LaVolpe wrote his tutorial...)
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  18. #18
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: Getting DPI

    In the newest Windows 10 we got a number of additional DPI behavior changes, API calls, and some improvements to DPI Virtualization. Since you can't really lag back on old "Windows 10s" anymore (all new Windows versions are called "Windows 10" now to fool the gullible, and moving forward is free and mandatory) this solves a lot of the problem.

    Just stop trying to support Windows 7 at all, give up on Windows 8.x.y.z which are even more deprecated than Windows 7. Windows Vista falls off extended support in under two months, XP is long dead.

    Display Scaling changes for the Windows 10 Anniversary Update

    High DPI Scaling Improvements for Desktop Applications and “Mixed Mode” DPI Scaling in the Windows 10 Anniversary Update

    Another large mutation of Windows 10 is scheduled soon, and might address a few issues with Virtualization.

    Many more programs can get away with staying blissfully DPI-ignorant now.

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