WPF RenderTargetBitmap downscaling TextRenderMode to GreyScale - wpf

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

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

How can I find the position of a maximized window?

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;
}
}

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

Is it possible to display a wpf window without an icon in the title bar?

As we all know, if the icon for a wpf window is undefined then the default icon is displayed. I want to display a window without any icon in the title bar. I realise that I could use a blank image, however this would cause the text in the title bar to be offset to the right.
Does anyone know of a way to completely remove the icon?
(I tried searching for a similar question but couldn't find anything.)
Simple, add this code to your window:
[DllImport("user32.dll")]
static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
private const int GWL_STYLE = -16;
private const uint WS_SYSMENU = 0x80000;
protected override void OnSourceInitialized(EventArgs e)
{
IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
SetWindowLong(hwnd, GWL_STYLE,
GetWindowLong(hwnd, GWL_STYLE) & (0xFFFFFFFF ^ WS_SYSMENU));
base.OnSourceInitialized(e);
}
While not exactly a proper solution, you could try one of the following things:
Setting the WindowStyle-Property to ToolWindow will make the Icon disappear, but the title bar (obviously) will be smaller.
Write a ControlTemplate for the whole Window. Depending on if the Window has to look like a "real" Window, there'd be much effort into trying to recreate the default Style in the Template.
I know this is answered, however Dan Rigsby's blog has an article that shows how to do this without hiding the minimize/maximize boxes.
I found this was frustrating me as I was using the articles (here and here but it kept hiding all the buttons when the sysmenu was hidden, to help I created this helper which as shown above call in OnSourceInitialized.
public static class WpfWindowHelper {
[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hwnd, int index);
[DllImport("user32.dll")]
public static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int width, int height, uint flags);
public const int GWL_EXSTYLE = -20;
public const int WS_EX_DLGMODALFRAME = 0x0001;
public const int SWP_NOSIZE = 0x0001;
public const int SWP_NOMOVE = 0x0002;
public const int SWP_NOZORDER = 0x0004;
public const int SWP_FRAMECHANGED = 0x0020;
public const int GWL_STYLE = -16;
public const int WS_MAXIMIZEBOX = 0x00010000;
public const int WS_MINIMIZEBOX = 0x00020000;
public const int WS_SYSMENU = 0x00080000;
public static void HideSysMenu(this Window w) {
IntPtr hwnd = new WindowInteropHelper(w).Handle;
int extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_DLGMODALFRAME);
SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
public static void HideMinimizeBox(this Window w) {
IntPtr hwnd = new WindowInteropHelper(w).Handle;
SetWindowLong(hwnd, GWL_STYLE,
GetWindowLong(hwnd, GWL_STYLE) & ~(WS_MINIMIZEBOX));
}
public static void HideMaximizeBox(this Window w) {
IntPtr hwnd = new WindowInteropHelper(w).Handle;
SetWindowLong(hwnd, GWL_STYLE,
GetWindowLong(hwnd, GWL_STYLE) & ~(WS_MAXIMIZEBOX));
}
public static void HideMinimizeAndMaximizeBoxes(this Window w) {
IntPtr hwnd = new WindowInteropHelper(w).Handle;
SetWindowLong(hwnd, GWL_STYLE,
GetWindowLong(hwnd, GWL_STYLE) & ~(WS_MAXIMIZEBOX | WS_MINIMIZEBOX));
}
}
No, this doesn't seem to be possible. Quoting from the documentation of the Icon property (emphasis mine):
A WPF window always displays an icon. When one is not provided by setting Icon, WPF chooses an icon to display based on the following rules:
Use the assembly icon, if specified.
If the assembly icon is not specified, use the default Microsoft Windows icon.
If you use Icon to specify a custom window icon, you can restore the default application icon by setting Icon to null.
So, apparently a completely transparent icon seems to be your best bet here. Or perhaps hack around all this by using Windows API functions to set the appropriate style on the window. But this may interfere with WPF's window management.
You can use an empty png image and convert it to icon and set it as icon for your window!!!
Add the following code to the main class of your Window to remove the maximize and minimize buttons, and hide the icon.
private const uint WS_MINIMIZEBOX = 0x00020000;
private const uint WS_MAXIMIZEBOX = 0x00010000;
private const int GWL_STYLE = -16;
private const int GWL_EXSTYLE = -20;
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOMOVE = 0x0002;
private const int SWP_NOZORDER = 0x0004;
private const int SWP_FRAMECHANGED = 0x0020;
private const int WM_SYSCOMMAND = 0x0112;
private const int WM_SETICON = 0x0080;
private const int WS_EX_DLGMODALFRAME = 0x0001;
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern uint GetWindowLong(IntPtr hwnd, int index);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hwnd, int index, uint newStyle);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter, int x, int y, int width, int height, uint flags);
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
uint styles = GetWindowLong(hwnd, GWL_STYLE);
// Remove the maximize and minimize buttons
styles &= 0xFFFFFFFF ^ (WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
SetWindowLong(hwnd, GWL_STYLE, styles);
// Change to dialog modal - necessary for the final step to work!
styles = GetWindowLong(hwnd, GWL_EXSTYLE);
styles |= WS_EX_DLGMODALFRAME;
SetWindowLong(hwnd, GWL_EXSTYLE, styles);
SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
((HwndSource)PresentationSource.FromVisual(this)).AddHook(HelpButtonHook);
// Remove the icon
SendMessage(hwnd, WM_SETICON, new IntPtr(1), IntPtr.Zero);
SendMessage(hwnd, WM_SETICON, IntPtr.Zero, IntPtr.Zero);
}
My first suggestion would be don't do it. In WinForms you can use types of formborderstyles to create a dialog box that has no icon, but only because that is a Windows standard. Only forms with those specific border types should have no icon; it's what users expect.

Resources