I use this (C# .NET) call to quickly get an image of a control.
Rectangle rectToCapture = myControl.RectangleToScreen(myControl.Bounds);
However, this only works if my Windows display scaling is set to 100%. If I set the display scaling to 125% this rectangle is totally wrong. How can I capture the correct rectangle regardless of scaling?
Per #Jimi I had to make the form DPIAware.
https://social.msdn.microsoft.com/Forums/windows/en-US/4d3979d4-88df-44af-8b66-16b1d4d41d57/issue-with-vs2015-windows-form-dpi-and-screen-resolution?forum=winforms
Related
I have a strange problem. I am trying to set the cursor position with WPF. So far so good. The problem is the following:
On my computer screen (1920x1080, 100% windows scaling) everything works fine, but on my surface laptop (3000x2000, 200% windows scaling) I have run into some problems. Because of the scaling factor the "new resolution" is 1500x1000 (because of the 200% every pixel is "twice as big").
When I set the cursor to 750, 500 with WinForms the cursor appears in the exact middle of the screen but when I set the cursor position to the same point with WPF the cursor appears in the upper left section.
So apparently WinForms uses the resolution 1500x1000 and WPF uses the resolution 3000x2000 when setting the cursor position...
Why are they using different resolutions here? This doesn't make sense to me. Because when I am reading the ScreenWidth and Height with
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width // and Height, WinForms
and
System.Windows.SystemParameters.PrimaryScreenWidth // and Heigth, WPF
both are showing 1500x1000.
Can anyone help me? Maybe it is just a WPF/WinForms difference?
Code to set cursor:
Cursor.Position = p; // with imported System.Windows.Forms;
// or (tryed both)
[DllImport("User32.dll")]
private static extern bool SetCursorPos(int X, int Y);
SetCursorPos(p.X, p.Y);
First post so please don't kill me!
I am not an expert in this topic, but maybe:
WPF uses vector graphics to render things, and has a native support for scaling. If you change font size in Windows to 200%, every WPF application will be 2x bigger.
But in WinForms, if you didn't write your application correctly, scaling can ruin your UI.
To avoid this, if you run WinForms app (in Windows 10) it will just use 100% font size and it will stretch rendered image to 200% size. So you can see for example blurry text. If image is stretched - screen coordinates must be stretched too.
But that's just my guess, I'm not an expert at this.
Problem:
We need help with how to use WinForms' ability to auto-scale to different DPI’s to allow us to print our Forms at 600dpi, rather than at the screen DPI.
For What-You-See-Is-What-You-Get printing, we have been simply taking our nicely laid out window and printing it (turning off scrollbars and buttons and such). That works great EXCEPT for one thing: it comes out at 96dpi or 120dpi (whatever is the screen resolution)… either of which look grainy and unprofessional (our customers are complaining). And although it is as readable as what would be on the screen, you expect printed documents to be MORE readable than on-screen… you expect to be able to see additional details, to be able to read smaller text, etc.
Alternatives considered:
Given we have auto-scaling working great, such that our window looks good in 96dpi, 120dpi, 144 dpi, etc., we were hoping we could just draw our window at 600dpi and then print that.
OR, we looked at drawing the window off-screen 5-6x larger than normal such that we have the same number of pixels as 600dpi, but at 96 or 120 dpi… but then drawing that giant window to the printed page at 300 or 600 dpi (whatever the printer is).
If you can tell us how to do either of those alternatives, OR if you can give us a different way to accomplish our goal, then we would greatly appreciate it.
Current code:
In case it matters, our Form consists of a FlowLayoutPanel laying other smaller FlowLayoutPanels into columns, those smaller FlowLayoutPanels laying out a single column of TextBoxes, RichTextBoxes, a third-party RichTextEditor, PictureBoxes, and DataGridViews. We use a class derived from PrintDocument implementing OnBeginPrint, OnPrintPage, and OnEndPrint. In OnPrintPage, it manipulates our normal window off-screen (below and right of the actual screens) to fit the page size, then asks our main panel (the top FlowLayoutPanel) to DrawToBitmap, then uses the Graphics object passed into the PrintEventArgs to DrawImage that Bitmap. We also use Graphics.DrawString to apply a footer to each page. The main code:
using (Bitmap bm = new Bitmap(sz.Width, sz.Height))
{
Rectangle rect = new Rectangle(0, 0, sz.Width, sz.Height);
mp.DrawToBitmap(bm, rect);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; // so footer is anti-aliased
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; // so when we scale up, we smooth out the jaggies somewhat
e.Graphics.DrawImage(bm, this.MarginBounds, rect, GraphicsUnit.Pixel);
if (this.Footer != null)
e.Graphics.DrawImage(this.Footer, this.FooterLocation);
if (!string.IsNullOrEmpty(pageNumber))
{
e.Graphics.DrawString(pageNumber, KBStyle.Normal.Font, Brushes.Black,
this.MarginBounds.X, this.FooterLocation.Y + FooterOffset);
}
}
How should we do this to get 600dpi to the printed page? (Or even 300 dpi would be great!)
When we print this, it looks far better when printed from a 120dpi machine than when printed from a 96dpi machine, hence why we know it is printing at screen resolution. But also makes us wonder if there’s some simple way to tell it “this Form should draw at 600 dpi” and then all the rest of the code above just works.
Note: if we grab an EMF (Enhanced Metafile) and print it to the printer in the code above, that EMF comes out at 600dpi. Unfortunately, we haven’t found a DrawToEMF method that we can call on the FlowLayoutPanel instead of DrawToBitmap. Changing the Bitmap to be 600dpi doesn’t help… the DrawToBitmap method still seems to draw the bitmap at screen resolution.
Thanks!!
Okay, I have figured this out... and it works great!
I still don’t know how to create a form at 300dpi and use the auto-scaling functionality.
BUT…
I have proven that if you create the window 3.125x larger than needed at 96 dpi, and scale the font up 3.125x, and so on, such that everything is the pixel count you’d need to be at 300dpi, even though your screen is at 96dpi, then you can use the normal Control.DrawToBitmap() functionality to turn that into a Bitmap, and then you can use the GDI Graphics.DrawImage(thatGiantBitmap, giantSrcRect, pageSizeDestRect) to the printer Graphics object, and it will map those giant 96dpi pixels to the page-size 300dpi pixels, giving you a 300dpi print. Perfect.
For any of our windows that support resizing AND let our users zoom the contents arbitrarily, then printing What-You-See-Is-What-You-Get is easy:
In OnBeginPrint of your PrintDocument, do:
(1) Optionally dup the Form so as to not mess with what the user is looking at
(2) Move the Form you want to print off-screen (below and right of all your screens)
(3) Set the Form so that it is allowed to grow bigger than the screen size (by default WinForms won’t grow larger than the screen)
(4) Divide 300 dpi by your screen dpi to get the growth factor
(5) Grow your Form by growth factor
(6) Zoom its contents by growth factor (if they don’t auto-scale / auto-zoom with the Form’s size)
In OnPrintPage of your PrintDocument, do:
(7) On whatever Control in your Form you want to print, do DrawToBitmap() to a Bitmap the size of that Control
(8) On the e.Graphics do DrawImage(thatGiantBitmap, giantSrcRect, pageSizeDestRect)
That DrawImage call will draw at the printer’s resolution, if you have that many pixels in your thatGiantBitmap. In this case, I computed the Bitmap to give the number of pixels needed for 300 dpi, so I will get 300 dpi print-outs even if the printer is 600 dpi. If you need full 600 dpi, just use 600 dpi in step 4's calculation.
Total guess here, but what about using CSS? I'm guessing at how much you want to scale, and don't know how you would know what scale the printer is.
Using the print media query makes this work for print but leaves the screen view alone.
#media print {
* {
transform: scale(2000px,2000px);
}
}
Just recently I switched my Windows to 125% DPI scaling mode and found out that my app does things that I don't want it to do.
One of the problems is scaling of the image I am showing in the app. Let's assume it can be any image and for this example let it be a 600-pixel wide one. I want it to stay just like that. I'm ok with scaling the rest of the interface but this one particular Image control has to be exactly as wide as the Image is, and not scaled up to 125% (to 750 pixels). How can I achieve that? Should I change control size after some DPI-related event fires?
Also, the second question is, why WPF is lying to me? When I get ActualWidth it tells me 600 pixels, but Paintbrush isn't lying - I hope - and it shows 750 pixels when I measure width on the screenshot. Should I be checking this value after any particular event so that I get an actual ActualWidth?
Some code:
imgNav.Source = ImageManager.LoadBitmapImage(uri);
BitmapImage bimp = imgNav.Source;
imgNav.Height = bimp.PixelHeight;
imgNav.Width = bimp.PixelWidth;
...
txtFoo.Text = imgNav.ActualWidth.ToString();
Thank you in advance for any suggestions.
I need to know whether there is any advantages or disadvantages on using 2d graphics in wpf over 2d images or not?
I mean if I'm going to display an ellipse in a wpf window which one is more useful and why? To create an Ellipse object or to create an Image control and then load an pre-designed ellipse image into it?
Does using 2D graphics have any advantages in the sense that they consume less memory or increase performance or anything like that?
Thanks in advance.
An Image is not realy good scalable.
An vector Graphics is.
On a high dpi display your Image with Width = 300 (Units not pixels) and Height = 300 (units) is on a Display with 96 dpi not bigger than a Display with 144dpi. But the Image on the 144 dpi display needs more Pixels for the same size.
1 Unit is 1/96 inch.
So it is better to have a scalable "image" than a fixed one.
Its one of the features of wpf that winform don't have! and the reason why you should not use Pixels as a Size / Width / Height / Position / etc.
Excursus:
In winform it is hard to programm a scalable programm. on high dpi monitors the font, buttons etc. looks very small. there was no option to solve this problem.
so windows programmed an algorithm that creates a bitmap of the programm and scales this up.
So: the progamms width and height is the same as before, but the user sees a much bigger one (the Bitmap). The user input is then recalculated on the real sized application; Everything is working fine - and looking fine.
Hope that helps and is correct.
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)