Closed danergo closed 1 month ago
First of all: thank you for this porting.
π
Labels are not rescaled in higher DPI displays
I can't reproduce this behavior. Fresh app with PerMonitorV2
enabled, on the fly DPI changing results:
Thx for the quick heads up! I can confirm, works as intended if I change the monitor from x to y scale while application is running.
But that's not too real life scenario, i.e: I only change scale once per 10 years :)
However, if you have multiple monitors with different scale (100, 250), and you move the app between them, that's I would call a real life scenario.
And that scenario is not working. Lets move the app from 100% display to any other with different scaling.
WoW π―It's very surprising...
What we have on this topic:
Currently we have an interesting discussion on this topic here (read down from the linked post). You can join, or post an example application with this behavior here and I'll mention it there...
P.s. I have only one monitor therefore can test DPI related things only with settings change.
I kindof "fixed" this with using "DpiChangedBeforeParent" and "DpiChangedAfterParent" events.
I kindof "fixed" this with using "DpiChangedBeforeParent" and "DpiChangedAfterParent" events.
@danergo, Those events available for PermonV2 mode applications. And DataVisualization chart is ported only to support existing applications and we are not investing further to support higher DPI modes for this control. If labels here coming from that, explicit handling of those events is the right way to go.
@dreddy-work The main question here why it's working with on the fly dpi change then?
I found another very strange behavior:
2 displays: -250% (primary) -100%
All fonts and lines (borders) MUST be manually rescaled to 100/250. I.e. font: 8.25F/2.5 to look correctly on the not-primary screen.
It seems as if the charting mechanism stores the primary Dpi, and it renders everything with that Dpi, and WinForms doesn't handle the Dpi change; SO in user code we have to deal with it (but in C# WinForms the Primary screen's Dpi value is not accessible).
Is this behavior the same with on the fly dpi change? If no (all working good), then we still need an answer from @dreddy-work... And if yes, then I didn't get second part of your question π
Similar!
On the fly dpi change I could solved by manually setting all fonts and borders to their inital size * DeviceDpi/96.
It worked perfectly, until a point my primary display is 100%. When my primary display is not 100%, setting a font to 8.25 * DeviceDpi/96 would make it extremely big on all screens. If I don't messing with the manual sizing, and leave everything as-is, chart labels are fine on the primary screen only, all other screen they look either way too big or way too small.
Sorry man - still didn't get it :(
I can confirm, works as intended if I change the monitor from x to y scale while application is running.
vs
On the fly dpi change I could solved by manually setting all fonts and borders to their inital size * DeviceDpi/96.
π€·ββοΈ
let's try step by step what is not working - may me some video, or repro app (only on the fly change problems - I have no 2 monitor)?
No worries, I'm going to summarize here all my findings.
Prerequisites:
PerMonitorV2
in csprojChartTest.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="WinForms.DataVisualization" Version="1.7.0" />
</ItemGroup>
</Project>
Form1.cs:
namespace ChartTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}
Form1.Designer.cs:
namespace ChartTest
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
System.Windows.Forms.DataVisualization.Charting.DataPoint dataPoint1 = new System.Windows.Forms.DataVisualization.Charting.DataPoint(0D, 1D);
System.Windows.Forms.DataVisualization.Charting.DataPoint dataPoint2 = new System.Windows.Forms.DataVisualization.Charting.DataPoint(0D, 4D);
System.Windows.Forms.DataVisualization.Charting.DataPoint dataPoint3 = new System.Windows.Forms.DataVisualization.Charting.DataPoint(0D, 2D);
this.chart1 = new System.Windows.Forms.DataVisualization.Charting.Chart();
this.label1 = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.chart1)).BeginInit();
this.SuspendLayout();
//
// chart1
//
chartArea1.Name = "ChartArea1";
this.chart1.ChartAreas.Add(chartArea1);
legend1.Name = "Legend1";
this.chart1.Legends.Add(legend1);
this.chart1.Location = new System.Drawing.Point(12, 12);
this.chart1.Name = "chart1";
series1.ChartArea = "ChartArea1";
series1.Legend = "Legend1";
series1.Name = "Series1";
series1.Points.Add(dataPoint1);
series1.Points.Add(dataPoint2);
series1.Points.Add(dataPoint3);
this.chart1.Series.Add(series1);
this.chart1.Size = new System.Drawing.Size(300, 300);
this.chart1.TabIndex = 0;
this.chart1.Text = "chart1";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(318, 25);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(166, 15);
this.label1.TabIndex = 1;
this.label1.Text = "Series1 (Label for comparison)";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.ClientSize = new System.Drawing.Size(527, 332);
this.Controls.Add(this.label1);
this.Controls.Add(this.chart1);
this.Name = "Form1";
this.Text = "Form1";
((System.ComponentModel.ISupportInitialize)(this.chart1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.DataVisualization.Charting.Chart chart1;
private Label label1;
}
}
This is how my designer looks like:
Testcase 1:
Displays:
I started 3 instances, and put one onto each display:
As you can see, "Series1" label on the chart, on the 250% display is way smaller than the label for comparison, which initially has the same size (and on the primary screen it looks right).
This testcase proves that the Chart control is not able to rescale itself in case of Dpi changes due to moving it to a different display (with different Dpi).
Therefore, I have made a fix into Form's DpiChanged event:
private void Form1_DpiChanged(object sender, DpiChangedEventArgs e)
{
chart1.Legends[0].Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F * e.DeviceDpiNew / 96, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
}
Testcase 2: With the fix above, I have made a same screenshot with 3 instances:
You can see, now the Legend's text's size is matching with the test label's size. Please note also, that other texts on chart are still small.
Now, let's do some further testing.
Testcase 3: Let's start the instances on the 250% display (Number1 - it's still not the primary!):
Please note: instances from left to right:
Okay, so let's call my "font fixer" in the Form's OnLoad too:
private void Form1_Load(object sender, EventArgs e)
{
chart1.Legends[0].Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F * DeviceDpi / 96, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
}
Testcase 4: Retesting with the above fix:
All looks great now.
Let's do some further testing with this.
Testcase 5:
Displays:
Let's start the instances on the primary screen, and move place them the same as before:
You can see, that legend's font is now oversized, BUT other texts on the chart looks good (ONLY on primary screen!)
Result: my font fixer doesn't work if the primary screen is not 100%. It seems the font size change to DeviceDpi / 96
is not sufficient, because font size need to be set by taking into account also the primary screen's Dpi.
So after a crazy amount of digging for the information for the primary screen's Dpi, I changed font fixer to:
chart1.Legends[0].Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F * DeviceDpi / PrimaryScreenDpi, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
And retested testcase4, and testcase5:
Testcase 4 retest:
Testcase 5 retest:
These are my findings, and unfortunately this also affects border sizes on both the chart axes and on linechart lines. Technically all line widths has to be corrected the same way as the fontsize I've shown above without the primary dpi:
So, the correct scaling for fonts is Current_Dpi_which_displays_the_chart / Primary_screens_dpi, and for line widths: Current_Dpi_which_displays_the_chart / 96.
Please note: none of these testing involved Dpi change or Primary display change during runtime. These changes were only done when the application was not running.
I hope now it's a bit clearer, let me know if there is anything unclear parts still. :) Thank you!
@danergo Came to say thank you for the detailed report. As soon as I have time, I will definitely try to figure it out...
@danergo
Ok, I've spend some time to dig into this...
And found that chart control simple not support PerMonitorV2
at all π
In this test I set up PerMonitorV2
in project file, but forgot to call ApplicationConfiguration.Initialize();
π€¦ββοΈSo app was running in SystemAware
mode. And this increased font is simple zooming...
That is. Only SystemAware
out of the box, or manually support PerMonitorV2
, like you tried, until PerMonitorV2
will be implements internally (I hope it will).
@danergo
I kindof "fixed" this with using "DpiChangedBeforeParent" and "DpiChangedAfterParent" events.
Hi, I wander - how you was able to fix this?
As I can see, that all scaling done around Graphics.DpiX/DpiY
and Graphics
doesn't support PerMonitorV2
- DpiX
always return app start dpi.
----UPD----
Oh, I see - your mostly talked about fonts. Fonts are one side of the problem, we also need to scale graphics...
Implemented in 1.10 preview.
First of all: thank you for this porting.
I have created a very simple form with C# .NET 7:
WinForms.DataVisualization is at version 1.7.0 (Latest stable by the NuGet manager).
When I run this application on standard DPI (96, 100% scale) screens it works as intended:
However after I move the window to a different DPI (240, 250% scale), all labels stays small:
To illustrate the problem better, I scaled down this last image back to 100% (please don't consider the borders, lines, etc, focus only to labels on legend, and around the chart):
Can we somehow keep the consistency between DPI changes?