I created an application that works perfectly until the user selects 125% or 150%. It would break my application. I later found a way to find the font size by detecting the DPI.
This was working great until people with Chinese versions of Windows 7 started using my application. The entire application breaks on Chinese Windows 7. From what I can tell (I can't really test it for I only have the English version and installation the language packs does not cause the problem) Chinese characters are causing a weird DPI that breaks my application.
My current code works like this:
if (dpi.DpiX == 120) // For 125% fonts
{
// Resize form and set default font to correct problems
}
else if (dpi.DpiX == 96) // For 100 and 150% fonts
{
// Resize form and set default font to correct problems
}
On English versions of Windows 7 that works great, but somehow Chinese versions skip right by this, and the form destroys itself, with controls not even showing up, font extremely large and pushing past the problem, picture boxes being moved around.
So what is a good way to detect the Windows font scale (100%, 125%, and 150%) without detecting DPI? I need something solid that will work on all Windows 7 operating systems and languages.
The correct way of handling variable DPI settings is not to detect them and adjust your controls' sizes manually in a switch statement (for starters, there are far more possibilities than those you show in your sample if statement).
Instead, you should set the AutoScaleMode property of your form to AutoScaleMode.Dpi and let the framework take care of this for you.
Add the following code to your form's constructor (or set this property at design time):
this.AutoScaleMode = AutoScaleMode.Dpi;
Although you might prefer to use AutoScaleMode.Font. For more information on automatic scaling, see the MSDN documentation.
For C++/Win32 users, here is a good reference: Writing High-DPI Win32 Applications.
get system DPI scale using this:
Read from registry AppliedDPI dword located in Computer\HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics. Then divide it by 96.
try
{
double scale = 1.0;
using (RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop\\WindowMetrics"))
{
if (key != null)
{
Object o = key.GetValue("AppliedDPI");
if (o != null)
{
int value = (int)o;
scale = (double)value / 96.0;
}
}
}
}
catch (Exception ex) //just for demonstration...it's always best to handle specific exceptions
{
//react appropriately
}
for 100% --> value is 96 scale is 1.0
for 125% --> value is 120 scale is 1.25
for 150% --> value is 144 scale is 1.5
now you can resize your form and set new font size by this scale automatically;
if your on a newer version of windows I recommend reinstalling your graphics card drivers ( e.g installing a newer version) I had the same problem, my display scale was set to 100% but the font was way off.
hope this fixes your problem
Related
I am working on a windows desktop application that will be used in various resolutions and text sizes. Before getting to AutoScaling and other ways for the desktop application to work, I need to see what the user sees.
Adjusting the resolution of my development machine is not good enough. The biggest kicker is the text size. Some users have it set to 125% of default which distorts practically everything.
Free tools like this only lets you play with resolutions, not text sizes.
Changing the text size in windows 10 is an ordeal, jumping through a lot of hoops. You have to log off and log back in. Also, when I open the project in Visual Studio with the text size change, the forms are jacked up. The form size is shrunk with all the controls outside.
I'm not 100% sure what your asking about but i think this might help you
yourForm.AutoScaleMode = AutoScaleMode.Dpi;
Also here is more info on Scaling in Windows Forms: https://msdn.microsoft.com/en-us/library/ms229605.aspx
Or more info on writing DPI aware Win32 Applications: https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266%28v=vs.85%29.aspx
You can get the user windows display text size by making a new graphics object
Example:
Graphics graphics = this.CreateGraphics();
flaot windowsFontSize = graphics.DpiX;
string fontSize = string.Empty;
if(windowsFontSize == 96f)
fontSize = "Smaller";
else if (windowsFontSize == 120f)
fontSize = "Medium";
else if (windowsFontSize == 144f)
fontSize = "Larger";
Info found here: How to get Windows Display settings?
The only way to have the program run at 125% text scale without changing you text size in your settings or multiplying everything in your form by 125% is to run a virtual machine. I can personally vouch for Parallels and VMware. If you want to learn more about Virtual Machines you can read this: https://en.wikipedia.org/wiki/Virtual_machine
I am seeing an interesting difference between the resolution that is set through Control Panel and the output of Screen.Bounds for my widescreen laptop. The screen is 15.5" and the resolution set through Control Panel is 1920x1080. However when I run some code like this.
Screen[] screens = Screen.AllScreens;
foreach (Screen scr in screens)
{
Console.WriteLine("Width: " + scr.Bounds.Width + ", Height: " + scr.Bounds.Width);
}
The output shows my resolution being 1536x864. I have done some looking around, and I thought it may be related to a DPI issue, and when I look at my display settings, the slider (I am on Windows 8.1) is in the middle, and the checkbox that states "Let me choose one scaling level for all my displays" is unchecked. I ran this little code to get the current DPI setting.
float dpiX, dpiY;
Graphics graphics = new System.Windows.Forms.Form().CreateGraphics();
Console.WriteLine("DPI: " + graphics.DpiX);
The DPI that is returned is 96.0 which by my understanding is the 100% DPI setting (so no enlargement or whatever it is called). What seems odd to me is that the bounds returned by Screen is exactly 80% of my actual resolution, which would make me think my DPI is set to 100 (or 125%) but it is not. I am only having this issue with my laptop screen, as my secondary monitor has bounds that are equal to resolution through Control Panel. Is this due to the fact that my DPI setting is not set to have the displays independent of eachother (that checkbox checked)? For a little bit of background, I am writing a tool that takes the current screens and gets pictures from reddit and fits them to the screens independently of each other, so whatever solution I have, it has to correctly get the resolution of each display.
I had same problem also for screen shot tool.
I found solution and it works for me.
private enum ProcessDPIAwareness
{
ProcessDPIUnaware = 0,
ProcessSystemDPIAware = 1,
ProcessPerMonitorDPIAware = 2
}
[DllImport("shcore.dll")]
private static extern int SetProcessDpiAwareness(ProcessDPIAwareness value);
private static void SetDpiAwareness()
{
try
{
if (Environment.OSVersion.Version.Major >= 6)
{
SetProcessDpiAwareness(ProcessDPIAwareness.ProcessPerMonitorDPIAware);
}
}
catch (EntryPointNotFoundException)//this exception occures if OS does not implement this API, just ignore it.
{
}
}
You should call SetDpiAwareness() method before call functions to get system resolution, etc.
Also if you have some UI in your application now it is your responsibility to scale your UI in screen with high DPI.
Hope this helps.
I believe you have to notify the operating system that your application is DPI aware. Otherwise the OS pretends that everything is just fine, leading to the behaviour you're observing - the OS handles the resizing.
You can find some information about writing DPI aware applications here - http://msdn.microsoft.com/cs-cz/library/dd464646.aspx Of course, you should make sure your application actually is DPI aware - if not, you better stick with the default. It's not as nice, but at least it will work.
The main difference that Windows 8.1 brought to this is that you can have different DPI on different monitors, and you can query the monitor DPI API. .NET (and especially WPF) by default handles DPI awareness automatically, but only based on system DPI. If your monitors have different DPI settings, it will behave as non-DPI-aware (more precisely, system-DPI-aware, but the end result is your applications graphics are going to be virtualized by Windows). I'd expect that if you disconnect your second display, your application would behave as expected on your sole display (at least after manually setting the DPI, whatever the value).
I am seeing an interesting difference between the resolution that is set through Control Panel and the output of Screen.Bounds for my widescreen laptop. The screen is 15.5" and the resolution set through Control Panel is 1920x1080. However when I run some code like this.
Screen[] screens = Screen.AllScreens;
foreach (Screen scr in screens)
{
Console.WriteLine("Width: " + scr.Bounds.Width + ", Height: " + scr.Bounds.Width);
}
The output shows my resolution being 1536x864. I have done some looking around, and I thought it may be related to a DPI issue, and when I look at my display settings, the slider (I am on Windows 8.1) is in the middle, and the checkbox that states "Let me choose one scaling level for all my displays" is unchecked. I ran this little code to get the current DPI setting.
float dpiX, dpiY;
Graphics graphics = new System.Windows.Forms.Form().CreateGraphics();
Console.WriteLine("DPI: " + graphics.DpiX);
The DPI that is returned is 96.0 which by my understanding is the 100% DPI setting (so no enlargement or whatever it is called). What seems odd to me is that the bounds returned by Screen is exactly 80% of my actual resolution, which would make me think my DPI is set to 100 (or 125%) but it is not. I am only having this issue with my laptop screen, as my secondary monitor has bounds that are equal to resolution through Control Panel. Is this due to the fact that my DPI setting is not set to have the displays independent of eachother (that checkbox checked)? For a little bit of background, I am writing a tool that takes the current screens and gets pictures from reddit and fits them to the screens independently of each other, so whatever solution I have, it has to correctly get the resolution of each display.
I had same problem also for screen shot tool.
I found solution and it works for me.
private enum ProcessDPIAwareness
{
ProcessDPIUnaware = 0,
ProcessSystemDPIAware = 1,
ProcessPerMonitorDPIAware = 2
}
[DllImport("shcore.dll")]
private static extern int SetProcessDpiAwareness(ProcessDPIAwareness value);
private static void SetDpiAwareness()
{
try
{
if (Environment.OSVersion.Version.Major >= 6)
{
SetProcessDpiAwareness(ProcessDPIAwareness.ProcessPerMonitorDPIAware);
}
}
catch (EntryPointNotFoundException)//this exception occures if OS does not implement this API, just ignore it.
{
}
}
You should call SetDpiAwareness() method before call functions to get system resolution, etc.
Also if you have some UI in your application now it is your responsibility to scale your UI in screen with high DPI.
Hope this helps.
I believe you have to notify the operating system that your application is DPI aware. Otherwise the OS pretends that everything is just fine, leading to the behaviour you're observing - the OS handles the resizing.
You can find some information about writing DPI aware applications here - http://msdn.microsoft.com/cs-cz/library/dd464646.aspx Of course, you should make sure your application actually is DPI aware - if not, you better stick with the default. It's not as nice, but at least it will work.
The main difference that Windows 8.1 brought to this is that you can have different DPI on different monitors, and you can query the monitor DPI API. .NET (and especially WPF) by default handles DPI awareness automatically, but only based on system DPI. If your monitors have different DPI settings, it will behave as non-DPI-aware (more precisely, system-DPI-aware, but the end result is your applications graphics are going to be virtualized by Windows). I'd expect that if you disconnect your second display, your application would behave as expected on your sole display (at least after manually setting the DPI, whatever the value).
I have a Winforms application (in Visual Studio 2010) that contains a Report Viewer control that previews and prints an A4 size report.
One user has his Windows font size set to 150% (i.e. a system DPI change) and when he runs the report, it is shrunk to 2/3 size in both the Report Viewer preview and the printed report.
The AutoScaleMode property of the form containing the Report Viewer is set to "Font", although it doesn't seem to affect the report if I change this.
This isn't a font (missing/scaling) issue as I have an example program to demonstrate the problem that contains nothing but a rectangle that takes up the whole page.
It seems I need some way to specify in the report itself that it should be rendered at 96dpi regardless of the system dpi setting, but can't see anywhere that I can specify this. What am I missing?
Thanks,
David
I ran into the same problem. WinForms ReportViewer is already DPI aware and will do its own scaling. You just have to tell the system that your application is DPI aware so that the system doesn't try to scale it after.
Add a manifest to your application if you haven't already, then inside the tag, add the following:
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
You can also use an API function SetProcessDPIAware, but it is recommended against:
http://msdn.microsoft.com/en-us/library/ms633543.aspx
As a complimentary answer to what #JoMan said (since I can't comment on his post) bear in mind that you can manually scale up the UI elements in your app relatively simply. So leave your application DPI aware (so that your system doesn't distort the printed results) as JOMan suggested. You could use something like this...
Using g As System.Drawing.Graphics = WindowsForm.CreateGraphics
Dim sngScaleFactor As Single = 1
Dim sngFontFactor As Single = 1
If (graphics.Dpix >= 96) Then
sngScaleFactor = (graphics.Dpix / 96) - 0.25
sngFontFactor = (graphics.Dpix / 96) - 0.25
End If
If WindowsForm.AutoScaleDimensions = WindowsForm.CurrentAutoScaleDimensions Then
WindowsForm.Scale(sngScaleFactor)
For Each child As Control In WindowsForm.Controls
ScaleFontRecursively(child, sngFontFactor)
Next
End If
End Using
I'm sure many people will argue (rightly!) that you generally don't want to detect DPI and manually scale yourself, but the bug with the dpi autoscaling screwing up printed microsoft reports is still outstanding as of 2018, so this provides an easy work around.
I switched to VS 2012 yesterday from VS 2010 and all seemed to go well except this.
I have a button on my form that when pressed extends the width of the form to show additional controls. Press the button again and it reduces the width to hide those controls. Now all of this worked great in VS 2010 and also works fine when I debug in VS 2012 but as soon as I publish or compile the project and open the .exe when you click on the button it adds like 5 to the width instead of the 100+ it needs to. I click it again and it will then change it to 372 like it should and shows all my controls. I click it again to hide the controls and it hides the controls partially (goes to the 188 + the mysterious 5) I hope all of this makes sense and am hoping there is a better way to run the process I need to.
Here is the code I am currently working with and I didn't change anything between switching from 2010 to 2012. In fact, if I open this same solution in 2010 and publish everything works fine.
private void button1_Click(object sender, EventArgs e)
{
if (this.Width == 188)
{
this.Width = 372;
this.Height = 540;
progressBar.Value = 100;
copied_status.Text = ("Output View Enabled");
}
else
{
progressBar.Value = 100;
copied_status.Text = ("Output View Disabled");
this.Width = 188;
this.Height = 540;
}
if (this.Width == 372)
{
button1.Text = "<<";
}
else
button1.Text = ">>";
}
The width of your form hasn't been 188 pixels in a long time. Now with VS2012, Windows finally stops lying about it.
At issue are the fat window borders in Aero. They were an extreme appcompat problem when the feature was introduced in Vista. Very necessary because those two pixels where getting hard to hit with a mouse. But drastically incompatible with the way an application creates a window. It asks for a specific window size, the outer size, the nWidth and nHeight arguments of the CreateWindow() function. But what really matters is the size of the client area, the part of the window inside the borders. If Microsoft wouldn't have done something about it, old applications would have ended up with a client area that was too small. Which looks very bad, the window content would not fit anymore. A control towards the bottom or right side of the form would not be completely displayed for example.
So, sneakily, Aero makes the window larger by the extra width of the fat borders. And when the app asks for the window size, it sneakily says that it is smaller by the same added width. The app doesn't know any better than it is still running with the same window size it had on XP. This works pretty well, but is not exactly ideal. Hard to get window edges to align properly with that lie for example.
Whether or not Aero will lie about the window size is based on the target operating system recorded in the EXE header. When it sees a version older then 6.00, the Vista version number, then it will assume that your EXE is a legacy program that doesn't know about the fat border feature. So needs to be lied to. You've been running with that target version number set to 4.00 for a long time, it is written by the .NET compiler when it builds your program. You can see it with dumpbin.exe /headers yourapp.exe.
This finally changed in VS2012 and .NET 4.5. Which is a .NET version that is not available in XP. The compiler can finally make the hard assumption that XP is history and you are going to run on a version of Windows that supports Aero. So it sets the target Windows version in the EXE header to 6.00. Correspondingly, Aero will now stop lying about the window size. You get the real one, not the faked one.
So a quick fix is to change the target .NET framework version to 4.0. That's available on XP so you'll get lied to again.
Of course it is better to fix your code. Never use the Size, Width or Height property, they'll inevitably depend on the border and caption size. Use the ClientSize property instead, that's the stable one and the one you really care about. But be careful with that property as well, a form may rescale when it runs on a machine that has its video adapter set to more than 96 dots per inch. Another feature that's very accessible in Vista and up. Rescaling changes the ClientSize proportionally by the DPI setting.
The real fix is to use a bool field instead that keeps track of the window state. And set the ClientSize property based on the position of a control you want to hide or reveal. So roughly:
private bool enlarged;
private void button1_Click(object sender, EventArgs e)
{
enlarged = !enlarged;
int width = someControl.Left - 5;
if (enlarged) width = someControl.Right + 5;
this.ClientSize = new Size(width, this.ClientSize.Height);
}
Replace someControl in this code with the name of your controls.