Included project offered as a starting point to fully support per-monitor DPI awareness within VB. The "framework" consists of 3 required classes and 2 optional ones that would be added to any project that would support per-monitor DPI awareness. Documentation is provided within each enclosed class. Additional documentation provided in the zipped "Documentation" folder.
Per-monitor DPI awareness is a completely new mindset for VBers. We are so used to VB handling nearly all the scaling of the GUI for us. We didn't have to do too much at all because many of us opted for DPI virtualization (no awareness) or system-DPI awareness via a manifest. In both of those cases, little to no effort was needed. However, with per-monitor DPI awareness, the form and nearly everything related to it must be scaled manually. The enclosed framework will help handle scaling of the form, its VB intrinsic controls, common controls, and images. The framework also handles any necessary hooking and/or subclassing and notifying you of DPI changes. When you are notified, the framework sends various events to walk you through the needed actions and optionally automates the necessary grunt work as much as possible. In the documentation, the term used is: scaling cycle.
Anyone that is creating apps (or usercontrols) to be distributed should seriously consider per-monitor DPI awareness. If creating stuff for your own use, then per-monitor DPI awareness may not be worth it.
Usercontrols: The framework is not designed for use inside your usercontrols -- not yet. The framework is designed for form-level and cannot easily be tweaked to support custom usercontrols. And in reality, you probably don't want to make your control per-monitor DPI aware. Why? Even if the host of your control is not DPI-aware, it is possible Windows will send your control DPI-change messages. You would then have to determine if the host was DPI aware and that is possible. However, current strategy says that the host scales its content, including controls. Instead, maybe your control should have a public method for the user to set the current DPI the control should render to. If not that option, you should expose all scalable, internal properties, like fonts and offsets so the user can scale them on demand when DPI changes.
There is no way this framework could possibly have code to help scale every compiled ocx used by the VB community. At a minimum, the enclosed classes can be used as a template for future expansion. Maybe, you and others can design scalers for other ocxs and post them here? I do not claim these classes will handle every possible scenario for every project. There will be cases where the code isn't doing what you expect -- that's what feedback is for.
After downloading, please read the 3 "ReadMe" files before jumping into anything. They will give you a really good idea of what you will be getting into.
Since per-monitor DPI awareness isn't widely addressed for VB, the hope is that this project's framework can be used as a major step forward or is a catalyst for new ideas.
There are two projects included below:
1. Framework.zip includes a test project, the classes and documentation. The test project has just a few controls, including a couple common controls. To test it in various DPIs, either compile the project or make your IDE DPI-aware. In the documentation folder, there is information on how to make your IDE DPI-aware without actually modifying your VB6.exe directly. The documentation also includes several sample manifests. Also included is a screenshot of a form loaded @ 96 DPI and then DPI changed to 175% larger, both with/without DPI-awareness. Since the image is scaled by this site, the difference in quality can't be appreciated without viewing full-sized. That image can be found in the documentation folder.
2. ProjectTemplate.zip includes files you can unzip into your VB's ...\Template\Projects folder. If done, there will be a new project type available to you when you create a new VB project. That new type is titled: DPI Aware EXE Wizard. If selected, it will create a new project with the framework already added. The only thing it won't create is a resource file, having a manifest. That will need to be done manually for each new project.
Last edited by LaVolpe; Sep 9th, 2020 at 02:28 PM.
Reason: justl text updates
Insomnia is just a byproduct of, "It can't be done"
- clsDpiAsstCmnCtrls class: Offering an on-demand replacement/updating of existing imagelists. For example, should windows color scheme change and imagelists pictures are rendered over a system color, that color may need to change. Currently, imagelists are offered for updating only during scaling cycles and no method exists to update them outside of that cycle -- except manually by the user. Changes will be incompatible with current class version
- clsDpiPmAssist: Offering a raised event should windows color scheme change.
Tips & Tricks
- Adding manifests to resource files. Service pack 6 must be installed, else your manifest file size must be evenly divisible by 4. Manifest applies to compiled apps. If your compiled app errors and aborts as you launch it -- could be your manifest is not correct.
Code:
1. Within the VB resource editor, prompt for a new custom item
2. Navigate to and select your manifest file
3. Double click on the newly added "Custom" resource
4. In the TYPE textbox, change "Custom" to "#24", including quotes
5. In the ID textbox, change the number to 1, no quotes
6. Close the editing window, save changes and close the resource editor
Optionally, you can use other resource editing tools like this one, on this site.
www.vbforums.com/showthread.php?845909-VB6-Manifest-Creator-II
- Avoid large lists in TreeViews & ListViews when using ImageLists in those controls. Why? When scaling, every item in those controls needs to be scanned for imagelist-related properties & those properties cached. Listview (v6) can have a ReportIcon property which results in scanning every subitem whether property is used or not. Replacing an existing ImageList with another one results in losing all those property settings; hence caching. After ImageList is replaced, then all those cached properties need to be reapplied. There will be a speed/efficiency hit for large lists.
Major Issues - Requires workarounds/rethinking
- Listview (v6) will require subclassing its parent if you set any list item's Bold property. Once any item's Bold property set to True, a bold font is created and never changes. Bolded items font no longer scale. Toggling all bolding to off & back to on has no effect. Changing the font for the listview has no effect. Don't use the Bold property, instead subclass and take ownership of drawing bolded items. The Treeview (v6) also has a bold property for individual nodes - no issues.
- Listview (v6) & Treeview (v6) both have checkbox options. The checkbox never scales. When DPI increases from system DPI, checkbox is undersized. When DPI decreases from system DPI, the checkbox is oversized.
- Many typical controls you add to your form can have some scaling issues. In the documentation, a list of known issues, per control, is provided. Most issues can be compensated for, but not all. For example, the VB listbox with its checkbox style will not scale the checkbox.
Other controls
RichText Box (rtb). Within the IDpiPmAssistant_ScaleControlOCX implemented event, following can be used to scale its content. Assumes: Dim cDpiAssist As clsDpiPmAssist. Key notes:
- Don't intermix zooming & font scaling; one or the other
- If setting rtb.TextRTF (or loading via its LoadFile method), then zoom afterwards, previous zoom setting is lost
Code:
Const WM_USER = &H400
Const EM_SETZOOM As Long = (WM_USER + 225)
If Action = dpiAsst_EndEvent Then
If TypeName(theControl) = "RichTextBox" Then
SendMessage rtb.hWnd, EM_SETZOOM, cDpiAssist.DpiForForm, ByVal cDpiAssist.DpiForSystem
End If
End If
Your Custom UserControl
Is it per-monitor compatible? Actually parts of it probably are. Let's say you have a listbox in your control. If the user manifested their project as per-monitor aware, then when DPI changes that listbox's vertical scrollbar's width will change whether you want it to or not (as of Win10.v1703). Keep that in mind. For simple usercontrols that don't have a lot of stuff visually, making them compatible should require very little effort.
Last edited by LaVolpe; Sep 11th, 2020 at 02:25 PM.
Insomnia is just a byproduct of, "It can't be done"
I tested it 5 min, and works, but need time to do more test, integreated in one of our applicatation.
And for that, time is the botleneck for the moment
an impressive job as always, it still takes me time to understand it, maybe over the years I understand the reason for many things, but well let me ask my first question of igorante. (I should first read the documentation)
lSize =20 * GetWindowsDPI
Public Function GetWindowsDPI() As Double
Dim hdc As Long, LPX As Double
hdc = GetDC(0)
LPX = CDbl(GetDeviceCaps(hdc, LOGPIXELSX))
ReleaseDC 0, hdc
If (LPX = 0) Then
GetWindowsDPI = 1#
Else
GetWindowsDPI = LPX / 96#
End If
End Function
As you will see the second way is how I do it and it works well, but what am I ignoring?
As you will see the second way is how I do it and it works well, but what am I ignoring?
Your solution returns the System DPI, not the current DPI for a project that is per-monitor aware (if those DPIs are different). System DPI doesn't change, for the project, after that project launches. Current DPI can change at any time. Your solution is fine for projects that are system-DPI aware, not for those that are per-monitor DPI aware.
If you add your code & MsgBox GetWindowsDPI*96 to a button in my sample project, compile the project, then launch it, you will see what I mean. After first launching it, click the button & note the value. Now change the DPI while the project is running and click the button again. You'll get the same value (System DPI) even though the DPI changed. If you test it that way, also add this afterwards in the button click. Note that VB's MsgBox is a bit broken in per-monitor awareness & there is a substitute in the class.
I have done an experimental trail to make Krool's MonthView DpiAware. Is the implementation on a proper way?
By the way, how to apply theme for MS MonthView? I did see the Monthview response to DpiAware in your demo (add a Monthview in your Framework demo), but doesn't have theme.
I have done an experimental trail to make Krool's MonthView DpiAware. Is the implementation on a proper way?
looks like you are on the right path, ensure you change all properties that may scale & that includes whatever font the control may be using. Of course this is impossible unless you a) have source code or b) the control exposes the font(s) and needed properties (for example possibly some offsets).
By the way, how to apply theme for MS MonthView? I did see the Monthview response to DpiAware in your demo (add a Monthview in your Framework demo), but doesn't have theme.
That demo uses the VB ocxs. If the monthview isn't themed then it can't be themed; it is likely owner-drawn. Krool's monthview probably can be themed because he is using the actual O/S monthview, not some owner-drawn one.
follow-up: I added a VB common controls monthview to a manifested IDE and unmanifested IDE. They draw exactly the same.
Last edited by LaVolpe; Sep 9th, 2020 at 11:11 AM.
Insomnia is just a byproduct of, "It can't be done"
I did not mean that. The form (non-client) is automatically scaled by the OS. If the DPI changed the size and font and graphics needs to draw "bigger" or "smaller" as per current monitor. And that's not the case..
It would be better you do not use PerMonitorV2 and just use system DPI awareness. That's already a small challenge to properly handle that.
If you then move to another screen with DPI change let the OS bitmap stretch your Form. So it looks correctly sized though a bit blurry.. but better than nothing.
looks like you are on the right path, ensure you change all properties that may scale & that includes whatever font the control may be using. Of course this is impossible unless you a) have source code or b) the control exposes the font(s) and needed properties (for example possibly some offsets).
I have tried your framework by adding some controls of Krool's VBCCR17.OCX, VBCCR17 failed to do auto scale. Can you write a new utility to support VBCCR.OCX? Is it possible without changing source code of VBCCR to make them DpiAwareness to PerMonitorV2?
I have tried your framework by adding some controls of Krool's VBCCR17.OCX, VBCCR17 failed to do auto scale.
Looking at that screenshot on the right half, are all those Krool's controls? Reason I ask is that I find it hard to believe that the VB-intrinsic-like controls didn't scale if they were passed through the scaling cycle. As for the common-control replacements? All bets off on those for now. You may not have used the framework correctly & I'd like to verify that first...
If you were using Krool's uncompiled controls, can you zip & provide the exact sample project you were using. In your project, don't zip files from Krool's stuff (lots of them), I can supply those. But include all other project files.
If you were using the compiled ocx then don't zip that up since forum rules prohibit that, but include everything else in the zip.
Can you write a new utility to support VBCCR.OCX?
First, let's figure out if the framework was used correctly. If so, then could I? Yes but probably won't -- read on.
Is it possible without changing source code of VBCCR to make them DpiAwareness to PerMonitorV2?
Would it cause code change? Depends. If a control exposes properties that should be scaled then the answer is no. If it doesn't and they should be scaled, then yes, code changes will be needed to either do the scaling or expose the properties.
What I can do, not right away, is maybe give you a list of what code should be tweaked (and why) and you can give that list to Krool. I don't want to be the middleman. As for a scaler class for his controls, trust me, I thought about it. However, he should have the opportunity to tweak his own code first. And I didn't want to spend time creating scaling solutions that would need to be changed after he updates his code for that purpose.
Insomnia is just a byproduct of, "It can't be done"
Looking at that screenshot on the right half, are all those Krool's controls? Reason I ask is that I find it hard to believe that the VB-intrinsic-like controls didn't scale if they were passed through the scaling cycle. As for the common-control replacements? All bets off on those for now. You may not have used the framework correctly & I'd like to verify that first...
If you were using Krool's uncompiled controls, can you zip & provide the exact sample project you were using. In your project, don't zip files from Krool's stuff (lots of them), I can supply those. But include all other project files.
If you were using the compiled ocx then don't zip that up since forum rules prohibit that, but include everything else in the zip.
First, let's figure out if the framework was used correctly. If so, then could I? Yes but probably won't -- read on.
Yes, On Right side in my picture, they are Krool's VBCCR17.OCX. You can download here.
I just put codes inside your two functions:
Code:
Private Sub pvBuildImageList(ILst As ImageList, IconSize As Long)
Dim cPics As clsDpiImaging, hAttrs As Long
Set cPics = New clsDpiImaging
' add unselected images with grayscale
hAttrs = cPics.CreateGDIpAttributesHandle(gsCCIR709)
ILst.ListImages.Add , , cPics.LoadResPictureEx(3201, vbResIcon, IconSize, IconSize, , , msUnmanagedBitmap, vbWindowBackground, , hAttrs)
' add selected images, no grayscale
ILst.ListImages.Add , , cPics.LoadResPictureEx(3202, vbResIcon, IconSize, IconSize, , , msUnmanagedBitmap, vbWindowBackground)
' add root image
ILst.ListImages.Add , , cPics.LoadResPictureEx(101, "CUSTOM", IconSize, IconSize, , , msUnmanagedBitmap, vbWindowBackground)
cPics.DeleteGDIpAttributesHandle hAttrs
End Sub
Private Sub pvInitDisplay()
Label1.Caption = "Set ScaleMode to Pixels && load at non-100% system DPI." & vbCrLf & _
"Then watch the line at top fail to scale properly"
Combo1.ListIndex = 1
Dim sPath As String, sFolder As String
pvBuildImageList ILlistview, 16
Set TreeView1.ImageList = ILlistview
sPath = "C:\"
TreeView1.Nodes.Add , , "root", sPath, 3
sFolder = Dir$(sPath & "*.*", vbDirectory Or vbReadOnly Or vbSystem)
Do Until LenB(sFolder) = 0
If (GetAttr(sPath & sFolder) And vbDirectory) = vbDirectory Then
TreeView1.Nodes.Add "root", tvwChild, , sFolder, 1, 2
End If
sFolder = Dir$()
Loop
Dir$ "": TreeView1.Nodes(1).Expanded = True
pvScaleImages
LabelW1.Caption = "Set ScaleMode to Pixels && load at non-100% system DPI." & vbCrLf & _
"Then watch the line at top fail to scale properly"
ComboBoxW1.AddItem "Item 1"
ComboBoxW1.AddItem "Item 2"
ComboBoxW1.AddItem "Item 2"
ComboBoxW1.ListIndex = 1
ListBoxW1.AddItem "Item A"
ListBoxW1.AddItem "Item B"
ListBoxW1.AddItem "Item C"
ListBoxW1.ListIndex = 1
Dim sPath1 As String, sFolder1 As String
pvBuildImageList ILlistview, 16
Set TreeViewW1.ImageList = ILlistview
sPath1 = "C:\"
TreeViewW1.Nodes.Add , , "root", sPath, 3
sFolder1 = Dir$(sPath1 & "*.*", vbDirectory Or vbReadOnly Or vbSystem)
Do Until LenB(sFolder1) = 0
If (GetAttr(sPath1 & sFolder1) And vbDirectory) = vbDirectory Then
TreeViewW1.Nodes.Add "root", tvwChild, , sFolder1, 1, 2
End If
sFolder1 = Dir$()
Loop
Dir$ "": TreeViewW1.Nodes(1).Expanded = True
pvScaleImages
End Sub
Private Sub pvScaleImages()
' example of possible routine to scale image properties for various controls
Dim cPics As clsDpiImaging, lSize As Long
Set cPics = New clsDpiImaging
' 20 = desired size @ 96 DPI
lSize = cDpiAssist.ScaleValueToDPI(20, 96, cDpiAssist.DpiForForm)
' button's picture property is ideal place to cache instance and is
' destroyed automatically when project closes or image replaced later
Set Command1.Picture = cPics.LoadResPictureEx(103, "CUSTOM", lSize, lSize, , , msUnmanagedBitmap, , acWantFormat32bppPARGB)
SendMessageW Command1.hWnd, BM_SETIMAGE, 0, Command1.Picture.Handle
Set CommandButtonW1.Picture = cPics.LoadResPictureEx(103, "CUSTOM", lSize, lSize, , , msUnmanagedBitmap, , acWantFormat32bppPARGB)
SendMessageW CommandButtonW1.hWnd, BM_SETIMAGE, 0, CommandButtonW1.Picture.Handle
End Sub
Those were the only changes you made? You did not make any other changes, anywhere? If not, fine. If so, zip it up because we definitely need to be on the same page, exactly. Otherwise, just spinning our wheels, wasting our time.
Last two questions, not that it should matter. Did you compile the project or run it in a manifested IDE? And finally, what DPI did you start with and what was it when you captured the screenshot.
I'm not on a VB machine and won't be until tomorrow. That's the earliest I'd be able to play and get a feel. I will take this off-line and reply via PMs from that point on regarding this topic. Otherwise, this thread can become heavy with posts regarding Krool's controls and I wouldn't want that. I want the thread to focus on general problems/solutions as much as possible vs. focusing on this one topic.
Insomnia is just a byproduct of, "It can't be done"
Those were the only changes you made? You did not make any other changes, anywhere? If not, fine. If so, zip it up because we definitely need to be on the same page, exactly. Otherwise, just spinning our wheels, wasting our time.
Last two questions, not that it should matter. Did you compile the project or run it in a manifested IDE? And finally, what DPI did you start with and what was it when you captured the screenshot.
My VB IDE is not manifested. My laptop is 100% scale (96 Dpi), My second Monitor is 175% Scale (168 Dpi). The Screenshot was taken on my 2nd Monitor running compiled exe.
My VB IDE is not manifested. My laptop is 100% scale (96 Dpi), My second Monitor is 175% Scale (168 Dpi). The Screenshot was taken on my 2nd Monitor running compiled exe.
Ok, cool. Initial guesses just from the screenshot... The non-Krool controls appeared to scale properly. Krool's controls appeared to have scaled size but not fonts. The difference in expected control size is related to incorrect control font size. The treeview icons will need special scaling since his controls use API imageliststs which are not addressed in the framework. My gut feeling is that with the exception of imagelists and possibly some controls, most of his controls can scale properly without any code changes or just minor changes. The ones that may need significant change may just be a few.
I'll PM you tomorrow and give you a quick heads up of what I initially found and what my next planned steps will be.
Insomnia is just a byproduct of, "It can't be done"
In your Framework demo, you choose Font.Name = Calibri. Is it purposely chosen? I tested, found only few Fonts got less blur or less bold (or thin) on high Dpi, e.g. Calibri, Courier New, Segoe, Time New Roman. What is the behind story?
Last edited by DaveDavis; Sep 10th, 2020 at 02:02 AM.
Nothing really. Use any TrueType or OpenType font for better scaling. VB's default of MS Sans Serif is one of the worse choices unfortunately. In the next update; that will probably be changed to Segoe since it's the default font for Microsoft since Vista. But I like Calibri better.
Edited: PM'd you earlier with a patch to get many of Krool's controls scaling. More work needs to be done. Let's keep questions on his controls in PMs.
Insomnia is just a byproduct of, "It can't be done"
On .NET, PerMonitor doesn't scale Font and Size, just keep Font clean w/o blur (Correct me if I am wrong). Your framework seems to scale all Fonts and some controls' Sizes by linear on PerMonitor (such as ListBox and ComboBox, Label,Status bar are being scale, Option and Check Size is OK in FrameWork Demo).
Last edited by DaveDavis; Sep 10th, 2020 at 08:14 PM.
Within the framework, fonts are scaled by the IFont interface implemented by VB's stdFonts. The framework actually does not scale a single font, it tells the IFont interface what scale ratio to use.
The framework will scale fonts and control sizes for all VB intrinsic controls unless told not to. By default, ocx sizes will be scaled but no ocx font properties are automatically scaled. Any ocx font properties to be scaled, must be specifically named when the scaling cycle events are called.
VB common controls ocxs are an exception because I provided an optional class that can scale those automatically for all known properties.
I know the framework is complex, but to better understand what is and what is not being scaled by default, please spend more time with the read-me files and the detailed code comments in the IDpiPmAssistant.cls
Last edited by LaVolpe; Sep 10th, 2020 at 08:47 PM.
Insomnia is just a byproduct of, "It can't be done"
Great job, LaVolpe. FRAMEWORK example looks very good with high dpi values; i tried the project compiled with all microsoft operating systems (in a virtual machine) from windows 2000 to windows 10 and it works great; only with windows xp it gives an error message without working: "The specified application could not be started. The application configuration is incorrect. A new installation of the application may solve the problem.". Is it possible to understand why?
Great job, LaVolpe... only with windows xp it gives an error message without working: "The specified application could not be started. The application configuration is incorrect. A new installation of the application may solve the problem.". Is it possible to understand why?
I had to play in my VM to find the problem (which I experienced too). It wasn't easy to track down. The problem it seems is that there was another compiled project that was using a manifest with the same "Identity" element. I removed the manifest from the res file and for simplicity sake, added a new external manifest, named it Project1.exe.manifest. I then went in and changed the identity element to ensure it was unique. Ran without a problem. You can get a generic manifest from my _ReadMe_Overview_DpiPM.rtf in the documentation folder of the project download (post #1). Jump to bottom of that document.
Last edited by LaVolpe; Sep 12th, 2020 at 11:09 PM.
Insomnia is just a byproduct of, "It can't be done"
Works very well!
Another question: I didn't understand, in every form of a project is it necessary to insert "Set cFormLoader = New clsDpiPmFormLoader
cFormLoader.Activate" in form_load?
the clsDpiPmFormLoader should only be created in a bas-module and activated in Sub Main. It should not be declared in forms.
clsDpiPmAssist and optionally clsDpiAsstCmnCtrls (if using common controls) would be declared in each form. Without clsDpiPmAssist, your form will not get scaling events from the framework and will not have auto-scaling capabilities.
Insomnia is just a byproduct of, "It can't be done"
P.S. I do have another version, not yet posted. That version fixes some minor bugs I've found over the past few months, makes scaling imagelists less complex on the user-end (more complex in clsDpiAsstCmnCtrls), enables imagelists to be manually scaled on-demand (not just during scaling cycles), and more.
Was waiting for more feedback regarding other potential bugs and/or enhancements before I posted the new version which is not compatible with the current version... some methods changed.
Insomnia is just a byproduct of, "It can't be done"
LaVolpe
I tested the framework by adding some controls at the extreme right side of the form.
I set the DPI to 100%
Then I changed the DPI to 125%, the controls which I have added become invisible.
A more complete description would be helpful; maybe upload a test project and steps to reproduce the problem. I have some guesses, but don't want to confuse the issue by just blurting them out. Need more details.
Insomnia is just a byproduct of, "It can't be done"
That doesn't help any. There appears to be other things going on in your tests. For example, the option buttons don't have the same Left property value any longer.
It looks like you are manually adjusting the positions during form resize or some other adjustments are being made? Maybe you can upload your project?
Insomnia is just a byproduct of, "It can't be done"
Try it without making the form start-up as maximized.
There is an issue otherwise and I don't immediately see the cause. The problem appears to be related to messages related to moving into/out of maximize state. Need to experiment/debug. Thanx for pointing this out.
P.S. The controls off the screen? From your project, re-check that. They appear to be off the form to start with, even within the IDE form-design (i.e., project not running).
Insomnia is just a byproduct of, "It can't be done"
Thank you sir
I tested again with normal windows state but it is the same issue.
I made the controls within the visible part of the form but still part of the controls are invisible.
I tested again with normal windows state but it is the same issue.
Using your Project1.exe, both @ 100% and 125%, I cannot replicate your screenshots. I am using Win10
1. I ran the exe @ 100% DPI. It maximized and looked similar to your 1st image in your screenshot.
2. I then changed DPI to 125% and the exe scaled appropriately.
3. I then ran another instance of the exe and looked similar to your 1st image but scaled to the new 125% DPI.
4. Toggling back & forth between the two instances showed no differences at all.
Insomnia is just a byproduct of, "It can't be done"
That fact gives me something to look at and research. Per-monitor awareness does not apply to Win7 anyway. This means that possibly the framework, in your case, is taking some actions when it shouldn't.
Insomnia is just a byproduct of, "It can't be done"