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.
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 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
I have a WinForm application which hosts many images. When I put the application on a Win7 machines that has a DPI of 120, it completely ruins the look of the form. Is there a way to disable the scaling for my form?
I am aware that this is something that is not advised and that DPI should be seamless and handled by the OS. But when it comes to a skinned application, the images do not scale well. I do not have the luxury of creating images for all the DPI variations, so please don't suggest that as an answer.
You'll have bigger problems when you change the AutoScaleMode property. Increasing the DPI also changes the system font size. Necessarily so, font sizes are expressed in points, 1/72 inch. The fonts need to be bigger to get the same point size when the DPI increases and keep the text just as readable when viewed from the same distance.
Since the controls don't get resized anymore, the text on, say, a button no longer fits. One way to battle this is to change the font size on the controls proportionally. Easy if you let all the controls inherit the form font, just changing the form's Font property automatically updates the controls as well. The clear disadvantage is that the user will have a harder time reading the text. This especially gets bad when the DPI goes to 150 dots per inch and beyond, your UI just turns into an unusable postage stamp.
Yes, background images need to get scaled to fit the larger control or form. A pixel in the image now no longer maps one-to-one to a pixel of the monitor. The default Graphics.InterpolationMode value does a fairly decent job of filtering the image. But it depends on the kind of image how well that turns out. A photo almost always scales very well. Finely detailed line art and text does not. Picking the right kind of image goes a long way to avoiding having to create separate ones.
This problem isn't going to go away until monitors start to have the kind of resolution a printer has. We're still a long way from 600 dpi for desktop monitors. Phones will be first.
Create a application manifest file (right-click on project/ add/new item/application file) and uncomment this section:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware
</windowsSettings>
</application>
Creds to this site: https://www.telerik.com/blogs/winforms-scaling-at-large-dpi-settings-is-it-even-possible-
Adding one line of code before the auto-generated call to InitializeComponent in the Form1-constructor solved it for me:
public partial class Form1 : Form
{
public Form1()
{
// Make the GUI ignore the DPI setting
Font = new Font(Font.Name, 8.25f * 96f / CreateGraphics().DpiX, Font.Style, Font.Unit, Font.GdiCharSet, Font.GdiVerticalFont);
InitializeComponent();
}
}
You can set the AutoScaleMode property of the form to None.