How can I find the position of a maximized window? - wpf

I need to know the position of a window that is maximized.
WPF Window has Top and Left properties that specifies the window's location. However, if you maximize the window these properties keep the values of the window in it's normal state.
If you´re running on a single-screen setup, the maximized position is naturally (0,0). However, if you have multiple screens that is not necessarily true. The window will only have position (0,0) if you have it maximized on the main screen.
So... is there any way to find out the position of a maximized window (preferably in the same logical units as the Top and Left properties)?

Here's the solution I came up with based on previous discussion here (thanks!).
This solution...
returns the position of a window in its current state
handles all window states (maximized, minimized, restored)
does not depend on Windows Forms (but is inspired by it)
uses the window handle to reliably determine the correct monitor
The main method GetAbsolutePosition is implemented here as an extension method. If you have a Window called myWindow, call it like this:
Point p = myWindow.GetAbsolutePosition();
Here's the complete code:
using System;
using System.Windows;
using System.Windows.Interop;
using System.Runtime.InteropServices;
static class OSInterop
{
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int smIndex);
public const int SM_CMONITORS = 80;
[DllImport("user32.dll")]
public static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out] MONITORINFOEX info);
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromWindow(HandleRef handle, int flags);
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public int width { get { return right - left; } }
public int height { get { return bottom - top; } }
}
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
public class MONITORINFOEX
{
public int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));
public RECT rcMonitor = new RECT();
public RECT rcWork = new RECT();
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] szDevice = new char[32];
public int dwFlags;
}
}
static class WPFExtensionMethods
{
public static Point GetAbsolutePosition(this Window w)
{
if (w.WindowState != WindowState.Maximized)
return new Point(w.Left, w.Top);
Int32Rect r;
bool multimonSupported = OSInterop.GetSystemMetrics(OSInterop.SM_CMONITORS) != 0;
if (!multimonSupported)
{
OSInterop.RECT rc = new OSInterop.RECT();
OSInterop.SystemParametersInfo(48, 0, ref rc, 0);
r = new Int32Rect(rc.left, rc.top, rc.width, rc.height);
}
else
{
WindowInteropHelper helper = new WindowInteropHelper(w);
IntPtr hmonitor = OSInterop.MonitorFromWindow(new HandleRef((object)null, helper.EnsureHandle()), 2);
OSInterop.MONITORINFOEX info = new OSInterop.MONITORINFOEX();
OSInterop.GetMonitorInfo(new HandleRef((object)null, hmonitor), info);
r = new Int32Rect(info.rcWork.left, info.rcWork.top, info.rcWork.width, info.rcWork.height);
}
return new Point(r.X, r.Y);
}
}

public static System.Drawing.Rectangle GetWindowRectangle(this Window w)
{
if (w.WindowState == WindowState.Maximized) {
var handle = new System.Windows.Interop.WindowInteropHelper(w).Handle;
var screen = System.Windows.Forms.Screen.FromHandle(handle);
return screen.WorkingArea;
}
else {
return new System.Drawing.Rectangle(
(int)w.Left, (int)w.Top,
(int)w.ActualWidth, (int)w.ActualHeight);
}
}

I finally found a solution working for me:
private System.Drawing.Rectangle getWindowRectangle()
{
System.Drawing.Rectangle windowRectangle;
if (this.WindowState == System.Windows.WindowState.Maximized)
{
/* Here is the magic:
* Use Winforms code to find the Available space on the
* screen that contained the window
* just before it was maximized
* (Left, Top have their values from Normal WindowState)
*/
windowRectangle = System.Windows.Forms.Screen.GetWorkingArea(
new System.Drawing.Point((int)this.Left, (int)this.Top));
}
else
{
windowRectangle = new System.Drawing.Rectangle(
(int)this.Left, (int)this.Top,
(int)this.ActualWidth, (int)this.ActualHeight);
}
return windowRectangle;
}

Universal one line answer working for all monitors and all high DPI variants:
Point leftTop = this.PointToScreen(new Point(0, 0));
This for example returns (-8, -8) for a window which is maximized on a 1920 wide screen whose ActualWidth returns 1936.
Just a rant to the other answers: NEVER mix up logical 96 dpi WPF pixels (using double) with native real pixels (using int) - especially by just casting double to int!

I realize that you are working in WPF, and this answer makes use of Forms technology, but it should work without much difficulty.
You can get a collection of the screens through My.Settings.Screens.AllScreens. From there you can access the resolution that the screen is currently working on.
Since WPF windows retain the Top/Left values that they had when they were maximized, then you can determine which screen they are on by figuring out which screen that Top/Left coordinates refer to, and then get the top/left coordinate for that screen.
Unfortunately, I am on the road, and can't test this at the moment. If you do implement, I would love to see what you come up with.

This seems to be a problem with System.Windows.Window!!!
Maximized window give unreliable values for Left, Width, ActualWidth, Top, Height and ActualHeight.
After maximizing a window, it can often keep the Left and Width values from the pre-maximized window.
For others reading - there is no problem when the window is un-maximized.
Also, what I find odd is that the values you read are in WPF DPI coordinates, [i.e. 1936x1096, from (-8, -8) to (1928, 1088)], but when you set these values you have to use screen pixel coordinates, [i.e. 1920x1080, using (0,0) etc...]
#tgr provided a reliable partial solution above, which I've improved below:
Fixing intellisense for extension methods by moving helper class to sub class
creating GetAbsoluteRect() method to provide Width/Height and point all in one call
refactoring common code
Here's the C# solution:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
public static partial class Extensions
{
static class OSInterop
{
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int smIndex);
public const int SM_CMONITORS = 80;
[DllImport("user32.dll")]
public static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out] MONITORINFOEX info);
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromWindow(HandleRef handle, int flags);
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public int width { get { return right - left; } }
public int height { get { return bottom - top; } }
}
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
public class MONITORINFOEX
{
public int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));
public RECT rcMonitor = new RECT();
public RECT rcWork = new RECT();
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] szDevice = new char[32];
public int dwFlags;
}
}
static Int32Rect _getOsInteropRect(Window w)
{
bool multimonSupported = OSInterop.GetSystemMetrics(OSInterop.SM_CMONITORS) != 0;
if (!multimonSupported)
{
OSInterop.RECT rc = new OSInterop.RECT();
OSInterop.SystemParametersInfo(48, 0, ref rc, 0);
return new Int32Rect(rc.left, rc.top, rc.width, rc.height);
}
WindowInteropHelper helper = new WindowInteropHelper(w);
IntPtr hmonitor = OSInterop.MonitorFromWindow(new HandleRef((object)null, helper.EnsureHandle()), 2);
OSInterop.MONITORINFOEX info = new OSInterop.MONITORINFOEX();
OSInterop.GetMonitorInfo(new HandleRef((object)null, hmonitor), info);
return new Int32Rect(info.rcWork.left, info.rcWork.top, info.rcWork.width, info.rcWork.height);
}
public static Rect GetAbsoluteRect(this Window w)
{
if (w.WindowState != WindowState.Maximized)
return new Rect(w.Left, w.Top, w.ActualWidth, w.ActualHeight);
var r = _getOsInteropRect(w);
return new Rect(r.X, r.Y, r.Width, r.Height);
}
public static Point GetAbsolutePosition(this Window w)
{
if (w.WindowState != WindowState.Maximized)
return new Point(w.Left, w.Top);
var r = _getOsInteropRect(w);
return new Point(r.X, r.Y);
}
}

I haven't found a solution to your problem, but if you need to position the window just to create a new one you can do the following:
...
Window windowNew = new Window();
ConfigureWindow(this, windowNew);
Window.Show();
...
static public void ConfigureWindow(Window windowOld, Window windowNew)
{
windowNew.Height = windowOld.ActualHeight;
windowNew.Width = windowOld.ActualWidth;
if (windowOld.WindowState == WindowState.Maximized)
{
windowNew.WindowState = WindowState.Maximized;
}
else
{
windowNew.Top = windowOld.Top;
windowNew.Left = windowOld.Left;
}
}

Related

Mfc invokes onsize event of embedded wpf control

I embedded a wpf control to an mfc app using Hwndsource and c++cli technology. SiteToContent was setted to WidthAndHeight.
A wrong resize occures on different computers. When mfc control is created, wpf is oversized a lot.
c++cli.h
#pragma once
#if _MANAGED
ref class ControlManager
{
public:
ControlManager() { }
~ControlManager() {}
Sharp::Control^ Current;
System::Windows::Interop::HwndSource^ HwndSource;
void Init(LPCREATESTRUCT lp, HANDLE Hwdn);
void SetSize(int w, int h);
};
#endif
class AFX_EXT_CLASS CXbimWnd final
{
public:
int OnCreate(LPCREATESTRUCT lpCreateStruct, HANDLE hwnd);
void OnSize(UINT nId, int h, int w);
private:
#ifdef _MANAGED
// current 3d view
gcroot<ControlManager^ > m_ObjHandle;
#else
intptr_t m_ObjHandle;
intptr_t HwndSource;
#endif
};
c++cli.cpp
int CXbimWnd::OnCreate(LPCREATESTRUCT lp, HANDLE hwnd)
{
m_ObjHandle = gcnew ControlManager(m_Log);
m_ObjHandle->Init(lp, hwnd);
return 0;
}
void ControlManager::Init(LPCREATESTRUCT lp, HANDLE hwnd)
{
System::Windows::Interop::HwndSourceParameters sourceParams("XbimXplorer");
sourceParams.PositionX = lp->x;
sourceParams.PositionY = lp->y;
sourceParams.Height = lp->cy;
sourceParams.Width = lp->cx;
sourceParams.ParentWindow = (System::IntPtr)hwnd;
sourceParams.WindowStyle = WS_VISIBLE | WS_CHILD | WS_MAXIMIZE;
HwndSource = gcnew System::Windows::Interop::HwndSource(sourceParams);
HwndSource->SizeToContent = System::Windows::SizeToContent::WidthAndHeight;
Current = gcnew Sharp::Control();
HwndSource->RootVisual = Current;
}
void CXbimWnd::OnSize(UINT /*nId*/, int h, int w)
{
m_ObjHandle->SetSize(h, w);
}
void ControlManager::SetSize(int h, int w)
{
Current->Height = h;
Current->Width = w;
}
mfc.h
class CBIMXBimBar : public CBCGPDockingControlBar
{
public:
CBIMXBimBar();
void AdjustLayout();
virtual ~CBIMXBimBar();
// Generated message map functions
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnSize(UINT nType, int cx, int cy);
DECLARE_MESSAGE_MAP()
CXbimWnd xbim;
};
mfc.cpp
BEGIN_MESSAGE_MAP(CBIMXBimBar, CBCGPDockingControlBar)
ON_WM_CREATE()
ON_WM_SIZE()
END_MESSAGE_MAP()
int CBIMXBimBar::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CBCGPDockingControlBar::OnCreate(lpCreateStruct) == -1)
return -1;
CRect rectDummy;
rectDummy.SetRectEmpty();
auto result = xbim.OnCreate(lpCreateStruct, m_hWnd);
if (result == 0)
AdjustLayout();
return result;
}
void CBIMXBimBar::OnSize(UINT nType, int cx, int cy)
{
AdjustLayout();
}
void CBIMXBimBar::AdjustLayout()
{
CRect rectClient;
GetClientRect(rectClient);
auto scale = globalUtils.GetDPIScale();
xbim.OnSize(0, int(rectClient.Height()/scale), int(rectClient.Width()/scale)); //<<---
}
I rescaled x,y of client rect coordinates by DPI value. It works on my computer but dont on another one. The size of embedded control is twice oversized in mfc client rect view
So. My invertigation gave me some information. The hosted WPF control via HwndSource does not get OnDpiChange event. It is a bug or a feature. My MFC app does not allow catch WM_DPICHANGE event to throw one to WPF exactly. So I just set
var m = new HwndSource.CompositionTarget.TransformToDevice();
WpfControl.LayoutTransform = new System.Windows.Media.ScaleTransform(1 / m.M11, 1 / m.M22);

ArgOutOfRangeEx while calling ListViews base.WndProc when click occurs outside subitems, C# 10.0

I am receiving a ArgumentOutOfRangeException while calling base.WndProc in an OwnerDrawn listview.
The exception occurs after a click is performed to the right (empty space) of the last subitem of any ListViewItem.
The listview column widths are programmatically set to fill the entire listview, however on occasion data is not received as expected leading to some extra space, this is an edge case which usually does not occur.
The message to be processed at the time of exception is WM_LBUTTONDOWN (0x0201), or WM_RBUTTONDOWN (0x0204).
All data for painting the LV subitems is from a class referenced by the tag of the LVI, at no point do I try to read the subitems, nor do I write data to any of the subitems.
Below is the smallest reproducible code, in C# (10.0), using .NET 6.0 that shows the exception.
I attempted to simply exclude processing of WM_LBUTTONDOWN. This did remove the exception, however it also stopped left click events from reaching MouseUp which is required. (also for right clicks)
Whilst I am working to fix my errors in column sizing after receiving bad or no data, I would like to check for this possible exception and simply return from the method before the exception can occur.
Data class
namespace testCase;
class myClass
{
public string s1 = "subitem1";
public string s2 = "subitem2";
}
Form code
namespace testCase;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ListViewExWatch lv = new();
Controls.Add(lv);
lv.MouseUp += Lv_MouseUp;
lv.Top = 0;
lv.Left = 0;
lv.Width = ClientSize.Width;
lv.Height = ClientSize.Height;
lv.OwnerDraw = true;
lv.BackColor = Color.AntiqueWhite;
lv.Columns.Add("Row", 50);
lv.Columns.Add("sub1", 50);
lv.Columns.Add("sub2", 50);
for(int i = 0; i < 10; i++)
{
ListViewItem lvi = new(){ Text = "Row " + i, Tag = new myClass() };
lvi.SubItems.Add("");
lvi.SubItems.Add("");
lv.Items.Add(lvi);
}
}
private void Lv_MouseUp(object? sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
MessageBox.Show("Left click - doing action A");
}
if (e.Button == MouseButtons.Right)
{
MessageBox.Show("Right click - Creating context menu");
}
}
}
Custom ListView override
using System.Runtime.InteropServices;
namespace testCase;
public class ListViewExWatch : ListView
{
#region Windows API
[StructLayout(LayoutKind.Sequential)]
struct DRAWITEMSTRUCT
{
public int ctlType;
public int ctlID;
public int itemID;
public int itemAction;
public int itemState;
public IntPtr hWndItem;
public IntPtr hDC;
public int rcLeft;
public int rcTop;
public int rcRight;
public int rcBottom;
public IntPtr itemData;
}
const int LVS_OWNERDRAWFIXED = 0x0400;
const int WM_SHOWWINDOW = 0x0018;
const int WM_DRAWITEM = 0x002B;
const int WM_MEASUREITEM = 0x002C;
const int WM_REFLECT = 0x2000;
const int WM_LBUTTONDOWN = 0x0201;
#endregion
public ListViewExWatch()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
protected override CreateParams CreateParams
{
get
{
CreateParams k_Params = base.CreateParams;
k_Params.Style |= LVS_OWNERDRAWFIXED;
return k_Params;
}
}
protected override void WndProc(ref Message k_Msg)
{
//if (k_Msg.Msg == WM_LBUTTONDOWN) return;
base.WndProc(ref k_Msg); // Exception: System.ArgumentOutOfRangeException: 'InvalidArgument=Value of '-1' is not valid for 'index'.
// Only occurs when clicking to the right of the last subItem
switch (k_Msg.Msg)
{
case WM_SHOWWINDOW:
View = View.Details;
OwnerDraw = false;
break;
case WM_REFLECT + WM_MEASUREITEM:
Marshal.WriteInt32(k_Msg.LParam + 4 * sizeof(int), 14);
k_Msg.Result = (IntPtr)1;
break;
case WM_REFLECT + WM_DRAWITEM:
{
object? lParam = k_Msg.GetLParam(typeof(DRAWITEMSTRUCT));
if (lParam is null) throw new Exception("lParam shouldn't be null");
DRAWITEMSTRUCT k_Draw = (DRAWITEMSTRUCT)lParam;
using Graphics gfx = Graphics.FromHdc(k_Draw.hDC);
ListViewItem lvi = Items[k_Draw.itemID];
myClass wi = (myClass)lvi.Tag;
TextRenderer.DrawText(gfx, lvi.Text, Font, lvi.SubItems[0].Bounds, Color.Black, TextFormatFlags.Left);
TextRenderer.DrawText(gfx, wi.s1, Font, lvi.SubItems[1].Bounds, Color.Black, TextFormatFlags.Left);
TextRenderer.DrawText(gfx, wi.s2, Font, lvi.SubItems[2].Bounds, Color.Black, TextFormatFlags.Left);
break;
}
}
}
}
The error message
System.ArgumentOutOfRangeException
HResult=0x80131502
Message=InvalidArgument=Value of '-1' is not valid for 'index'. Arg_ParamName_Name
ArgumentOutOfRange_ActualValue
Source=System.Windows.Forms
StackTrace:
at System.Windows.Forms.ListViewItem.ListViewSubItemCollection.get_Item(Int32 index)
at System.Windows.Forms.ListView.HitTest(Int32 x, Int32 y)
at System.Windows.Forms.ListView.ListViewAccessibleObject.HitTest(Int32 x, Int32 y)
at System.Windows.Forms.ListView.WmMouseDown(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.ListView.WndProc(Message& m)
at testCase.ListViewExWatch.WndProc(Message& k_Msg) in C:\Users\XXXX\source\repos\testCase\ListViewExWatch.cs:line 50
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, WM msg, IntPtr wparam, IntPtr lparam)
From the error it is apparent that the base listview code it is trying to get a subitem that doesn't exist because the click is not on a subitem, hence the -1 for index within the error message.
At the time of exception there is no member of k_Msg that contains the index (-1), so I cannot check for that and simply return.
I could surround the call to base.WndProc in a try catch, since it is an edge case. I've always had the mindset to check for possible exceptions and prevent them whenever possible rather than catch them. But this one has me stumped.
Am I being too pedantic in this regard?
Most likely I am missing something basic here, could you fine folks please point me in the right direction?
I had implemented the following code to catch only this specific exception and still allow any other to bubble up as desired.
try
{
base.WndProc(ref k_Msg); // This throws a ArgOutOfRangeEx when a click is performed to the right of any subitem (empty space)
}
catch (ArgumentOutOfRangeException ex) when (ex.ParamName == "index" && (int?)ex.ActualValue == -1 && ex.TargetSite?.DeclaringType?.Name == "ListViewSubItemCollection")
{
Program.Log(LogLevel.Normal, "ListViewExWatch.WndProc()", "ArgumentOutOfRangeException: A click has been perfored outside of a valid subitem. This has been handled and indicates column witdth calcs were wrong.");
return;
}
With further research however, I managed to specifically workaround the issue
From https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondown
lParam
The low-order word specifies the x-coordinate of the cursor...
The high-order word specifies the y-coordinate of the cursor...
I ended up modifying the lParam before processing the message. By moving the x-coordinate to inside the last subitem the exception is averted and clicks are processed as normal.
protected override void WndProc(ref Message k_Msg)
{
if (k_Msg.Msg == WM_LBUTTONDOWN || k_Msg.Msg == WM_RBUTTONDOWN)
{
// Get the x position of the end of the last subitem
int width = -1;
foreach (ColumnHeader col in Columns) width += col.Width;
// Where did the click occur?
int x_click = SignedLOWord(k_Msg.LParam);
int y_click = SignedHIWord(k_Msg.LParam);
// If the click is to the right of the last subitem, set the x-coordinate to inside the last subitem.
if (x_click > width) k_Msg.LParam = MakeLparam(width, y_click);
}
base.WndProc(ref k_Msg);
...
It is interesting to note the correct x and y coordinates are still reported in the Lv_MouseUp handler. This may not be the case for other handlers such as MouseDown which has obviously been modified, since I do not use them it has not presented a problem
Here are the helper functions used above (from referencesource.microsoft.com)
public static IntPtr MakeLparam(int low, int high)
{
return (IntPtr)((high << 16) | (low & 0xffff));
}
public static int SignedHIWord(IntPtr n)
{
return SignedHIWord(unchecked((int)(long)n));
}
public static int SignedLOWord(IntPtr n)
{
return SignedLOWord(unchecked((int)(long)n));
}
public static int SignedHIWord(int n)
{
return (short)((n >> 16) & 0xffff);
}
public static int SignedLOWord(int n)
{
return (short)(n & 0xFFFF);
}

WPF RenderTargetBitmap downscaling TextRenderMode to GreyScale

RenderTargetBitmap removes downgrades a RichtextBox's TextRenderingMode to GreyScale. So a captured PNG looks poor quality and doesnt match the WPF control
If I use WINDOWS ALT+PRINT SCREEN, the text is captured perfectly.
So how can I render the text control to the same quality as ALT+PRINT SCREEN.
Any advice would seriously be appreciated
All the best
You can use the same technique to render your window into the bitmap as windows using when taking a screen shot with Alt + Print Screen. The idea is to get a window handle and then render it to a bitmap using BitBlt system call.
Below is an example (you would need to reference the System.Drawing.dll assembly in your project to make it work):
public static void SaveToBitmapNative(Window window, FrameworkElement element, string fileName)
{
WindowInteropHelper helper = new WindowInteropHelper(window);
// detect the window client area position if rendering a child element
double incX = 0, incY = 0;
if (window != element)
{
System.Drawing.Point pos = new System.Drawing.Point(0, 0);
ClientToScreen(helper.Handle, ref pos);
incX = pos.X - (int)window.Left;
incY = pos.Y - (int)window.Top;
}
// transform child position to window coordinates
GeneralTransform transform = element.TransformToVisual(window);
Point point = transform.Transform(new Point(0, 0));
Rect rect = new Rect(point.X + incX, point.Y + incY, element.ActualWidth, element.ActualHeight);
// render window into bitmap
using (System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(
(int)rect.Width, (int)rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
using (System.Drawing.Graphics memoryGraphics = System.Drawing.Graphics.FromImage(bitmap))
{
IntPtr dc = memoryGraphics.GetHdc();
IntPtr windowDC = GetWindowDC(helper.Handle);
BitBlt(dc, 0, 0, (int)rect.Width, (int)rect.Height,
windowDC, (int)rect.Left, (int)rect.Top, TernaryRasterOperations.SRCCOPY);
memoryGraphics.ReleaseHdc(dc);
ReleaseDC(helper.Handle, windowDC);
}
// save bitmap to file
bitmap.Save(fileName);
}
}
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, TernaryRasterOperations rop);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr ptr);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("user32.dll")]
static extern bool ClientToScreen(IntPtr hWnd, ref System.Drawing.Point lpPoint);
public enum TernaryRasterOperations : uint
{
SRCCOPY = 0x00CC0020,
SRCPAINT = 0x00EE0086,
SRCAND = 0x008800C6,
SRCINVERT = 0x00660046,
SRCERASE = 0x00440328,
NOTSRCCOPY = 0x00330008,
NOTSRCERASE = 0x001100A6,
MERGECOPY = 0x00C000CA,
MERGEPAINT = 0x00BB0226,
PATCOPY = 0x00F00021,
PATPAINT = 0x00FB0A09,
PATINVERT = 0x005A0049,
DSTINVERT = 0x00550009,
BLACKNESS = 0x00000042,
WHITENESS = 0x00FF0062
}
here's how you can call this
private void saveButton_Click(object sender, RoutedEventArgs e)
{
// saves the entire window into the bitmap
SaveToBitmapNative(this, this, "c:\\test0.png");
// saves a child control (RichTextBox) into the bitmap
SaveToBitmapNative(this, richTextBox, "c:\\test1.png");
}
Note: this would not work for layered windows updated via UpdateLayeredWindow function.
hope this helps, regards

Winforms-How can I make MessageBox appear centered on MainForm?

Winforms-How can I make dialog boxes appear centered on MainForm? That is as opposed to be based on Normal windows default which renders them in the centre of the screen.
In my case I have a small main form that may for example be positioned in a corner, the the MessageBox popup is displayed what seems a ways away.
It is possible with some servings of P/Invoke and the magic provided by Control.BeginInvoke(). Add a new class to your project and paste this code:
using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class CenterWinDialog : IDisposable {
private int mTries = 0;
private Form mOwner;
public CenterWinDialog(Form owner) {
mOwner = owner;
owner.BeginInvoke(new MethodInvoker(findDialog));
}
private void findDialog() {
// Enumerate windows to find the message box
if (mTries < 0) return;
EnumThreadWndProc callback = new EnumThreadWndProc(checkWindow);
if (EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero)) {
if (++mTries < 10) mOwner.BeginInvoke(new MethodInvoker(findDialog));
}
}
private bool checkWindow(IntPtr hWnd, IntPtr lp) {
// Checks if <hWnd> is a dialog
StringBuilder sb = new StringBuilder(260);
GetClassName(hWnd, sb, sb.Capacity);
if (sb.ToString() != "#32770") return true;
// Got it
Rectangle frmRect = new Rectangle(mOwner.Location, mOwner.Size);
RECT dlgRect;
GetWindowRect(hWnd, out dlgRect);
MoveWindow(hWnd,
frmRect.Left + (frmRect.Width - dlgRect.Right + dlgRect.Left) / 2,
frmRect.Top + (frmRect.Height - dlgRect.Bottom + dlgRect.Top) / 2,
dlgRect.Right - dlgRect.Left,
dlgRect.Bottom - dlgRect.Top, true);
return false;
}
public void Dispose() {
mTries = -1;
}
// P/Invoke declarations
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT rc);
[DllImport("user32.dll")]
private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int w, int h, bool repaint);
private struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
}
Sample usage:
private void button1_Click(object sender, EventArgs e) {
using (new CenterWinDialog(this)) {
MessageBox.Show("Nobugz waz here");
}
}
Note that this code works for any of the Windows dialogs. MessageBox, OpenFormDialog, FolderBrowserDialog, PrintDialog, ColorDialog, FontDialog, PageSetupDialog, SaveFileDialog.
This is for Win32 API, written in C. Translate it as you need...
case WM_NOTIFY:{
HWND X=FindWindow("#32770",NULL);
if(GetParent(X)==H_frame){int Px,Py,Sx,Sy; RECT R1,R2;
GetWindowRect(hwnd,&R1); GetWindowRect(X,&R2);
Sx=R2.right-R2.left,Px=R1.left+(R1.right-R1.left)/2-Sx/2;
Sy=R2.bottom-R2.top,Py=R1.top+(R1.bottom-R1.top)/2-Sy/2;
MoveWindow(X,Px,Py,Sx,Sy,1);
}
} break;
Add that to the WndProc code... You can set position as you like, in this case it just centres over the main program window. It will do this for any messagebox, or file open/save dialog, and likely some other native controls. I'm not sure, but I think you may need to include COMMCTRL or COMMDLG to use this, at least, you will if you want open/save dialogs.
I experimented with looking at the notify codes and hwndFrom of NMHDR, then decided it was just as effective, and far easier, not to. If you really want to be very specific, tell FindWindow to look for a unique caption (title) you give to the window you want it to find.
This fires before the messagebox is drawn onscreen, so if you set a global flag to indicate when action is done by your code, and look for a unique caption, you be sure that actions you take will only occur once (there will likely be multiple notifiers). I haven't explored this in detail, but I managed get CreateWindow to put an edit box on a messagebox dialog/ It looked as out of place as a rat's ear grafted onto the spine of a cloned pig, but it works. Doing things this way may be far easier than having to roll your own.
Crow.
EDIT: Small correction to make sure that the right window is handled. Make sure that parent handles agree throughout, and this should work ok. It does for me, even with two instances of the same program...
Write your own messagebox. A form and a label should do it. Or do you also need to globalize it?
The class proved to be applicable to two other situations. I had a FolderBrowserDialog that I wanted to be larger, and I wanted it to come up near the top-left of the parent dialog (near the button I click to open it).
I copied the CenterWinDialog class and made two new classes. One class changes the dialog size, and the other changes its position to a specific offset from the parent form. This is the usage:
using (new OffsetWinDialog(this) { PreferredOffset = new Point(75, 75 )})
using (new SizeWinDialog(this) { PreferredSize = new Size(400, 600)})
{
DialogResult result = dlgFolderBrowser.ShowDialog();
if (result == DialogResult.Cancel)
return;
}
and these are the two classes that were based on the original one.
class OffsetWinDialog : IDisposable
{
private int mTries = 0;
private Form mOwner;
public OffsetWinDialog(Form owner)
{
mOwner = owner;
owner.BeginInvoke(new MethodInvoker(findDialog));
}
public Point PreferredOffset { get; set; }
private void findDialog()
{
// Enumerate windows to find the message box
if (mTries < 0)
return;
EnumThreadWndProc callback = new EnumThreadWndProc(checkWindow);
if (EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero))
{
if (++mTries < 10)
mOwner.BeginInvoke(new MethodInvoker(findDialog));
}
}
private bool checkWindow(IntPtr hWnd, IntPtr lp)
{
// Checks if <hWnd> is a dialog
StringBuilder sb = new StringBuilder(260);
GetClassName(hWnd, sb, sb.Capacity);
if (sb.ToString() != "#32770") return true;
// Got it
Rectangle frmRect = new Rectangle(mOwner.Location, mOwner.Size);
RECT dlgRect;
GetWindowRect(hWnd, out dlgRect);
MoveWindow(hWnd,
frmRect.Left + PreferredOffset.X,
frmRect.Top + PreferredOffset.Y,
dlgRect.Right - dlgRect.Left,
dlgRect.Bottom - dlgRect.Top,
true);
return false;
}
public void Dispose()
{
mTries = -1;
}
// P/Invoke declarations
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT rc);
[DllImport("user32.dll")]
private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int w, int h, bool repaint);
private struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
}
and
class SizeWinDialog : IDisposable
{
private int mTries = 0;
private Form mOwner;
public SizeWinDialog(Form owner)
{
mOwner = owner;
mOwner.BeginInvoke(new Action(findDialog));
}
public Size PreferredSize { get; set; }
private void findDialog()
{
// Enumerate windows to find the message box
if (mTries < 0)
return;
EnumThreadWndProc callback = new EnumThreadWndProc(checkWindow);
if (EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero))
{
if (++mTries < 10)
mOwner.BeginInvoke(new MethodInvoker(findDialog));
}
}
private bool checkWindow(IntPtr hWnd, IntPtr lp)
{
// Checks if <hWnd> is a dialog
StringBuilder sb = new StringBuilder(260);
GetClassName(hWnd, sb, sb.Capacity);
if (sb.ToString() != "#32770")
return true;
// Got it
Rectangle frmRect = new Rectangle(mOwner.Location, mOwner.Size);
RECT dlgRect;
GetWindowRect(hWnd, out dlgRect);
SetWindowPos(new HandleRef(this, hWnd), new HandleRef(), dlgRect.Left, dlgRect.Top, PreferredSize.Width, PreferredSize.Height, 20 | 2);
return false;
}
public void Dispose()
{
mTries = -1;
}
// P/Invoke declarations
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT rc);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool SetWindowPos(HandleRef hWnd, HandleRef hWndInsertAfter, int x, int y, int cx, int cy,
int flags);
private struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
}
Create your own..
public partial class __MessageBox : Form
{
public MMMessageBox(string title, string message)
{
InitializeComponent();
this.Text = title;
this.labelString.Text = message;
}
}

How can I get the DPI in WPF?

How can I get the DPI in WPF?
https://learn.microsoft.com/en-us/archive/blogs/jaimer/getting-system-dpi-in-wpf-app seems to work
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;
}
var dpiXProperty = typeof(SystemParameters).GetProperty("DpiX", BindingFlags.NonPublic | BindingFlags.Static);
var dpiYProperty = typeof(SystemParameters).GetProperty("Dpi", BindingFlags.NonPublic | BindingFlags.Static);
var dpiX = (int)dpiXProperty.GetValue(null, null);
var dpiY = (int)dpiYProperty.GetValue(null, null);
With .NET 4.6.2 Preview and higher, you can call VisualTreeHelper.GetDpi(Visual visual). It returns a DpiScale structure, which tells you the DPI at which the given Visual will be or has been rendered.
I have updated my answer from 2015. Here is some utility code that uses the latest DPI functions from Windows 10 (specifically GetDpiForWindow function which is the only method that supports the DPI_AWARENESS of the window/application/process, etc.) but falls back to older ones (dpi per monitor, and desktop dpi) so it should still work with Windows 7.
It has not dependency on WPF nor Winforms, only on Windows itself.
// note this class considers dpix = dpiy
public static class DpiUtilities
{
// you should always use this one and it will fallback if necessary
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiforwindow
public static int GetDpiForWindow(IntPtr hwnd)
{
var h = LoadLibrary("user32.dll");
var ptr = GetProcAddress(h, "GetDpiForWindow"); // Windows 10 1607
if (ptr == IntPtr.Zero)
return GetDpiForNearestMonitor(hwnd);
return Marshal.GetDelegateForFunctionPointer<GetDpiForWindowFn>(ptr)(hwnd);
}
public static int GetDpiForNearestMonitor(IntPtr hwnd) => GetDpiForMonitor(GetNearestMonitorFromWindow(hwnd));
public static int GetDpiForNearestMonitor(int x, int y) => GetDpiForMonitor(GetNearestMonitorFromPoint(x, y));
public static int GetDpiForMonitor(IntPtr monitor, MonitorDpiType type = MonitorDpiType.Effective)
{
var h = LoadLibrary("shcore.dll");
var ptr = GetProcAddress(h, "GetDpiForMonitor"); // Windows 8.1
if (ptr == IntPtr.Zero)
return GetDpiForDesktop();
int hr = Marshal.GetDelegateForFunctionPointer<GetDpiForMonitorFn>(ptr)(monitor, type, out int x, out int y);
if (hr < 0)
return GetDpiForDesktop();
return x;
}
public static int GetDpiForDesktop()
{
int hr = D2D1CreateFactory(D2D1_FACTORY_TYPE.D2D1_FACTORY_TYPE_SINGLE_THREADED, typeof(ID2D1Factory).GUID, IntPtr.Zero, out ID2D1Factory factory);
if (hr < 0)
return 96; // we really hit the ground, don't know what to do next!
factory.GetDesktopDpi(out float x, out float y); // Windows 7
Marshal.ReleaseComObject(factory);
return (int)x;
}
public static IntPtr GetDesktopMonitor() => GetNearestMonitorFromWindow(GetDesktopWindow());
public static IntPtr GetShellMonitor() => GetNearestMonitorFromWindow(GetShellWindow());
public static IntPtr GetNearestMonitorFromWindow(IntPtr hwnd) => MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
public static IntPtr GetNearestMonitorFromPoint(int x, int y) => MonitorFromPoint(new POINT { x = x, y = y }, MONITOR_DEFAULTTONEAREST);
private delegate int GetDpiForWindowFn(IntPtr hwnd);
private delegate int GetDpiForMonitorFn(IntPtr hmonitor, MonitorDpiType dpiType, out int dpiX, out int dpiY);
private const int MONITOR_DEFAULTTONEAREST = 2;
[DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadLibrary(string lpLibFileName);
[DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport("user32")]
private static extern IntPtr MonitorFromPoint(POINT pt, int flags);
[DllImport("user32")]
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, int flags);
[DllImport("user32")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32")]
private static extern IntPtr GetShellWindow();
[StructLayout(LayoutKind.Sequential)]
private partial struct POINT
{
public int x;
public int y;
}
[DllImport("d2d1")]
private static extern int D2D1CreateFactory(D2D1_FACTORY_TYPE factoryType, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, IntPtr pFactoryOptions, out ID2D1Factory ppIFactory);
private enum D2D1_FACTORY_TYPE
{
D2D1_FACTORY_TYPE_SINGLE_THREADED = 0,
D2D1_FACTORY_TYPE_MULTI_THREADED = 1,
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("06152247-6f50-465a-9245-118bfd3b6007")]
private interface ID2D1Factory
{
int ReloadSystemMetrics();
[PreserveSig]
void GetDesktopDpi(out float dpiX, out float dpiY);
// the rest is not implemented as we don't need it
}
}
public enum MonitorDpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
The only way I found to get the "real" monitor dpi is the following. All other mentioned techniques just say 96 which is not correct for the most monitors.
public class ScreenInformations
{
public static uint RawDpi { get; private set; }
static ScreenInformations()
{
uint dpiX;
uint dpiY;
GetDpi(DpiType.RAW, out dpiX, out dpiY);
RawDpi = dpiX;
}
/// <summary>
/// Returns the scaling of the given screen.
/// </summary>
/// <param name="dpiType">The type of dpi that should be given back..</param>
/// <param name="dpiX">Gives the horizontal scaling back (in dpi).</param>
/// <param name="dpiY">Gives the vertical scaling back (in dpi).</param>
private static void GetDpi(DpiType dpiType, out uint dpiX, out uint dpiY)
{
var point = new System.Drawing.Point(1, 1);
var hmonitor = MonitorFromPoint(point, _MONITOR_DEFAULTTONEAREST);
switch (GetDpiForMonitor(hmonitor, dpiType, out dpiX, out dpiY).ToInt32())
{
case _S_OK: return;
case _E_INVALIDARG:
throw new ArgumentException("Unknown error. See https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx for more information.");
default:
throw new COMException("Unknown error. See https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx for more information.");
}
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062.aspx
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);
//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
const int _S_OK = 0;
const int _MONITOR_DEFAULTTONEAREST = 2;
const int _E_INVALIDARG = -2147024809;
}
/// <summary>
/// Represents the different types of scaling.
/// </summary>
/// <seealso cref="https://msdn.microsoft.com/en-us/library/windows/desktop/dn280511.aspx"/>
public enum DpiType
{
EFFECTIVE = 0,
ANGULAR = 1,
RAW = 2,
}
This is how I managed to get a "scale factor" in WPF.
My laptop's resolution is 1920x1440.
int resHeight = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height; // 1440
int actualHeight = SystemParameters.PrimaryScreenHeight; // 960
double ratio = actualHeight / resHeight;
double dpi = resHeigh / actualHeight; // 1.5 which is true because my settings says my scale is 150%
Use GetDeviceCaps function:
static void Main(string[] args)
{
// 1.25 = 125%
var dpi = GetDpi();
}
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
private static float GetDpi()
{
IntPtr desktopWnd = IntPtr.Zero;
IntPtr dc = GetDC(desktopWnd);
var dpi = 100f;
const int LOGPIXELSX = 88;
try
{
dpi = GetDeviceCaps(dc, LOGPIXELSX);
}
finally
{
ReleaseDC(desktopWnd, dc);
}
return dpi / 96f;
}
You can try using ManagementClass:
public static string GetDPI()
{
using (ManagementClass mc = new ManagementClass("Win32_DesktopMonitor"))
{
using (ManagementObjectCollection moc = mc.GetInstances())
{
int PixelsPerXLogicalInch = 0; // dpi for x
int PixelsPerYLogicalInch = 0; // dpi for y
foreach (ManagementObject each in moc)
{
PixelsPerXLogicalInch = int.Parse((each.Properties["PixelsPerXLogicalInch"].Value.ToString()));
PixelsPerYLogicalInch = int.Parse((each.Properties["PixelsPerYLogicalInch"].Value.ToString()));
}
return PixelsPerXLogicalInch + "," + PixelsPerYLogicalInch;
}
}
}
There are
https://blogs.windows.com/buildingapps/2017/01/25/calling-windows-10-apis-desktop-application/#FJtMAIFjbtXiLQAp.97
January 25, 2017 3:54 pm
"Calling Windows 10 APIs From a Desktop Application"
and
https://learn.microsoft.com/en-us/uwp/api/windows.devices.display.displaymonitor
"Display​Monitor Class"
Namespace: Windows.Devices.Display Assemblies:Windows.Devices.Display.dll, Windows.dll
Provides information about a display monitor device connected to the system.
These data include commonly used information from the monitor's Extended Display Identification Data (EDID, which is an industry-standard display descriptor block that nearly all monitors use to provide descriptions of supported modes and general device information) and DisplayID (which is a newer industry standard that provides a superset of EDID).
Raw​DpiX
Gets the physical horizontal DPI of the monitor (based on the monitor’s native resolution and physical size).
Raw​DpiY
Gets the physical vertical DPI of the monitor (based on the monitor’s native resolution and physical size).
Basic monitor info in Windows from 2006
https://learn.microsoft.com/en-us/windows/desktop/wmicoreprov/msmonitorclass
MSMonitorClass class
WmiMonitorRawEEdidV1Block class
WmiMonitorBasicDisplayParams class
MaxHorizontalImageSize ( EDID byte 21 )
MaxVerticalImageSize ( EDID byte 22 )
( Sizes in EDID are in centimeters above and in millimeters in EDID Detailed Timing Descriptor
12 Horizontal image size, mm, 8 lsbits (0–4095 mm, 161 in)
13 Vertical image size, mm, 8 lsbits (0–4095 mm, 161 in)
14 Bits 7–4 Horizontal image size, mm, 4 msbits
Bits 3–0 Vertical image size, mm, 4 msbits
)
and
https://social.msdn.microsoft.com/Forums/vstudio/en-US/e7bb9384-b343-4543-ac0f-c98b88a7196f/wpf-wmi-just-get-an-empty-string

Resources