I'm working with code in a WPF application that needs to figure out the DPI scaling size for each monitor in Windows. I'm able to figure out the DPI of the primary screen but for some reason I cannot figure out how to get the scale for other monitors - the others all return the same DPI as the main monitor.
There's a bit of code to do this so bear with me. The first set of code deals with getting the DPI based on an HWND. The code gets the active monitor and then retrieves the DPI settings and compares figures out a ratio to the 96 DPI (typically 100%).
public static decimal GetDpiRatio(Window window)
{
var dpi = WindowUtilities.GetDpi(window, DpiType.Effective);
decimal ratio = 1;
if (dpi > 96)
ratio = (decimal)dpi / 96M;
return ratio;
}
public static decimal GetDpiRatio(IntPtr hwnd)
{
var dpi = GetDpi(hwnd, DpiType.Effective);
decimal ratio = 1;
if (dpi > 96)
ratio = (decimal)dpi / 96M;
//Debug.WriteLine($"Scale: {factor} {ratio}");
return ratio;
}
public static uint GetDpi(IntPtr hwnd, DpiType dpiType)
{
var screen = Screen.FromHandle(hwnd);
var pnt = new Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
var mon = MonitorFromPoint(pnt, 2 /*MONITOR_DEFAULTTONEAREST*/);
Debug.WriteLine("monitor handle: " + mon);
try
{
uint dpiX, dpiY;
GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
return dpiX;
}
catch
{
// fallback for Windows 7 and older - not 100% reliable
Graphics graphics = Graphics.FromHwnd(hwnd);
float dpiXX = graphics.DpiX;
return Convert.ToUInt32(dpiXX);
}
}
public static uint GetDpi(Window window, DpiType dpiType)
{
var hwnd = new WindowInteropHelper(window).Handle;
return GetDpi(hwnd, dpiType);
}
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
public enum DpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
This code is used as part of a screen capture solution where there's supposed to be an overlay over the window the user's mouse is over. I capture the mouse position and based on that I get a pixel location and I then create the WPF window there. Here I have to apply the DPI ratio in order to get the Window to render in the right place and size.
This all works fine on the primary monitor or on multiple monitors as long as the DPI is the same.
The problem is that the call to GetDpiForMonitor() always returns the primary monitor DPI even though the HMONITOR value passed to it is different.
DPI Awareness
This is a WPF application so the app is DPI aware, but WPF runs in System DPI Awareness, rather than Per Monitor DPI Aware. To that effect I hooked up static App() code on startup to explicitly set to per monitor DPI:
try
{
// for this to work make sure [assembly:dpiawareness
PROCESS_DPI_AWARENESS awareness;
GetProcessDpiAwareness(Process.GetCurrentProcess().Handle, out awareness);
var result = SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware);
GetProcessDpiAwareness(Process.GetCurrentProcess().Handle, out awareness);
}
[DllImport("SHCore.dll", SetLastError = true)]
public static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);
[DllImport("SHCore.dll", SetLastError = true)]
public static extern void GetProcessDpiAwareness(IntPtr hprocess, out PROCESS_DPI_AWARENESS awareness);
public enum PROCESS_DPI_AWARENESS
{
Process_DPI_Unaware = 0,
Process_System_DPI_Aware = 1,
Process_Per_Monitor_DPI_Aware = 2
}
// and in assemblyinfo
[assembly: DisableDpiAwareness]
I see that the DPI setting changes to Process_Per_Monitor_DPI_Aware but that also seems to have no effect on the behavior. I still see the DPI results returned as the same as the main monitor.
There's a test in a largish solution that allows playing with this here:
https://github.com/RickStrahl/MarkdownMonster/blob/master/Tests/ScreenCaptureAddin.Test/DpiDetectionTests.cs in case anyone is interested in checking this out.
Any ideas how I can reliably get the DPI Scaling level for all monitors on the system (and why the heck is there no system API or even a WMI setting for this)?
WPF has per-monitor DPI support since .NET Framework 4.6.2. There is more information and an example available at GitHub: http://github.com/Microsoft/WPF-Samples/tree/master/PerMonitorDPI.
You may also want to check out the VisualTreeHelper.GetDpi method.
I have been wrestling with similar problems (secondary monitor Screen bounds seemed to be scaled by the same scale factor as set on the primary display), and I found some documentation that seems to at least explain that this is expected behavior:
DPI Awareness Mode - System
Windows Version Introduced - Vista
Application's view of DPI - All displays have the same DPI
(the DPI of the primary display at the time the Windows session was
started)
Behavior on DPI change - Bitmap-stretching (blurry)
This is extracted from the first table in High DPI desktop application development on Windows
That's the first documentation I found that at least explicitly spells out that the code will report that all windows share the same scaling when the application is under System DPI Awareness.
Related
on certain machines that vary in configuration (OS, graphics card and memory) I get an OutOfMemory exception. Some tests showed that there is no significant increase in virtual memory consumed. That's the piece of code where the exception is raised:
public override Size GetPreferredSize(Size proposedSize)
{
try
{
using (Graphics g = this.CreateGraphics())
{
SizeF measured = g.MeasureString(this.Text, this.Font); // <= OutOfMemoryException
measured += new SizeF(1, 1);
return measured.ToSize();
}
}
catch (OutOfMemoryException oom)
{
System.Diagnostics.Trace.WriteLine(oom.ToString());
}
return proposedSize;
}
The class is derived directly from label.
CreateGraphics() makes a call to the GDI+ function GdipCreateFromHWND which could in some cases return a status (3) that raises the OutOfMemoryException I face:
[EditorBrowsable(EditorBrowsableState.Advanced), SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
public static Graphics FromHwndInternal(IntPtr hwnd)
{
IntPtr zero = IntPtr.Zero;
int status = SafeNativeMethods.Gdip.GdipCreateFromHWND(new HandleRef(null, hwnd), out zero);
if (status != 0)
{
throw SafeNativeMethods.Gdip.StatusException(status); // status = 3 throws an OutOfMemoryException with text "Out of memory"
}
return new Graphics(zero);
}
But unfortunately I have not found documentation on the function and cases when it returns Out Of Memory.
The issue is at least repeatable on one customers machine very fast. All he has to do is click on a button that creates a new window where one of this derived Label is placed and which is used to display content in a WebBrowser control.
If you have any ideas that could help me find reasons of the exception it would be great!
Cheers,
Michael
The un-solution: Don't use Graphics. If your application runs SetCompatibleTextRenderingDefault(false) at startup (which it should, because it yields better text rendering), you should use TextRenderer.MeasureText instead of MeasureString, because otherwise you will use GDI+ for measuring and GDI for drawing which will create discrepancies between actual rendering and measurement.
Another solution could be not caching the Graphics object but caching the size. Since you are not using proposedSize anyway, simply measure the text whenever the Text or Font property change and return that cached value in GetPreferredSize.
This might work:
private Graphics _graphics;
protected override void OnPaint(PaintEventArgs e)
{
_graphics = e.Graphics;
base.OnPaint(e);
}
public override Size GetPreferredSize(Size proposedSize)
{
try
{
SizeF measured = _graphics.MeasureString(this.Text, this.Font);
measured += new SizeF(1, 1);
return measured.ToSize();
}
catch (OutOfMemoryException oom)
{
System.Diagnostics.Trace.WriteLine(oom.ToString());
}
return proposedSize;
}
The documentation at http://msdn.microsoft.com/en-us/library/system.windows.forms.control.creategraphics.aspx states "you cannot cache the Graphics object for reuse, except to use non-visual methods like Graphics.MeasureString", which is exactly what you are trying to do. This will fail if GetPreferredSize is called before the control is painted, and you'll want to Dispose the Graphics object, but maybe this will help get you closer to a viable solution.
I use Remote Desktop to connect from a laptop with Windows XP Professional SP3 and one screen to a remote PC running Windows 7 Professional with two monitors.
The laptop resolution is around 1024x768 and each monitor on the remote PC is around 1600x900.
Before I start the Remote Desktop session, I move all windows on the second monitor of the Windows 7 PC to the first monitor. (Both laptop and PC are in the same office area.)
The Remote Desktop session works, but after closing the session on the laptop and returning to work on the remote Windows 7 PC, I usually have to relocate and resize many of the windows to get back to the original arrangement.
With my current configuration, how can I avoid the "relocate and resize" step above?
If the laptop had Windows 7 Professional, would that help solve this problem?
You should probably move this to superuser, but since you asked on StackOverflow, you could implement a program that does what you describe.
In pseudocode:
class WindowPosition {
IntPtr hWnd;
RECT Location;
}
List<WindowPosition> positions = null;
void OnCaptureWindowPositionHotkey() {
positions = new List<WindowPosition>();
EnumWindows(enumStoreWindows, null);
}
void OnResetWindowPositionHotkey() {
EnumWindows(enumStoreWindows, null);
}
void enumSetWindows(IntPtr hwnd, IntPtr obj) {
positions.Add(new WindowPosition() {
hWnd = hwnd,
Location = GetWndLocation(hwnd)
};
}
RECT GetWndLocation(IntPtr hwnd) {
RECT outrect = null;
GetWindowRect(hwnd, out outrect);
return outrect;
}
void enumSetWindows(IntPtr hwnd, IntPtr obj) {
var loc = (from wl in positions
where wl.hWnd == hwnd
select wl).FirstOrDefault();
if (loc == null) return;
SetWindowPos(hwnd, null,
loc.Location.X,
loc.Location.Y,
loc.Location.Width,
loc.Location.Height,
0);
}
where EnumWindows, SetWindowPos and GetWindowRect are all Win32 functions. See:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms633497(v=vs.85).aspx, http://msdn.microsoft.com/en-us/library/windows/desktop/ms633545(v=vs.85).aspx, and http://msdn.microsoft.com/en-us/library/windows/desktop/ms633519(v=vs.85).aspx.
How can a WPF app know if its getting remotely operated (via VNC or remote desktop)?
In winforms there is System.Windows.Forms.SystemInformation.TerminalServerSession as per Detecting remote desktop connection but is there a strightforward way for this in WPF?
I guess the hack for now could be to have an invisible Winforms host on WPF and use its own capacity to host dummy win form that can identify the same... but that looks lame to me!
Any inputs would be appreciated!
Thx
I guess the hack for now could be to have an invisible Winforms host on WPF and use its own capacity to host dummy win form that can identify the same... but that looks lame to me!
You don't need an invisible WinForms host... you can just add a reference to the System.Windows.Forms assembly, and use the SystemInformation.TerminalServerSession static property.
If you don't want a dependency on WinForms, you can use the GetSystemMetrics Win32 API:
const int SM_REMOTESESSION = 0x1000;
[DllImport("user32")]
static extern int GetSystemMetrics(int nIndex);
public static bool IsTerminalServerSession()
{
return (GetSystemMetrics(SM_REMOTESESSION) & 1) != 0;
}
This method does not require a Windows Forms Window, only a reference to the DLL.
If you don't want to reference this, you can call the method to check this yourself, the implementation is as follows (I've wrapped it in a class):
static class SystemInformation
{
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern int GetSystemMetrics(int nIndex);
public static bool IsTerminalServerSession
{
get
{
//copied the Windows Forms implementation
return (GetSystemMetrics(0x1000) & 1) != 0;
}
}
}
I have a custom windows implementation in a WPF app that hooks WM_GETMINMAXINFO as follows:
private void MaximiseWithTaskbar(System.IntPtr hwnd, System.IntPtr lParam)
{
MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor != System.IntPtr.Zero)
{
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo(monitor, monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
mmi.ptMinTrackSize.x = Convert.ToInt16(this.MinWidth * (desktopDpiX / 96));
mmi.ptMinTrackSize.y = Convert.ToInt16(this.MinHeight * (desktopDpiY / 96));
}
Marshal.StructureToPtr(mmi, lParam, true);
}
It all works a treat and it allows me to have a borderless window maximized without having it sit on to of the task bar, which is great, but it really doesn't like being moved between monitors with the new Win7 keyboard shortcuts.
Whenever the app is moved with Win+Shift+Left/Right the WM_GETMINMAXINFO message is received, as I'd expect, but MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) returns the monitor the application has just been moved FROM, rather than the monitor it is moving TO, so if the monitors are of differing resolutions the window end up the wrong size.
I'm not sure if there's something else I can call, other then MonitorFromWindow, or whether there's a "moving monitors" message I can hook prior to WM_GETMINMAXINFO. I'm assuming there is a way to do it because "normal" windows work just fine.
According to the MSDN page the WM_GETMINMAXINFO is sent:
"when the size or position of the window is about to change"
which explains why your call to MonitorFromWindow returns the previous monitor.
How about using the WM_WINDOWPOSCHANGED message? It's sent after every time the window is moved. You can get the HWND from the WINDOWPOS structure and use that in a MonitorFromWindow call to get the monitor. When a subsequent WM_GETMINMAXINFO is sent you can use that as you do now.
Is there a way to read/write the cookies that a WebBrowser control uses?
I am doing something like this...
string resultHtml;
HttpWebRequest request = CreateMyHttpWebRequest(); // fills http headers and stuff
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
resultHtml = sr.ReadToEnd();
}
WebBrowser browser = new WebBrowser();
browser.CookieContainer = request.CookieContainer; // i wish i could do this :(
browser.NavigateToString(resultHtml);
One of the potentially confusing things about the WebBrowser control and cookies is that at a first glance, it often looks like your app gets a separate cookie store. For example, if you log into a site that stores a persistent cookie to identify you, then whether you appear to be logged in for that site from inside an app hosting the control will be independent of whether you seem to be logged in via Internet Explorer.
In fact, you can even be logged in with different identities.
However, although it might be natural to draw the conclusion that each app hosting the WebBrowser therefore gets its own cookies, in fact that's not true. There are merely two sets of cookies: the ones used in 'low integrity' mode (which is what IE runs in by default), and the other set, which is what you'll get in a normal app that hosts the WebBrowser and also what you'll get if you run IE elevated.
the webbrowser control uses WinInet for networking, specifically use the InternetSetCookie(Ex) and InternetGetCookie(Ex) functions for Cookie management. There isn't a WinInet wrapper in .Net, but you can p-invoke.
Yes you are right, InternetGetCookieEx is the only way to retrieve HttpOnly cookies and it is the preferred way to grab cookie from WebBrowser control.
I posted a complete example here
You can use Application.GetCookie and Application.SetCookie methods.
Although Application is more or less related to WPF, you can use these methods in any desktop .NET code. In fact, they are wrappers on InternetGetCookieEx and InternetSetCookieEx Windows APIs.
I faced the same issue few days ago.
Besides the examples of the previous answers, here is a Win32 wrapper for the WebBrowser control. The advantage of this implementation is that it exposes more options that the default WebBrowser control.
Unfortunately if It's not WPF native, so you will have to create a wrapper if you're planning to use it in WPF.
http://code.google.com/p/csexwb2/
Here is sample from [link][1]
> public static class WinInetHelper
{
public static bool SupressCookiePersist()
{
// 3 = INTERNET_SUPPRESS_COOKIE_PERSIST
// 81 = INTERNET_OPTION_SUPPRESS_BEHAVIOR
return SetOption(81, 3);
}
public static bool EndBrowserSession()
{
// 42 = INTERNET_OPTION_END_BROWSER_SESSION
return SetOption(42, null);
}
static bool SetOption(int settingCode, int? option)
{
IntPtr optionPtr = IntPtr.Zero;
int size = 0;
if (option.HasValue)
{
size = sizeof(int);
optionPtr = Marshal.AllocCoTaskMem(size);
Marshal.WriteInt32(optionPtr, option.Value);
}
bool success = InternetSetOption(0, settingCode, optionPtr, size);
if (optionPtr != IntPtr.Zero) Marshal.Release(optionPtr);
return success;
}
[System.Runtime.InteropServices.DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool InternetSetOption(
int hInternet,
int dwOption,
IntPtr lpBuffer,
int dwBufferLength
);
}