This took all day but I have figured out a workaround for the broken DPI handling of forms not directly descended from TForm in Seattle Update 1. Submitted the report here: https://quality.embarcadero.com/browse/RSP-12971 Now I realize that the "documentation" says PixelsPerInch is supposed to be the design PPI and not the runtime, but that is just stupid and useless. Also, the PixelsPerInch property changes to the monitor DPI if the form is moved to a new DPI monitor, which leads me to believe that NOBODY IS TESTING THIS STUFF. They should not be making changes like this without thorough testing. Anyway, my workarounds are just a few lines of code in ReadState and Loaded overrides, and restores the behavior from Delphi Seattle, which both made sense and WORKED. Full project is attached to the report.


This took all day but I have figured out a workaround for the broken DPI handling of forms not directly descended from TForm in Seattle Update 1. Submitted the report here: https://quality.embarcadero.com/browse/RSP-12971 Now I realize that the "documentation" says PixelsPerInch is supposed to be the design PPI and not the runtime, but that is just stupid and useless. Also, the PixelsPerInch property changes to the monitor DPI if the form is moved to a new DPI monitor, which leads me to believe that NOBODY IS TESTING THIS STUFF.  They should not be making changes like this without thorough testing. Anyway, my workarounds are just a few lines of code in ReadState and Loaded overrides, and restores the behavior from Delphi Seattle, which both made sense and WORKED. Full project is attached to the report.

Comments

  1. It seems that someone else is having trouble with DPI in inherited forms - https://quality.embarcadero.com/browse/RSP-12966

    ReplyDelete
  2. Ilya S It is long overdue. However, can tell you from my work designing a full-featured text editor from the ground-up (which used its own text rendering engine), that it is a LOT of work to get right. I was able to use Uniscribe to handle all of the glyph placement/etc, and did not have to worry about "getting it right" when it came to RTL text and multi-glyph characters (vowel points in Hebrew letters, etc).  Firemonkey does not have access to Uniscribe so it would need a different engine (is one available they could use?) or they would need to write their own -- which I do not think they could do reliably without a significant investment. I doubt you will see BiDi in any meaningful, working state in FireMonkey.

    ReplyDelete
  3. Design PPI is the correct design. The app cannot specify the runtime PPI. It's been this way for over 15 years and that aspect of the design is sound. It seems that you are somewhat confused and do not yet understand all the issues here.

    ReplyDelete
  4. David Heffernan Delphi 10 Seattle changed the usage of the PixelsPerInch property to be far more useful than it was before -- in 10 pre-update it was used for two things: 1. To store the design time PPI to allow for proper scaling during ReadState and 2. to track the current PPI of the form during runtime (note that un Update 1 PPI is STILL updated to reflect per-monitor runtime PPI changes). We can quibble over the usefulness of a design-only property (why is it not a read-only value, then?) but it has little to do with the bug I reported. My choice to update PixelsPerInch  in Loaded is because moving the form to a new PPI during runtime does the same thing, so the PixelsPerInch property is inconsistently applied in Update 1 and only becomes consistent once the form is moved to a different PPI monitor-- and in fact this introduces a another bug because the form will think it was already scaled to the target monitor DPI when it was not (assuming the  target monitor has the same DPI as design-time did and the initial monitor was different). It also makes perfect since in a high/multi-dpi world to have a property that tells you what the current form's PPI is, because it will re-scale every time that changes, and you need to know it for rendering things at runtime since the VCL won't manage your image lists for you, etc, to say nothing of stuff done direct to the Canvas. You cannot rely on one of the Screen monitorfrom* functions because you do not know if scaling has been applied yet based on WIndows messages -- which do not use the same criteria for deciding when the form should be counted as having crossed the threshold into a new monitor for dpi change considerations. I readily admit I "do not yet understand all the issues here" since I am always learning, but I have been working with a very large project doing multi- and high-dpi design since Seattle was released and have a pretty good grasp on it. The bug I reported is real -- if your form is not directly descended from TForm, the VCL will not scale it properly. Period. It is easy to see that this was overlooked because it requires a certain type of testing to see the problem, but it is quite real. In fact looking at the VCL code in ReadState it is apparent that whoever wrote it has made a lot of invalid assumptions about what goes in in a high+multi DPI environment.

    ReplyDelete
  5. David Heffernan Besides, who said PixelsPerInch should be used to *set* the runtime PPI?  Not me.  I only argue that it should accurately reflect the current PPI of the form during runtime, as it did in Delphi Seattle 10.  I realize this was not according to the documentation but with the added support for multi-dpi awareness it made a lot of sense, and the documentation should simply have been updated. Instead, Update 1 broke functionality that worked and only half-way went to follow the documentation (since it still changes when the window gets a API message telling it that the DPI is changing). Anyway, the fix is easy and even if they decide to leave PixelsPerInch as a value that only tells you what the design PPI was (not giving you any help during runtime WRT actually scaling things to the form's current real PPI) that would be trivial to implement in a custom property.

    ReplyDelete
  6. I was commenting on your post which stated that design PPI was a bad idea. Perhaps that post was imprecise.

    ReplyDelete
  7. David Heffernan Design PPI value is not just a good idea, it is necessary for proper runtime scaling. But once the form has been scaled it is necessary to know what the new, scaled value is in order to keep pace with it, and in order to properly re-scale when the from goes to a new DPI monitor.  So all I am saying is that the behavior introduced in Seattle 10 was proper given the new support for multi-dpi in the VCL, and that the regression to documentation in Update 1 is wrong (and not only wrong, but was not completely done anyway, breaking multi-dpi support on any system with a system dpi <> design dpi).  I hope that clarifies. :-)

    ReplyDelete
  8. Not really clear. I'll have to look at code. However, I doubt I'll take anything that Emba provide, and will do what I also do and replace with something of my own design.

    ReplyDelete
  9. Brandon Staggs Are you sure you are doing this right?
    What you are supposed to do at design time is set the form at 96 pixels per inch no matter what, even if you are designing in hi-res monitor.

    Then when you read Screen.PixelsPerInch, when you have "hi-dpi" in the manifest, it will return current logical dpi. For instance, if it returns 192 you know you need to scale everything by 2 because 192/96 = 2.  If user has Windows set for 200% scaling it always returns 192.

    In Windows 96 dpi always means 100% scaling.

    ReplyDelete
  10. Douglas Rudd I do all design at 96 DPI. And if you do your design at >96 DPI you had better let the form designer save the correct DPI or your forms will not be scaled correctly when they are read in. My testing is on system with multiple monitors with different DPI settings and on a Surface Book with 200% scaling or more. I know how the scaling works, and BTW, Screen.PixelsPerInch only gives you the system DPI, it does not tell you what the DPI of the monitor the form is on is set to.  A lot changed in Windows 8.1 and I think Delphi developers should be careful to learn the new APIs because a lot has changed in Windows since Windows XP. You cannot count on Screen.PixelsPerInch to tell you anything except what the global system metrics are being scaled to (width of scroll bars, etc). Regardless, this has little to nothing to do with the reported bug, and is more about per-monitor dpi support, which is new to Seattle. (Per-monitor DPI support is half-way broken in Update 1 because of the changes in ReadState, just look at the code in TCustomForm that responds to Monitor DPI change messages.) If your form is initialized on a main monitor with >96 DPI and then moved to a 96DPI monitor it will be a mess.

    ReplyDelete
  11. If you want to understand how this works, you need to look at two VCL functions un Update 1.

    TCustomForm.ReadState(Reader: TReader); is where initial scaling takes place and has been changed to not update PixelsPerInch after the form is scaled (and do to its condition it stop scaling DFMs after the first read, which is the bug I reported).

    TCustomForm.WMDpiChanged(var Message: TWMDpi) is where per-monitor DPI scaling is handled and it DOES update the PixelsPerInch and uses PixelsPerInch to decide how to respond to the message.

    Whoever made the changes in Update1 did not test their changes on a system with a main monitor set to something other than 96 DPI and secondary monitors set to 96, because they would have seen that forms are completely ruined as soon as there is a DPI switch, because FPixelsPerInch is used for different purposes in two different places in TCustomForm. Also note the introduction of FReserved as a protected (not private) field, which can only be described as a hack. (I am wondering if someone was trying to avoid a compatibility-breaking DCU change?)

    The problems are completely solved by simply reverting to the original Delphi Seattle behavior.

    If there is really a need to leave PixelsPerInch as documented, that is fine -- but a new property needs to be made to expose what the current PixelsPerInch is. REMEMBER, Windows 8.1 changes everything.  When an application declares itself as per-monitor dpi aware, all of the assumptions about form scaling go out the window. You have to be able to handle changes to scaling at runtime, anytime.

    ReplyDelete
  12. PixelsPerInch should match the design time dimensions, and should be updated to match the new dimensions whenever the form is scaled at runtime. If that doesn't happen anymore, it's a defect.

    Per monitor dpi is broken by design because the non client area doesn't scale. Blame ms for that. In Delphi the lack of handling of glyphs is a major hurdle.

    ReplyDelete
  13. David Heffernan Correct, Update 1 removed the "updating" of PixelsPerInch -- except in the monitor dpi change notification handler. !!! -- as for per-monitor being broken by design, that is a valid argument. Non-client is not scaled, but neither are other things you ask the OS to draw, such as menus, check boxes, etc. Those are all still drawn according to the system metrics (which is not per-monitor). It's up to the developer to decide what to do with that.  I think MS is expecting people to move away from raw Win32 for these things. One solution is to write your own manifest and to declare your application as high-dpi aware but not per-monitor aware, so the OS will simply rescale your entire window automatically.  It does not look good on multi-dpi systems but it is what File Explorer and many other MS applications do....

    ReplyDelete
  14. If update 1 is as you describe, that is a defect.

    Myself, I'm sticking to system DPI for now.

    ReplyDelete
  15. This is crazy. I'm sticking to my own custom hackery. The VCL is fundamentally NOT suitable out of the box for per-monitor-DPI in modern Windows.

    Nothing changed for the better in 10.1 berlin, right?

    ReplyDelete

Post a Comment