WPF Window startup location for Per-Monitor-DPI - c#

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.

Related

GetClientRect is not giving the correct Rectangle

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

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.

Why does GetWindowRect include the title bar in my WPF window?

I'm trying to get caret position using GetWindowRect() (and GetGUIThreadInfo()):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Runtime.InteropServices;
namespace WpfApplication1
{
public class CaretInfo
{
public double Width { get; private set; }
public double Height { get; private set; }
public double Left { get; private set; }
public double Top { get; private set; }
public CaretInfo(double width, double height, double left, double top)
{
Width = width;
Height = height;
Left = left;
Top = top;
}
}
public class CaretHelper
{
public static CaretInfo GetCaretPosition()
{
// Get GUI info containing caret poisition
var guiInfo = new GUITHREADINFO();
guiInfo.cbSize = (uint)Marshal.SizeOf(guiInfo);
GetGUIThreadInfo(0, out guiInfo);
// Get width/height
double width = guiInfo.rcCaret.right - guiInfo.rcCaret.left;
double height = guiInfo.rcCaret.bottom - guiInfo.rcCaret.top;
// Get left/top relative to screen
RECT rect;
GetWindowRect(guiInfo.hwndFocus, out rect);
double left = guiInfo.rcCaret.left + rect.left + 2;
double top = guiInfo.rcCaret.top + rect.top + 2;
int capacity = GetWindowTextLength(guiInfo.hwndFocus) * 2;
StringBuilder stringBuilder = new StringBuilder(capacity);
GetWindowText(guiInfo.hwndFocus, stringBuilder, stringBuilder.Capacity);
Console.WriteLine("Window: " + stringBuilder);
Console.WriteLine("Caret: " + guiInfo.rcCaret.left + ", " + guiInfo.rcCaret.top);
Console.WriteLine("Rect : " + rect.left + ", " + rect.top);
return new CaretInfo(width, height, left, top);
}
[DllImport("user32.dll", EntryPoint = "GetGUIThreadInfo")]
public static extern bool GetGUIThreadInfo(uint tId, out GUITHREADINFO threadInfo);
[DllImport("user32.dll")]
public static extern bool ClientToScreen(IntPtr hWnd, out Point position);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr handle, out RECT lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetClientRect(IntPtr hWnd, ref Rect rect);
[StructLayout(LayoutKind.Sequential)]
public struct GUITHREADINFO
{
public uint cbSize;
public uint flags;
public IntPtr hwndActive;
public IntPtr hwndFocus;
public IntPtr hwndCapture;
public IntPtr hwndMenuOwner;
public IntPtr hwndMoveSize;
public IntPtr hwndCaret;
public RECT rcCaret;
};
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowTextLe
For Notepad and almost anywhere else the coordinates are correctly fetched:
In my WPF (and any other WPF) window, however, GetWindowRect() decides to include the title bar and offsetting the caret top position by the height of the title bar:
Any idea why?
I tried using DwmGetWindowAttribute() as well, but it gets the same coordinates for the WPF window as GetWindowRect().
Edit:
With the answer from Brian Reichle I've been able to determine a way to get the coordinate of the client area:
[DllImport("user32.dll")]
public static extern bool ClientToScreen(IntPtr hWnd, ref System.Drawing.Point lpPoint);
System.Drawing.Point point = new System.Drawing.Point(0, 0);
ClientToScreen(guiInfo.hwndFocus, ref point)
0,0 is the top left coordinate of the client area of the window specified by guiInfo.hwndFocus and it's always 0,0 because it's relative to the window's client area. ClientToScreen() converts the coordinates to be relative to screen instead (has to be System.Drawing.Point, System.Windows.Point won't work).
The title bar is included because its part of the window, if you don't want the non-client area then you need to request the client area rect (GetClientRect).
The confusion from notepad is probably because you are using the window handle of the text box rather than of the window itself. Remember, WPF uses a single handle for the overall window while win32 will often (but not always) use a separate handle for each control within the window.
In a comment you mentioned that GetClientRect 'returned' 0,0, did you check if it returned true (success) or false (failure)? If it returned false, did you check the result of GetLastError()?

How to change the gamma ramp of a single display monitor (NVidia Config)?

I try to change my gamma of just one screen and not all my screens.
I use this code to help me
But this SetDeviceGammaRamp(GetDC(IntPtr.Zero), ref s_ramp);
Is for all devices.
[EDIT2] I saw one weird thing : SetDeviceGammaRamp is not the same gamma of the Nvidia Panel Controller (I tried to change my value of SetDeviceGammaRamp, and it's like if i changed the value of brightness and contrast in the Nvidia panel). So i think i must to use NVidia API :/
So, how can i change this code to put my gamma on my first screen, or my second, but not both
[EDIT1] This is what i made :
class Monitor
{
[DllImport("user32.dll")]
static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData);
public delegate int MonitorEnumProc(IntPtr hMonitor, IntPtr hDCMonitor, ref Rect lprcMonitor, IntPtr dwData);
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
static extern bool GetMonitorInfo(IntPtr hmon, ref MonitorInfo mi);
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
/// <summary>
/// The struct that contains the display information
/// </summary>
public class DisplayInfo
{
public string Availability { get; set; }
public string ScreenHeight { get; set; }
public string ScreenWidth { get; set; }
public Rect MonitorArea { get; set; }
public Rect WorkArea { get; set; }
public IntPtr DC { get; set; }
}
[StructLayout(LayoutKind.Sequential)]
struct MonitorInfo
{
public uint size;
public Rect monitor;
public Rect work;
public uint flags;
}
/// <summary>
/// Collection of display information
/// </summary>
public class DisplayInfoCollection : List<DisplayInfo>
{
}
/// <summary>
/// Returns the number of Displays using the Win32 functions
/// </summary>
/// <returns>collection of Display Info</returns>
public DisplayInfoCollection GetDisplays()
{
DisplayInfoCollection col = new DisplayInfoCollection();
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData)
{
MonitorInfo mi = new MonitorInfo();
mi.size = (uint)Marshal.SizeOf(mi);
bool success = GetMonitorInfo(hMonitor, ref mi);
if (success)
{
DisplayInfo di = new DisplayInfo();
di.ScreenWidth = (mi.monitor.right - mi.monitor.left).ToString();
di.ScreenHeight = (mi.monitor.bottom - mi.monitor.top).ToString();
di.MonitorArea = mi.monitor;
di.WorkArea = mi.work;
di.Availability = mi.flags.ToString();
di.DC = GetDC(hdcMonitor);
col.Add(di);
}
return 1;
}, IntPtr.Zero);
return col;
}
public Monitor()
{
}
}
And for SetDeviceGammaRamp, i made this :
GammaRamp gamma = new GammaRamp();
Monitor.DisplayInfoCollection monitors;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Monitor monitor = new Monitor();
monitors = monitor.GetDisplays();
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
int value = trackBar1.Value;
gamma.SetValue(Convert.ToByte(value), monitors[1].DC);
}
GammaRamp class :
public void SetValue(byte value, IntPtr hdc)
{
Ramp gammaArray = new Ramp { Red = new ushort[256], Green = new ushort[256], Blue = new ushort[256] };
for (int i = 0; i < 256; i++)
{
gammaArray.Red[i] = gammaArray.Green[i] = gammaArray.Blue[i] = (ushort)Math.Min(i * (value + 128), ushort.MaxValue);
}
SetDeviceGammaRamp(hdc, ref gammaArray);
}
You can get the DC of another monitor by using EnumDisplayMonitors or GetMonitorInfo functions.
See complete explanations at HMONITOR and the Device Context.
EDIT
As explained in EnumDisplayMonitors,
pass IntPtr.Zero to hdc parameter (values encompasses all displays)
then in MONITORENUMPROC, hdcMonitor should contain the right DC for the current monitor being evaluated
then change your di.DC = GetDC(IntPtr.Zero); to di.DC = GetDC(hdcMonitor);
(passing Zero to GetDC will obviously specify all monitors, not what you want)
EDIT 2
Little confusion with the docs, in fact the 3rd type of call in the remarks of EnumDisplayMonitors should be performed:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow
{
private readonly List<IntPtr> _dcs = new List<IntPtr>();
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var hdc = NativeMethods.GetDC(IntPtr.Zero);
if (hdc == IntPtr.Zero)
throw new InvalidOperationException();
if (!NativeMethods.EnumDisplayMonitors(hdc, IntPtr.Zero, Monitorenumproc, IntPtr.Zero))
throw new InvalidOperationException();
if (NativeMethods.ReleaseDC(IntPtr.Zero, hdc) == 0)
throw new InvalidOperationException();
foreach (var monitorDc in _dcs)
{
// do something cool !
}
}
private int Monitorenumproc(IntPtr param0, IntPtr param1, ref tagRECT param2, IntPtr param3)
{
// optional actually ...
var info = new MonitorInfo {cbSize = (uint) Marshal.SizeOf<MonitorInfo>()};
if (!NativeMethods.GetMonitorInfoW(param0, ref info))
throw new InvalidOperationException();
_dcs.Add(param1); // grab DC for current monitor !
return 1;
}
}
public class NativeMethods
{
[DllImport("user32.dll", EntryPoint = "ReleaseDC")]
public static extern int ReleaseDC([In] IntPtr hWnd, [In] IntPtr hDC);
[DllImport("user32.dll", EntryPoint = "GetDC")]
public static extern IntPtr GetDC([In] IntPtr hWnd);
[DllImport("user32.dll", EntryPoint = "GetMonitorInfoW")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorInfoW([In] IntPtr hMonitor, ref MonitorInfo lpmi);
[DllImport("user32.dll", EntryPoint = "EnumDisplayMonitors")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumDisplayMonitors([In] IntPtr hdc, [In] IntPtr lprcClip, MONITORENUMPROC lpfnEnum,
IntPtr dwData);
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int MONITORENUMPROC(IntPtr param0, IntPtr param1, ref tagRECT param2, IntPtr param3);
[StructLayout(LayoutKind.Sequential)]
public struct MonitorInfo
{
public uint cbSize;
public tagRECT rcMonitor;
public tagRECT rcWork;
public uint dwFlags;
}
[StructLayout(LayoutKind.Sequential)]
public struct tagRECT
{
public int left;
public int top;
public int right;
public int bottom;
}
}
You should be able to get per-monitor DC, (cannot 100% confirm since I only have one screen).
If all else fails then maybe that NVidia thing interferes somehow under the hood.

Categories

Resources