Conversation
|
The DPI factor is only computed once but for some reason it seems to work fine when moving to a different screen? Even when I set windows display scaling to a different value on the other screen. The only other thing I'm wondering about is on WinForms, it now uses DPI correction, which doesn't matter now as the DPI factor will always be 1. But if they add DPI support, it's possible they'll go the Avalonia route (handling DPI correction internally), which means this change will break the control whereas if they go the WPF route not making this change will break the control. I think Microsoft would probably mimic their existing product in this regard, but I don't know which is more likely. The Avalonia control uses a "normal" |
|
Hey @bclehmann! I'm going to put this down for tonight so I don't burn out, but I'll make one note that might be helpful
I think .NET Framework and .NET Core WinForms behave differently. There's a demo of each in the sandbox folder, but I recall WinForms in .NET 5 supporting display scaling out of the box (one of the big advantages of upgrading). It looks like you can even define this in Maybe these preprocessor directives (copy/pasted from the internet) would be useful in determining how to detect DPI scaling from inside a user control? #if NETFRAMEWORK
FrameworkSpecific();
#else
CoreSpecific();
#endif |
I tried running on Net Framework 4.8, Net Core 3.1, and Net 5 (yeesh Microsoft has to sort out their naming...) and the DPI factor was always set to 1. I may be doing something wrong. And as for the preprocessor directives, how does it work when you publish to NuGet? I thought the packages were framework-agnostic, and assuming that the C# preprocessor is a traditional preprocessor, those directives would make a choice at compile time on your machine, not at runtime on the users'. However, if the nupkg has an assembly for each version then the preprocessor directives would work. |
Preprocessor directives and NuGetIf you check out https://www.nuget.org/packages/ScottPlot.WinForms you can see the platforms a package was targeted for in the dependencies drop-down. If you download the .nupkg file and rename it to .zip, you can extract the folder and see how it works. It looks like if it targets 4 platforms, the package contains 4 DLLs. I think this means we can use framework/core preprocessor directives freely! DPICorrectedInputState and IInputStateI noticed that the only time I like what you did with I'm thinking out loud here and typing in the browser, but what would you think about something like this stored at the top-level of a class? We could use events to call class DisplayScale
{
public double ScaleFactor {get; private set;}
const int DEFAULT_DPI = 96;
public DisplayScale() => Measure();
public Measure(){
using Graphics gfx = Drawing.GDI.Graphics(new Bitmap(1, 1));
ScaleFactor = gfx.DpiX / DEFAULT_DPI; // is this definitely a floating point result?
}
}// control code
DisplayScale DPI = new DisplayScale();// control GetInputState()
X = e.X * DPI.ScaleFactor,
Y = e.Y * DPI.ScaleFactor,What do you think? |
Preprocessor
You are correct, I tried this myself with a decompiler and the preprocessor directives work as intended: C#: protected override void Dispose(bool disposing)
{
#if NET5_0
throw new System.Exception("Waaaoow");
#endif
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}Output NET 5.0: .method family hidebysig virtual instance void Dispose(bool disposing) cil managed
{
// Method Start RVA 0x28b0
// Code Size 12 (0xc)
.maxstack 2
.locals init
(
[0] bool #flag1
)
L_0000: nop
L_0001: ldstr "Waaaoow"
L_0006: newobj instance void [System.Runtime]System.Exception::.ctor(string)
L_000b: throw
}Output NET 4.72 .method family hidebysig virtual instance void Dispose(bool disposing) cil managed
{
// Method Start RVA 0x28b0
// Code Size 43 (0x2b)
.maxstack 2
.locals init
(
[0] bool #flag1
)
L_0000: nop
L_0001: ldarg.1
L_0002: brfalse.s L_000f
L_0004: ldarg.0
L_0005: ldfld [ScottPlot.WinForms]ScottPlot.FormsPlot::components
L_000a: ldnull
L_000b: cgt.un
L_000d: br.s L_0010
L_000f: ldc.i4.0
L_0010: stloc.0
L_0011: ldloc.0
L_0012: brfalse.s L_0022
L_0014: nop
L_0015: ldarg.0
L_0016: ldfld [ScottPlot.WinForms]ScottPlot.FormsPlot::components
L_001b: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0020: nop
L_0021: nop
L_0022: ldarg.0
L_0023: ldarg.1
L_0024: call instance void [System.Windows.Forms]System.Windows.Forms.ContainerControl::Dispose(bool)
L_0029: nop
L_002a: ret
}My apologies, the decompiler I used can normally turn the MSIL back into C# (or even VBScript), but it doesn't seem to like the class that I put my test code in. C'est la vie. IInputStateI chose to use an interface so that the two classes (regular DisplayScale.Measure()I like this idea, I think it would fit in |
PreprocessorAwesome, thanks for checking that out! I suspected that was the case but it helps to see it to know for sure. FYI I edited your comment to add Where to put scaling logic
Yeah, that's my feeling too. I kind of like seeing that those discrete multiplication steps too - it makes it really obvious exactly what's happening.
That sounds good to me! Probably in the ScottPlot.Control namespace? |
Yeah, it sorta works, it got the opcodes that both have in common like
Yeah, that sounds good to me. |
2a1c973 to
96e538f
Compare
The back-end is intended for managing plots in a UI-agnostic manner. The front-end/controls is intended to manage platform-specific state. I also renamed some things to use the word "ratio" everywhere "factor" was written.
|
Thanks @bclehmann! Interestingly, controls that use display scale correction for mouse tracking also need it for resizing 0de7edd I made a note in https://github.com/ScottPlot/Website/issues/1 to put some of what we discussed here on the new FAQ section when that gets off the ground. |


New Contributors:
please review CONTRIBUTING.md
Purpose:
#720
This works by creating a new
DPICorrectedInputStateclass, which implements a newIInputStateinterface. One can get an instance of this new class like thisnew DPICorrectedInputState(input, dpiScaling). This conveniently means that the only method that needs changing is theGetInputStatemethod, and only if it's a platform which has DPI scaling and requires ScottPlot to handle the DPI scaling (i.e. WPF). The modification is trivial even in that case.New Functionality:
Fixes #720