C# Scaling UserControl content to match users Dpi/Font Size - c#

How do I get my OwnerDrawn UserControl to respect the users dpi (96/120/xxx) and/or font-size (normal, large, extra large)?
Some people suggest to use the DpiX and DpiY properties on a Graphics object, but that doesn't seem to to anything in my control (i.e. they are always set to 96, regardless of which font-size or dpi I choose).
There is another similar question here on StackOverflow where it suggests to use the AutoScale properties, but the suggested solutions don't really do anything either.
Is there no way of doing this in .NET except for relying on WPF?

You would need to set the AutoScaleMode property of the UserControl to AutoScaleMode.Dpi, and not set the AutoScale property to true. If you do, it will reset the AutoScaleMode back to None. The AutoScale property is obsolete and is there only for backwards compatibility (see the Important Note in this MSDN article).
Also, in Windows Vista/7, unless you explicitly specify that your application is DPI-aware, Windows will emulate a default DPI environment so that your application renders with 96 DPI, then scale the resulting bitmap to the appropriate size. To avoid that, you can alter your application manifest to inform Windows that you are in fact DPI aware - see the Using manifest to declare DPI awareness section in this article.

I have the same problem, I tried using GetDC + GetDeviceCaps + ReleaseDC, except using Graphics worked, atleast on Vista32. I am not experienced with DPI yet, but nobody had answered this and at least this might be helpful for others.
Check out Creating a DPI-Aware Application. This mention why it might always return 96 regardless of actual DPI setting.
Quote from above link:
DPI scaling in a Win32 application
In Win32 applications, do the following:
Use the SetProcessDPIAware function to cancel dpi scaling.
When sizing drawn interface elements, use physical measurements, such as centimeters. By using physical dimensions rather than pixels, you ensure consistent sizing on all types of displays.
To get the system dpi setting, use the CDC::GetDeviceCaps function with the LOGPIXELSX flag. If you do not cancel dpi scaling, this call returns the default value of 96 dpi.
Use the GetSystemMetrics function to get preferred sizes of user interface elements, such as window borders. When dpi scaling is disabled, the measurement values that are returned for interface elements are scaled to the selected dpi setting. If dpi scaling is active, the function returns measurements based on 96 dpi, regardless of the system dpi setting.
Answered from: About DPI Issue
Sample code rewritten with using-statement (original source):
float dpiX = 96, dpiY = 96;
using(Graphics graphics = this.CreateGraphics())
{
dpiX = graphics.DpiX;
dpiY = graphics.DpiY;
}

Related

Handling change of primary DPI without restarting the app

I have a WPF project which draws windows (like popups) over a different app 'APP', according to some elements' position of APP.
Those windows' position is calculated according to the system DPI (aka primary monitor's DPI).
When I change the primary DPI in the display settings, the windows' position is calculated using the 'old' primary DPI, which results in wrong location.
Is there some way to perform these calculations, which depend on the primary DPI, using the 'new' primary DPI and not using the old one?
I know that when changing the primary DPI, windows alerts me that "Some app won't respond to scaling changes until you close and reopen them.", but I need to find a way around it.
Thanks
You'll want to look into the SystemEvents.DisplaySettingsChanged event, and write a method that handles that event when it happens. I've done this before in WPF, so I know that it works.
Then, use the answer here by Ana Betts to calculate the primary DPI:
PresentationSource source = PresentationSource.FromVisual(this);
double dpiX, dpiY;
if (source != null) {
dpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;
dpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;
}

Difference in PInvoke GetMonitorInfo between WPF and Console Application [duplicate]

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).

Screen Resolution not matching Screen.Bounds

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).

C# WinForms disable DPI scaling

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.

C# WPF resolution independancy?

I am developing a map control in WPF with C#. I am using a canvas control e.g. 400 x 200 which is assigned a map area of e.g. 2,000m x 1,000m.
The scale of the map would be: canvas_size_in_meters / real_size_in_meters.
I want to find the canvas_size_in_meters.
The canvas.ActualWidth gives the Width in DIU's (Device Independant Units). So, 400 DIU's is 400/96 = 4,17 inches, PROVIDED that the physical resolution of my monitor is 96 dpi.
However, using a ruler, I found that the physical resolution of my monitor is 87 dpi. (There are only few monitors that ACTUALLY have 96 physical dpi)
That DPI difference (10%) translates to a +10% difference in the actual map control width on screen.
How do I measure the size of a WPF control in inches EXACTLY and regardless of screen resolution and DPI setting ?
How do I measure the size of a WPF control in inches EXACTLY and regardless of screen resolution and DPI setting ?
This isn't actually possible, because for it to work, WPF would have to know the resolution (in terms of DPI) of your monitor. Sounds nice in theory, but in practice windows doesn't know this information. This is why windows itself always assumes 96dpi blindly instead of being smarter about it.
Even if there were some way to manually tell it, or if your particular monitor has a custom driver that does pass the correct information to windows, this isn't going to work on anyone else's computer, so windows doesn't pass this information on to any applications.
The best you can do is draw a scale like google maps does. You know that 1 pixel == 1 mile, so you can draw a 50 pixel line on your map, with a label saying "this line equals 50 miles"
There is way to compute current pixel size in mm or inches. As mentioned in the earlier posts, it is not a fixed value and would vary depending on the current resolution and monitor size.
First get the current resolution. Assume it is 1280x1024
Now get the monitor width in mm using GetDeviceCaps function. Its a standard windows library function.
int widthmm = GetDeviceCaps(deviceContext, HORZSIZE);
My monitor width is 362mm
So pixel size = 362/1280 = 0.282 mm
The accuracy of this method depends on the assumption that the display area covers the width of the monitor exactly.
So to answer the original question, the canvas size of 400 x 200 pixels would be
(400 * 0.282/1000) x (200 * 0.282/1000) in meters when shown on my monitor.
Thank you for you prompt reply.
I totally agree, but I didn't want to believe it in the first place. You see, there has to be an approximate calculation of the scale of the map if the map is used to display different layers of map data (scale dependant).
Most applications use a slider control with e.g. 10 discrete map levels to set the "scale".
Having an absolute scale is not crucial for the application, it would be nice to display an indicative scale, like 1:15,000.
An absolute scale would require for an extra variable monitorPhysicalDPI (initially set to 96) that if the uses chooses to change would give slightly better scaling (again it's not crucial). The size of the map control would be:
map.ActualWidth * (96/monitorPhysicalDPI) * inchesPerDIU, inchesPerDIU is 1/96
Again these are cosmetics.. Wouldn't it be nice if Windows knew the ACTUAL control's dimensions? (user would have to give information about the screen dimensions on OS setup, or simply installing the monitor INF file)

Categories

Resources