GetClientRect is not giving the correct Rectangle - c#

I am trying to create an overlay form that overlays the contents of an external window (excluding the borders etc). I believe that GetClientRect is the correct winapi for that purpose however it does not seem to be working.
I created an example where I load up a form as a black box and display it over an open Notepad.
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace WinFormsApp2
{
public partial class Form1 : Form
{
private Process? targetProc = null;
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetClientRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);
public static Rectangle GetWindowRectangle(IntPtr hWnd)
{
Point point = new Point();
GetClientRect(hWnd, out RECT rect);
ClientToScreen(hWnd, ref point);
return new Rectangle(point.X, point.Y, rect.Right, rect.Bottom);
}
private IntPtr notepadhWnd;
public Form1()
{
InitializeComponent();
this.FormBorderStyle = FormBorderStyle.None;
this.WindowState = FormWindowState.Normal;
this.BackColor = Color.Black;
StartPosition = FormStartPosition.Manual;
targetProc = Process.GetProcessesByName("notepad").FirstOrDefault(p => p != null);
try
{
if (targetProc != null)
{
notepadhWnd = targetProc.MainWindowHandle;
if (notepadhWnd != IntPtr.Zero)
{
Rectangle rect = GetWindowRectangle(notepadhWnd);
Location = new Point(rect.Left, rect.Top);
Size = new Size(rect.Width, rect.Height);
}
}
}
catch (Exception ex)
{
// Log and message or
throw;
}
}
}
}
The output of this is:
I expected the output to be:
From all my searches I believe that the GetClientRect API should only return the client area but not sure what I am missing.

Grab coffee, this will be a long answer.
First, take a look at the image of notepad below. From the wording of your question it sounds like you are expecting GetClientRect to return the area marked in red. GetClientRect returns the dimensions of the window handle you provide but without the borders or scroll bars. In other words, it will give you the interior area of the green rectangle.
There is no single "client window" - in the screenshot below the menu bar is a child window. So is the status bar at the bottom of the screen. So is the editor space which seems to be what you are interested in.
So, how do you get the hWnd of the editor space? I'm not aware of any answer to that question that doesn't rely on the dark magic. It is all fraught with peril... do you iterate through all the child windows and pick the biggest? Pick a point in the dead center of the window and use API calls to find the hWnd at that location? No matter what you do, it will not be an exact science. For purposes of this question, though, I'll show one approach.
Every window has a Class name associated with it. In the image below I'm using a tool called Spy++ to reveal information about the various windows. As you can see, the window that makes up the editor space has a class name of "RichEditD2DPT"
I want to stress again how brittle this solution is. You could have more than one child window of the given class. The application developer could change to a different control with a different class name without warning. Nevertheless, below is code to enumerate through the child windows until the desired class is found. After that, I'm simply passing that hWnd into the code you already had and it seems to work.
public partial class Form1 : Form
{
private readonly StringBuilder _buffer = new StringBuilder(1024);
private IntPtr _notepadMainhWnd;
private IntPtr _notepadEditorhWnd;
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetClientRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
private delegate bool WindowEnumProc(IntPtr hwnd, IntPtr lparam);
public static Rectangle GetWindowRectangle(IntPtr hWnd)
{
Point point = new Point();
GetClientRect(hWnd, out RECT rect);
bool ret = ClientToScreen(hWnd, ref point);
Debug.WriteLine($"{point.X},{point.Y}");
return new Rectangle(point.X, point.Y, rect.Right - rect.Left, rect.Bottom - rect.Top);
}
public Form1()
{
InitializeComponent();
this.FormBorderStyle = FormBorderStyle.None;
this.WindowState = FormWindowState.Normal;
this.BackColor = Color.Black;
StartPosition = FormStartPosition.Manual;
var targetProcess = Process.GetProcessesByName("notepad").FirstOrDefault(p => p != null);
if (targetProcess != null)
{
// Get the main application window
_notepadMainhWnd = targetProcess.MainWindowHandle;
if (_notepadMainhWnd != IntPtr.Zero)
{
// Looks for child windows having the class name "RichEditD2DPT"
EnumChildWindows(_notepadMainhWnd, callback, IntPtr.Zero);
if (_notepadEditorhWnd != IntPtr.Zero)
{
Rectangle rect = GetWindowRectangle(_notepadEditorhWnd);
Location = new Point(rect.Left, rect.Top);
Size = new Size(rect.Width, rect.Height);
}
}
}
}
private bool callback(IntPtr hWnd, IntPtr lParam)
{
if (GetClassName(hWnd, _buffer, _buffer.Capacity) != 0)
{
if (string.CompareOrdinal(_buffer.ToString(), "RichEditD2DPT") == 0)
{
_notepadEditorhWnd = hWnd;
return false;
}
}
return true;
}
}

Related

WPF Window startup location for Per-Monitor-DPI

Struggling to get a WPF Window showing up on secondary screen with mixed DPI monitors. Reproducible in .NET Framework 4.8 as well as .NET Standard 2.0
Setup:
Primary monitor : 4K, 250%
Secondary monitor: 1080p, 100%
Step 1:
add a Manifest for PerMonitorV2
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
</assembly>
Step 2:
public MainWindow()
{
SourceInitialized += (_, __) =>
{
WindowStartupLocation = WindowStartupLocation.Manual;
WindowState = WindowState.Normal;
Width = 1920;
Height = 1050;
Left = -1920;
Top = 0;
};
InitializeComponent();
}
Result:
MainWindow is indeed showing up on secondary screen, but with wrong Left/Top and using DPI of the Primary screen. Only Width and Height are correct.
References:
The only references that I found are with regards to Notepad, are written in MFC:
https://blogs.windows.com/windowsdeveloper/2016/10/24/high-dpi-scaling-improvements-for-desktop-applications-and-mixed-mode-dpi-scaling-in-the-windows-10-anniversary-update/#jwYiMyGKQRTHkBP7.97
https://github.com/Microsoft/Windows-classic-samples/tree/main/Samples/DPIAwarenessPerWindow
Discussion on GitHub (WPF workarounds)
https://github.com/dotnet/wpf/issues/4127
It is saying something about SetThreadDpiAwarenessContext but it is unclear to me how to make it work in C#....
DPI_AWARENESS_CONTEXT previousDpiContext =
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
BOOL ret = SetWindowPlacement(hwnd, wp);
SetThreadDpiAwarenessContext(previousDpiContext);
You can move the window to the center of any monitor. It is just a matter of calculation.
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
public static class WindowHelper
{
public static void MoveToCenter(Window window)
{
if (!GetCursorPos(out POINT cursorPoint))
return;
IntPtr monitorHandle = MonitorFromPoint(cursorPoint, MONITOR_DEFAULTTO.MONITOR_DEFAULTTONULL);
MONITORINFO monitorInfo = new() { cbSize = (uint)Marshal.SizeOf<MONITORINFO>() };
if (!GetMonitorInfo(monitorHandle, ref monitorInfo))
return;
IntPtr windowHandle = new WindowInteropHelper(window).EnsureHandle();
if (!GetWindowPlacement(windowHandle, out WINDOWPLACEMENT windowPlacement))
return;
int left = monitorInfo.rcWork.left + Math.Max(0, (int)((monitorInfo.rcWork.Width - windowPlacement.rcNormalPosition.Width) / 2D));
int top = monitorInfo.rcWork.top + Math.Max(0, (int)((monitorInfo.rcWork.Height - windowPlacement.rcNormalPosition.Height) / 2D));
windowPlacement.rcNormalPosition = new RECT(left, top, windowPlacement.rcNormalPosition.Width, windowPlacement.rcNormalPosition.Height);
SetWindowPlacement(windowHandle, ref windowPlacement);
}
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorPos(out POINT lpPoint);
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint(POINT pt, MONITOR_DEFAULTTO dwFlags);
private enum MONITOR_DEFAULTTO : uint
{
MONITOR_DEFAULTTONULL = 0x00000000,
MONITOR_DEFAULTTOPRIMARY = 0x00000001,
MONITOR_DEFAULTTONEAREST = 0x00000002,
}
[DllImport("User32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
[StructLayout(LayoutKind.Sequential)]
private struct MONITORINFO
{
public uint cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
[StructLayout(LayoutKind.Sequential)]
private struct WINDOWPLACEMENT
{
public uint length;
public uint flags;
public uint showCmd;
public POINT ptMinPosition;
public POINT ptMaxPosition;
public RECT rcNormalPosition;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public int Width => right - left;
public int Height => bottom - top;
public RECT(int x, int y, int width, int height)
{
left = x;
top = y;
right = x + width;
bottom = y + height;
}
}
}
using System.Windows;
public partial class MainWindow : Window
{
public MainWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
InitializeComponent();
}
private bool _isMoved;
protected override Size ArrangeOverride(Size arrangeBounds)
{
if (!_isMoved)
{
_isMoved = true;
WindowHelper.MoveToCenter(this);
}
return base.ArrangeOverride(arrangeBounds);
}
}
But I found that the title bar's DPI remains same as that of primary monitor. In general, non-client area's DPI is hard to fix. So this hack is not so practical unless the default title bar is removed.

How does one create text that can overlay all windows like the Windows Activation Watermark?

I would like to create a count down timer that displays in the lower right hand of the screen with slightly transparent text that can be seen no matter what you have on the screen. Think of something like the windows not activated watermark message that appears when your windows copy isn't activated. That displays in the lower right overlaying all windows and is "stuck" to the screen.
Is there a way to do this via a program? C# preferred.
In my research so far I only came across Deskbands which allow you to put something in the task bar but not outside of it.
Note: The following answer creates a normal semi-transparent topmost form with smooth edge. It's not exactly like windows activation text, for example it goes behind tooltip windows or goes behind menus, but it stays on top of other non-topmost windows.
You can create a Layered Window by setting WS_EX_LAYERED style, then you can assign a smooth-edge transparent region to the window. You can also set WS_EX_TRANSPARENT for the window, then it will ignore the mouse event. Then to make it always on top set its TopMost to true. And finally if you want to make it semi-transparent use suitable Opacity value.
Example
In the following example, I've created an overlay form which is always on top and shows time:
1 - Add the following class which contains required native methods to your project:
using System;
using System.Runtime.InteropServices;
public class NativeMethods
{
public const int WS_EX_LAYERED = 0x80000;
public const int HTCAPTION = 0x02;
public const int WM_NCHITTEST = 0x84;
public const int ULW_ALPHA = 0x02;
public const byte AC_SRC_OVER = 0x00;
public const byte AC_SRC_ALPHA = 0x01;
public const int WS_EX_TRANSPARENT = 0x20;
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
public POINT(int x, int y)
{ this.x = x; this.y = y; }
}
[StructLayout(LayoutKind.Sequential)]
public struct SIZE
{
public int cx;
public int cy;
public SIZE(int cx, int cy)
{ this.cx = cx; this.cy = cy; }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BLENDFUNCTION
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst,
ref POINT pptDst, ref SIZE psize, IntPtr hdcSrc, ref POINT pprSrc,
int crKey, ref BLENDFUNCTION pblend, int dwFlags);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteObject(IntPtr hObject);
}
2 - Add the following class which is a base class for semi-transparent smooth-edge windows forms:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using static NativeMethods;
public class PerPixelAlphaForm : Form
{
public PerPixelAlphaForm()
{
this.FormBorderStyle = FormBorderStyle.None;
this.ShowInTaskbar = false;
this.TopMost = true;
}
protected override CreateParams CreateParams
{
get
{
CreateParams createParams = base.CreateParams;
if (!DesignMode)
createParams.ExStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
return createParams;
}
}
public void SelectBitmap(Bitmap bitmap, int opacity = 255)
{
if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
{
throw new ApplicationException(
"The bitmap must be 32bpp with alpha-channel.");
}
IntPtr screenDc = GetDC(IntPtr.Zero);
IntPtr memDc = CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr hOldBitmap = IntPtr.Zero;
try
{
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
hOldBitmap = SelectObject(memDc, hBitmap);
SIZE newSize = new SIZE(bitmap.Width, bitmap.Height);
POINT sourceLocation = new POINT(0, 0);
POINT newLocation = new POINT(this.Left, this.Top);
BLENDFUNCTION blend = new BLENDFUNCTION();
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = (byte)opacity;
blend.AlphaFormat = AC_SRC_ALPHA;
UpdateLayeredWindow(this.Handle, screenDc, ref newLocation,
ref newSize, memDc, ref sourceLocation, 0, ref blend, ULW_ALPHA);
}
finally
{
ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
SelectObject(memDc, hOldBitmap);
DeleteObject(hBitmap);
}
DeleteDC(memDc);
}
}
}
3 - Then add the following class which is a form, having a timer that shows time:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Windows.Forms;
public partial class Form1 : PerPixelAlphaForm
{
private IContainer components = null;
private Timer timer1;
private Bitmap image;
public Form1()
{
this.components = new Container();
this.timer1 = new Timer(this.components);
this.timer1.Enabled = true;
this.timer1.Interval = 500;
this.timer1.Tick += new EventHandler(this.timer1_Tick);
this.StartPosition = FormStartPosition.Manual;
this.Location = new Point(300, 300);
this.Size = new Size(800, 500);
this.image = new Bitmap(Width, Height, PixelFormat.Format32bppArgb);
}
private void timer1_Tick(object sender, EventArgs e)
{
using (var g = Graphics.FromImage(image))
{
g.Clear(Color.Transparent);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = TextRenderingHint.AntiAlias;
g.DrawString(DateTime.Now.ToString("HH:mm:ss"),
new Font(this.Font.FontFamily, 60, FontStyle.Bold), Brushes.Black,
ClientRectangle, StringFormat.GenericDefault);
SelectBitmap(image, 150);
}
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
components.Dispose();
if (disposing && image != null)
image.Dispose();
base.Dispose(disposing);
}
}
You can find more information in the following posts:
Windows Forms: Pass clicks through a partially transparent always-on-top window
Transparent background image for Form - Smooth edge shape for the Form

DwmGetWindowAttribute doesn't get the correct Rect size for MediaPlayer if it's in full screen mode

I am trying to get application size using this code :
[DllImport(#"dwmapi.dll")]
private static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out Rect pvAttribute, int cbAttribute);
[Serializable, StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public Rectangle ToRectangle()
{
return Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
}
private static bool DWMWA_EXTENDED_FRAME_BOUNDS(IntPtr handle, out Rectangle rectangle)
{
Rect rect;
var result = DwmGetWindowAttribute(handle, (int)Dwmwindowattribute.DwmwaExtendedFrameBounds,
out rect, Marshal.SizeOf(typeof(Rect)));
rectangle = rect.ToRectangle();
return result >= 0;
}
it's working fine for all running applications but if it's Media Player in fullscreen mode I didn't get the right Rect size.
Windows Media Player is weird in full screen mode such that the main window handle doesn't correspond to the full screen window displayed. The full screen window still has a handle but a little more work is needed to get to it.
First you'd need to declare some WinAPI functions and structs:
delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
[DllImport("User32.dll")]
static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("User32.dll")]
static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfo lpmi);
[DllImport("User32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("User32.dll")]
static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
[StructLayout(LayoutKind.Sequential)]
struct MonitorInfo
{
public uint Size;
public Rect Monitor;
public Rect Work;
public uint Flags;
}
// You seem to have this one already
[StructLayout(LayoutKind.Sequential)]
struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
From there, the method looks like this:
// Pass Windows Media Player's main window handle here.
static bool GetWmpFullScreenHandle(IntPtr mainHandle, out IntPtr fullScreenHandle)
{
IntPtr tempHandle = IntPtr.Zero;
// Getting WMP's PID from the main window handle.
GetWindowThreadProcessId(mainHandle, out uint wmpProcessId);
// Optionally, check if the PID resolves to a WMP process.
if (System.Diagnostics.Process.GetProcessById(wmpProcessId).ProcessName != "wmplayer")
{
fullScreenHandle = IntPtr.Zero;
return false;
}
// This iterates through all the open window handles on the machine
// and passes them to the callback below.
EnumWindows((hWnd, lParam) =>
{
// Getting the window handle's PID.
GetWindowThreadProcessId(hWnd, out uint windowProcessId);
// Checking if the window handle belongs to the WMP process.
if (windowProcessId == wmpProcessId)
{
var monitorInfo = new MonitorInfo
{
Size = Convert.ToUInt32(Marshal.SizeOf(typeof(MonitorInfo)))
};
// Getting the dimensions of the monitor the window is displayed on,
// as well as the window dimensions.
if (GetMonitorInfo(MonitorFromWindow(hWnd, 0), ref monitorInfo) &&
GetWindowRect(hWnd, out Rect windowRect))
{
Rect monitorRect = monitorInfo.Monitor;
// If the window dimensions are the same as its monitor's
// dimensions, then we found a hidden full-screen window!
if (windowRect.Left == monitorRect.Left &&
windowRect.Top == monitorRect.Top &&
windowRect.Right == monitorRect.Right &&
windowRect.Bottom == monitorRect.Bottom)
{
tempHandle = hWnd;
}
}
}
return true;
},
IntPtr.Zero);
fullScreenHandle = tempHandle;
// Returns true if the hidden full-screen handle was found, false otherwise.
return fullScreenHandle != IntPtr.Zero;
}
If found, you can then pass the resulting handle to DWMWA_EXTENDED_FRAME_BOUNDS to get the Rectangle.

restoring from tray does not work (windowState) [duplicate]

Is there an easy method to restore a minimized form to its previous state, either Normal or Maximized? I'm expecting the same functionality as clicking the taskbar (or right-clicking and choosing restore).
So far, I have this, but if the form was previously maximized, it still comes back as a normal window.
if (docView.WindowState == FormWindowState.Minimized)
docView.WindowState = FormWindowState.Normal;
Do I have to handle the state change in the form to remember the previous state?
I use the following extension method:
using System.Runtime.InteropServices;
namespace System.Windows.Forms
{
public static class Extensions
{
[DllImport( "user32.dll" )]
private static extern int ShowWindow( IntPtr hWnd, uint Msg );
private const uint SW_RESTORE = 0x09;
public static void Restore( this Form form )
{
if (form.WindowState == FormWindowState.Minimized)
{
ShowWindow(form.Handle, SW_RESTORE);
}
}
}
}
Then call form.Restore() in my code.
The easiest way to restore a form to normal state is:
if (MyForm.WindowState == FormWindowState.Minimized)
{
MyForm.WindowState = FormWindowState.Normal;
}
You could simulate clicking on the taskbar button like this:
SendMessage(docView.Handle, WM_SYSCOMMAND, SC_RESTORE, 0);
For me, the code above does NOT work.
But at last I found the working code. Here it is:
CxImports.ManagedWindowPlacement placement = new CxImports.ManagedWindowPlacement();
CxImports.GetWindowPlacement(Convert.ToUInt32(Handle.ToInt64()), placement);
if (placement.flags == CxImports.WPF_RESTORETOMAXIMIZED)
WindowState = FormWindowState.Maximized;
else
WindowState = FormWindowState.Normal;
I guess, you can find all the needed "imported" functions by simple googling.
If anybody wonders how to do that with other apps windows,this code works for me:
public void UnMinimize(IntPtr handle)
{
WINDOWPLACEMENT WinPlacement = new WINDOWPLACEMENT();
GetWindowPlacement(handle, out WinPlacement);
if(WinPlacement.flags.HasFlag(WINDOWPLACEMENT.Flags.WPF_RESTORETOMAXIMIZED))
{
ShowWindow(handle, (int)SW_MAXIMIZE);
}
else
{
ShowWindow(handle, (int)SW_RESTORE);
}
}
Stuff is here:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public Int32 Left;
public Int32 Top;
public Int32 Right;
public Int32 Bottom;
}
public struct POINT
{
public int x;
public int y;
}
public struct WINDOWPLACEMENT
{
[Flags]
public enum Flags : uint
{
WPF_ASYNCWINDOWPLACEMENT = 0x0004,
WPF_RESTORETOMAXIMIZED = 0x0002,
WPF_SETMINPOSITION = 0x0001
}
/// <summary>
/// The length of the structure, in bytes. Before calling the GetWindowPlacement or SetWindowPlacement functions, set this member to sizeof(WINDOWPLACEMENT).
/// </summary>
public uint length;
/// <summary>
/// The flags that control the position of the minimized window and the method by which the window is restored. This member can be one or more of the following values.
/// </summary>
///
public Flags flags;//uint flags;
/// <summary>
/// The current show state of the window. This member can be one of the following values.
/// </summary>
public uint showCmd;
/// <summary>
/// The coordinates of the window's upper-left corner when the window is minimized.
/// </summary>
public POINT ptMinPosition;
/// <summary>
/// The coordinates of the window's upper-left corner when the window is maximized.
/// </summary>
public POINT ptMaxPosition;
/// <summary>
/// The window's coordinates when the window is in the restored position.
/// </summary>
public RECT rcNormalPosition;
}
public class UnMinimizeClass
{
[DllImport("user32.dll")]
public static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
const int SW_MAXIMIZE = 3;
const int SW_RESTORE = 9;
public static void UnMinimize(IntPtr handle)
{
WINDOWPLACEMENT WinPlacement = new WINDOWPLACEMENT();
GetWindowPlacement(handle, out WinPlacement);
if (WinPlacement.flags.HasFlag(WINDOWPLACEMENT.Flags.WPF_RESTORETOMAXIMIZED))
{
ShowWindow(handle, SW_MAXIMIZE);
}
else
{
ShowWindow(handle, (int)SW_RESTORE);
}
}
}
Using MainWindow.WindowState = WindowState.Normal; isn't enough
Next approach works for my WPF applcation:
MainWindow.WindowState = WindowState.Normal;
MainWindow.Show();
MainWindow.Activate();
I just added one more piece to generify the solution given by #Mesmo. It will create the instance if not created or restore and focus the form if the instance is already created from anywhere in the application. My requirement was that I didn't want to open multiple forms for some of the functionality in the application.
Utilities Class:
public static class Utilities
{
[DllImport("user32.dll")]
private static extern int ShowWindow(IntPtr hWnd, uint Msg);
private const uint SW_RESTORE = 0x09;
public static void Restore(this Form form)
{
if (form.WindowState == FormWindowState.Minimized)
{
ShowWindow(form.Handle, SW_RESTORE);
}
}
public static void CreateOrRestoreForm<T>() where T: Form
{
Form form = Application.OpenForms.OfType<T>().FirstOrDefault();
if (form == null)
{
form = Activator.CreateInstance<T>();
form.Show();
}
else
{
form.Restore();
form.Focus();
}
}
}
Usage:
Utilities.CreateOrRestoreForm<AboutForm>();
This is simple and works if you don't want to use any PInvoke or API trickery. Keep track of when the form has been resized, ignoring when it is minimised.
FormWindowState _PreviousWindowState;
private void TestForm_Resize(object sender, EventArgs e)
{
if (WindowState != FormWindowState.Minimized)
_PreviousWindowState = WindowState;
}
Later when you want to restore it -- for example if a tray icon is clicked:
private void Tray_MouseClick(object sender, MouseEventArgs e)
{
Activate();
if (WindowState == FormWindowState.Minimized)
WindowState = _PreviousWindowState; // former glory
}
[DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetWindowRect(IntPtr hWnd, ref wndRect lpRect);
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")] public static extern bool EnumWindows(WNDENUMPROC lpEnumFunc, int lParam);//用来遍历所有窗口
[DllImport("user32.dll")] public static extern int GetWindowTextW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpString, int nMaxCount);//获取窗口Text
[DllImport("user32.dll")] public static extern int GetClassNameW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpString, int nMaxCount);//获取窗口类名
public static List<wndInfo> GetAllDesktopWindows(bool? isVisitable_)
{
//用来保存窗口对象
List<wndInfo> wndList = new List<wndInfo>();
//enum all desktop windows
EnumWindows(delegate (IntPtr hWnd, int lParam)
{
wndInfo wnd = new wndInfo();
StringBuilder sb = new StringBuilder(256);
//get hwnd
wnd.hWnd = hWnd;
if (isVisitable_ == null || IsWindowVisible(wnd.hWnd) == isVisitable_)
{
//get window name
GetWindowTextW(hWnd, sb, sb.Capacity);
wnd.szWindowName = sb.ToString();
//get window class
GetClassNameW(hWnd, sb, sb.Capacity);
wnd.szClassName = sb.ToString();
wndList.Add(wnd);
}
return true;
}, 0);
return wndList;
}
private void Btn_Test5_Click(object sender, RoutedEventArgs e)
{
var ws = WSys.GetAllDesktopWindows(true);
foreach (var w in ws)
{
if (w.szWindowName == "计算器")
{
WSys.ShowWindow(w.hWnd, 5);
WSys.ShowWindow(w.hWnd, 9);
Log.WriteLine(w.szWindowName);
}
}
}
The above code did not quite work for me in all situations
After checking the flags I also have to check showcmd=3 and if so maximise else restore

WebBrowser.DrawToBitmap() or other methods?

I am trying to capture the content of the WebBrowser control. DrawToBitmap() would work perfectly, but it is not supported in documentation for the WebBrowser control. I have been trying to find another way to capture the contents of the WebBrowser control and save them to a local image file.
Does anyone have any workarounds or other methods to save the contents of the WebBrowser control to a local image file?
The Control.DrawToBitmap doesn't always work so I resorted to the following native API calls that provide more consistent results:
The Utilities class. Call Utilities.CaptureWindow(Control.Handle) to capture a specific control:
public static class Utilities
{
public static Image CaptureScreen()
{
return CaptureWindow(User32.GetDesktopWindow());
}
public static Image CaptureWindow(IntPtr handle)
{
IntPtr hdcSrc = User32.GetWindowDC(handle);
RECT windowRect = new RECT();
User32.GetWindowRect(handle, ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
IntPtr hdcDest = Gdi32.CreateCompatibleDC(hdcSrc);
IntPtr hBitmap = Gdi32.CreateCompatibleBitmap(hdcSrc, width, height);
IntPtr hOld = Gdi32.SelectObject(hdcDest, hBitmap);
Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, ApiConstants.SRCCOPY);
Gdi32.SelectObject(hdcDest, hOld);
Gdi32.DeleteDC(hdcDest);
User32.ReleaseDC(handle, hdcSrc);
Image image = Image.FromHbitmap(hBitmap);
Gdi32.DeleteObject(hBitmap);
return image;
}
}
The Gdi32 class:
public class Gdi32
{
[DllImport("gdi32.dll")]
public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern bool DeleteDC(IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
}
The User32 class:
public static class User32
{
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
[DllImport("user32.dll")]
public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
}
The constants used:
public const int SRCCOPY = 13369376;
The structs used:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
A friendly Control extension method:
public static class ControlExtensions
{
public static Image DrawToImage(this Control control)
{
return Utilities.CaptureWindow(control.Handle);
}
}
This is a code snippet from my CC.Utilities project and I specifically wrote it to take screenshots from the WebBrowser control.
The following method can capture the entire window image even if the window is larger than the size of screen. Then it can capture the image of the contents of the page if the window be resized to the webBrowser.Document.OffsetRectangle.Size
class NativeMethods
{
[ComImport]
[Guid("0000010D-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IViewObject
{
void Draw([MarshalAs(UnmanagedType.U4)] uint dwAspect, int lindex, IntPtr pvAspect, [In] IntPtr ptd, IntPtr hdcTargetDev, IntPtr hdcDraw, [MarshalAs(UnmanagedType.Struct)] ref RECT lprcBounds, [In] IntPtr lprcWBounds, IntPtr pfnContinue, [MarshalAs(UnmanagedType.U4)] uint dwContinue);
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public static void GetImage(object obj, Image destination, Color backgroundColor)
{
using(Graphics graphics = Graphics.FromImage(destination))
{
IntPtr deviceContextHandle = IntPtr.Zero;
RECT rectangle = new RECT();
rectangle.Right = destination.Width;
rectangle.Bottom = destination.Height;
graphics.Clear(backgroundColor);
try
{
deviceContextHandle = graphics.GetHdc();
IViewObject viewObject = obj as IViewObject;
viewObject.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, deviceContextHandle, ref rectangle, IntPtr.Zero, IntPtr.Zero, 0);
}
finally
{
if(deviceContextHandle != IntPtr.Zero)
{
graphics.ReleaseHdc(deviceContextHandle);
}
}
}
}
}
Usage :
Bitmap screenshot = new Bitmap(1024, 768);
NativeMethods.GetImage(webBrowser.ActiveXInstance, screenshot, Color.White);
I am using DrawToBitmap (Visual Studio 2008 C#) to capture big images (user signed invoices,content out of the screen). Basically it is working well but I am getting blank images. About 100 employees are using this software, about every 2 weeks I can see one blank image.
I have done a lot of testing and one funny thing I found is:
I created a button to generate the image from the webbrowser. Usually is OK but if I click the webbrowser first, then click the create-button, the blank image will appear.
I used OleDraw method as in this topic on SO, but integrated it in a class derived from WebBrowser. This allows to do normal Control.DrawToBitmap not only for the WebBrowser, but for a form with WebBrowser in it as well. This also works if the form is hidden (covered by another form, including MDI parent form) and should work when user has locked session with Win+L (I haven't tested it).
public class WebBrowserEx : WebBrowser
{
private const uint DVASPECT_CONTENT = 1;
[DllImport("ole32.dll", PreserveSig = false)]
private static extern void OleDraw([MarshalAs(UnmanagedType.IUnknown)] object pUnk,
uint dwAspect,
IntPtr hdcDraw,
[In] ref System.Drawing.Rectangle lprcBounds
);
protected override void WndProc(ref Message m)
{
const int WM_PRINT = 0x0317;
switch (m.Msg)
{
case WM_PRINT:
Rectangle browserRect = new Rectangle(0, 0, this.Width, this.Height);
// Don't know why, but drawing with OleDraw directly on HDC from m.WParam.
// results in badly scaled (stretched) image of the browser.
// So, drawing to an intermediate bitmap first.
using (Bitmap browserBitmap = new Bitmap(browserRect.Width, browserRect.Height))
{
using (var graphics = Graphics.FromImage(browserBitmap))
{
var hdc = graphics.GetHdc();
OleDraw(this.ActiveXInstance, DVASPECT_CONTENT, hdc, ref browserRect);
graphics.ReleaseHdc(hdc);
}
using (var graphics = Graphics.FromHdc(m.WParam))
{
graphics.DrawImage(browserBitmap, Point.Empty);
}
}
// ignore default WndProc
return;
}
base.WndProc(ref m);
}
}

Categories

Resources