Cursor created via CreateIconIndirect isn't scaled with display DPI - c#

I am creating a non-DPI-aware Winforms application. This application works fine on displays with high DPI scaling; Windows simply scales up the application as it should (it looks a bit fuzzy, but that's fine).
The problem is that the mouse cursor, which is created from a Bitmap object using CreateIconIndirect, is not scaled along with the application window, making it look really tiny.
Here's the code I use to create the mouse cursor from a Bitmap object:
[DllImport("user32.dll")]
private static extern bool GetIconInfo(IntPtr hIcon, out IconInfo pIconInfo);
[DllImport("user32.dll")]
private static extern IntPtr CreateIconIndirect(ref IconInfo icon);
/// <summary>
/// Creates a new Cursor object from the specified bitmap and hotspot.
/// </summary>
private static Cursor CreateCursor(Bitmap bmp, Point hotSpot)
{
IntPtr handle = bmp.GetHicon();
IconInfo tmp = new IconInfo();
GetIconInfo(handle, out tmp);
tmp.xHotspot = hotSpot.X;
tmp.yHotspot = hotSpot.Y;
tmp.fIcon = false;
handle = CreateIconIndirect(ref tmp);
return new Cursor(handle);
}
private struct IconInfo
{
public bool fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
So my question is: How can I get a cursor created using CreateIconIndirect to scale properly with the display's DPI scaling, without make my application DPI-aware?
I initially thought that I could simply get the display's DPI scaling and upscale my Bitmap myself, but none of the solutions for retrieving DPI in the following two posts work for me, presumably because my app isn't DPI-aware and would need to retrieve the DPI for the specific display on which the app is running.
How to get Windows Display settings?
How To Get DPI in C# .NET
This is on Windows 10 x64 using .Net 4.6.
Thanks!

Related

this.Region Path data leads to a tiny window

So I created a simple form to test out using an SVG image to draw a custom shaped window. Inspiration found here
It seems to work fine, but no matter what I do my window size is too small to put any controls on.
Reasons for doing this: It's cool? Windows needs better themeing support. I was bored!
I am using Svg from nuget.com from within Visual Studio
Code:
using Svg;
public const int WM_NCLBUTTONDOWN = 0xA1; public const int HT_CAPTION = 0x2;
internal class NativeMethods
{
// Allows forms with Toolbox property set to false to be moved
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool ReleaseCapture();
}
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
System.Drawing.Drawing2D.GraphicsPath frmshp = new System.Drawing.Drawing2D.GraphicsPath();
//frmshp.AddEllipse(0, 0, this.Width, this.Height);
//SvgDocument.Open(#"TestWindowsshape.svg");
SvgDocument newshp = SvgDocument.Open(#"TestWindowsshape.svg");
frmshp = (newshp.Path);
this.Region = new Region(frmshp);
}
private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Make settings window movable without a titlebar
if (e.Button == MouseButtons.Left)
{
NativeMethods.ReleaseCapture();
NativeMethods.SendMessage(Handle, WM_NCLBUTTONDOWN (IntPtr)HT_CAPTION, (IntPtr)0);
}
}
I have tried to increase the svg size, played with the code some, but nothing I do will make the drawn window bigger. I know I can do this with a BMP, and the TransparancyKey option, but I would like to not do it that way, since the BMP & transparency method has the drawback of not being able to use one color in the bitmap itself. Any advice would be appreciated
Edit:
Matrix m = new Matrix();
m.Scale(100, 100, MatrixOrder.Append);
m.Translate(100, 100, MatrixOrder.Append);
newshp.Path.Transform(m);
Has been tried, with no effect. I would assume that this should have worked does that mean the problem is within my SVG?
The problem seems to be in the SVG file, i adjusted the size of a Rectangle in Notepad++ and got a bigger windows, however more complex shapes will be a hassle. It seems Inkscape cannot create SVGs of a reliable size... I have the "Document properties" set to my screen resolution, but the vectors all turn out too small. Perhaps Illustrator can do this properly.

How to capture window contents of a Windows Store App in C#

I have a bit of code to capture windows desktop app contents and save to a Bitmap object in .NET. It uses User32.dll and Gdi32.dll (BitBlt) and works just fine. However, the code produces all-black bitmaps when I give the code a handle to a window that holds a Windows Store app. I'm not sure if this is a security feature or what. I cannot use the ScreenCapture api as the contents of the window, after being resized, are almost always taller/larger than the screen. Has anyone had any luck capturing window contents even when they're larger than the screen, for a Windows Store app?
EDIT: Just as a note I am trying to capture a different program's window, not my own program. My program can be assumed to be a Windows Console application in .NET 4.6.1 / C#
Also, I know that this must be possible somehow in Windows APIs, because the Aero Peek feature, where if you hover over the taskbar on the running program's icon shows the full height of the window, including offscreen components. (see tall window on right, set to 6000px much higher than my display)
As of Windows 8.1, you can use Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap to render elements to a bitmap. There are a couple of caveats to this:
You can capture elements that are offscreen, as long as they are in the XAML visual tree and have Visibility set to Visible and not Collapsed.
Some elements, like video, won't be captured.
See the API for more details:
https://msdn.microsoft.com/library/windows/apps/xaml/windows.ui.xaml.media.imaging.rendertargetbitmap.aspx
This might do the trick. Basically get the window handle to the app, call the native functions on it to figure out the app window position, provide those do the graphics class and copy from the screen.
class Program
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string strClassName, string strWindowName);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
public struct Rect
{
public int Left { get; set; }
public int Top { get; set; }
public int Right { get; set; }
public int Bottom { get; set; }
}
static void Main(string[] args)
{
/// Give this your app's process name.
Process[] processes = Process.GetProcessesByName("yourapp");
Process lol = processes[0];
IntPtr ptr = lol.MainWindowHandle;
Rect AppRect = new Rect();
GetWindowRect(ptr, ref AppRect);
Rectangle rect = new Rectangle(AppRect.Left, AppRect.Top, (AppRect.Right - AppRect.Left), (AppRect.Bottom - AppRect.Top));
Bitmap bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
// make sure temp directory is there or it will throw.
bmp.Save(#"c:\temp\test.jpg", ImageFormat.Jpeg);
}
}

How to get DPI scale for all screens?

I need to get the DPI scale, as set from Control Panel > Display, for each of the screens connected to the computer, even those that do not have a WPF window open. I have seen a number of ways to get DPI (for example, http://dzimchuk.net/post/Best-way-to-get-DPI-value-in-WPF) but these seem to be dependent on either Graphics.FromHwnd(IntPtr.Zero) or PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice.
Is there a way to get the DPI settings for each individual screen?
Background - I am creating a layout configuration editor so that the user can set up their configuration prior to launch. For this, I draw each of the screens relative to each other. For one configuration we are using a 4K display that has a larger than default DPI scale set. It is drawing much smaller than it physically appears in relation to the other screens because it reports as the same resolution as the other screens.
I found a way to get the dpi’s with the WinAPI.
As first needs references to System.Drawing and System.Windows.Forms. It is possible to get the monitor handle with the WinAPI from a point on the display area - the Screen class can give us this points. Then the GetDpiForMonitor function returns the dpi of the specified monitor.
public static class ScreenExtensions
{
public static void GetDpi(this System.Windows.Forms.Screen screen, DpiType dpiType, out uint dpiX, out uint dpiY)
{
var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
var mon = MonitorFromPoint(pnt, 2/*MONITOR_DEFAULTTONEAREST*/);
GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062(v=vs.85).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(v=vs.85).aspx
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
}
//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280511(v=vs.85).aspx
public enum DpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
There are three types of scaling, you can find a description in the MSDN.
I tested it quickly with a new WPF application:
private void Window_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var sb = new StringBuilder();
sb.Append("Angular\n");
sb.Append(string.Join("\n", Display(DpiType.Angular)));
sb.Append("\nEffective\n");
sb.Append(string.Join("\n", Display(DpiType.Effective)));
sb.Append("\nRaw\n");
sb.Append(string.Join("\n", Display(DpiType.Raw)));
this.Content = new TextBox() { Text = sb.ToString() };
}
private IEnumerable<string> Display(DpiType type)
{
foreach (var screen in System.Windows.Forms.Screen.AllScreens)
{
uint x, y;
screen.GetDpi(type, out x, out y);
yield return screen.DeviceName + " - dpiX=" + x + ", dpiY=" + y;
}
}
I hope it helps!

How do I put a full screen window on top in C# after screen is cloned?

I am working on a C# WPF application that uses two screens. In the application the user is able to clone or extend the screen depending on what the user want to do. This is done in windows 7 and is using the following code:
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
private static extern long SetDisplayConfig(uint numPathArrayElements, IntPtr pathArray, uint numModeArrayElements, IntPtr modeArray, uint flags);
UInt32 SDC_TOPOLOGY_INTERNAL = 0x00000001;
UInt32 SDC_TOPOLOGY_CLONE = 0x00000002;
UInt32 SDC_TOPOLOGY_EXTEND = 0x00000004;
UInt32 SDC_TOPOLOGY_EXTERNAL = 0x00000008;
UInt32 SDC_APPLY = 0x00000080;
public void CloneDisplays()
{
SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, (SDC_APPLY | SDC_TOPOLOGY_CLONE));
}
public void ExtendDisplays()
{
SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, (SDC_APPLY | SDC_TOPOLOGY_EXTEND));
}
Now to my problem. When using the above code I manage to clone/extend the screen. However, after this is done the taskbar at the bottom of the screen is in front of the full screen application which should not be the case. How do i put the application window back at the top?
Additional information:
When I start the application it starts in fullscreen with the taskbar behind the application. This is done by setting the following:
WindowState="Maximized"
WindowStyle="None"
And this is what I want after the clone/extend has been done.
Thanks
Edit:
I have noticed that after I clone/extend the screen and sleep for say 5 seconds everything works as it should. However, as soon as the 5 seconds is over and the function exits the taskbar gets on top. Therefore it seems that I can not change something right after the clone/extend because the taskbar will always get on top in the end. So somehow I have to figure out how to stop the taskbar to behave like this, instead of changing the property of the window.
Try setting the width and height of the WPF window as follows, You could set this within window constructor.
Width = System.Windows.SystemParameters.PrimaryScreenWidth;
Height = System.Windows.SystemParameters.PrimaryScreenWidth;
To hide the taskbar try setting,
Width = System.Windows.SystemParameters.FullPrimaryScreenWidth;
Height = System.Windows.SystemParameters.FullPrimaryScreenHeight;
I'm already doing full-screen mode within my winforms applications, but i think you can do it more or less the same within WPF:
(this has to be different but similar in WPF):
form.WindowState = FormWindowState.Normal;
form.FormBorderStyle = FormBorderStyle.None;
form.Bounds = Screen.GetBounds(form);
Then the next step is to hide the task-bar if your application is on the primary screen:
if (Screen.PrimaryScreen.Equals(Screen.FromRectangle(Screen.GetBounds(form))))
{
ShowWindowsToolbar(false);
}
And the method ShowWindowsToolbar() is implemented as follows:
[DllImport("user32.dll")]
private static extern int FindWindow(string lpszClassName, string lpszWindowName);
[DllImport("user32.dll")]
private static extern int ShowWindow(int hWnd, int nCmdShow);
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
public void WindowsToolbar(bool visible)
{
int hWnd = FindWindow("Shell_TrayWnd", "");
ShowWindow(hWnd, visible ? SW_SHOW : SW_HIDE);
}
That's the way, how most of the tools out there support this kind of stuff. Also note, that this mode can mostly entered/leaved by pressing F11. So it would be good, if you also support this keystroke.
Turns out all I have to do is update the dispatcher queue and force it to do the update right after the clone/extend has been done. Then I can update the window properties.
public void ExtendDisplays()
{
SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, (SDC_APPLY | SDC_TOPOLOGY_EXTEND));
this.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })); //Force update
current_window.hide();
current_window.show();
}

How do I find the position / location of a window given a hWnd without NativeMethods?

I'm currently working with WatiN, and finding it to be a great web browsing automation tool. However, as of the last release, it's screen capturing functionality seems to be lacking. I've come up with a workable solution for capturing screenshots from the screen (independently generating code similar to this StackOverflow question) in addition to some code by Charles Petzold. Unfortunately, there is a missing component: Where is the actual window?
WatiN conveniently provides the browser's hWnd to you, so we can (with this simplified example) get set to copy an image from the screen, like so:
// browser is either an WatiN.Core.IE or a WatiN.Core.FireFox...
IntPtr hWnd = browser.hWnd;
string filename = "my_file.bmp";
using (Graphics browser = Graphics.FromHwnd(browser.hWnd) )
using (Bitmap screenshot = new Bitmap((int)browser.VisibleClipBounds.Width,
(int)browser.VisibleClipBounds.Height,
browser))
using (Graphics screenGraphics = Graphics.FromImage(screenshot))
{
int hWndX = 0; // Upper left of graphics? Nope,
int hWndY = 0; // this is upper left of the entire desktop!
screenGraphics.CopyFromScreen(hWndX, hWndY, 0, 0,
new Size((int)browser.VisibileClipBounds.Width,
(int)browser.VisibileClipBounds.Height));
screenshot.Save(filename, ImageFormat.Bmp);
}
Success! We get screenshots, but there's that problem: hWndX and hWndY always point to the upper left most corner of the screen, not the location of the window we want to copy from.
I then looked into Control.FromHandle, however this seems to only work with forms you created; this method returns a null pointer if you pass the hWnd into it.
Then, further reading lead me to switch my search criteria...I had been searching for 'location of window' when most people really want the 'position' of the window. This lead to another SO question that talked about this, but their answer was to use native methods.
So, Is there a native C# way of finding the position of a window, only given the hWnd (preferably with only .NET 2.0 era libraries)?
I just went through this on a project and was unable to find any managed C# way.
To add to Reed's answer the P/Invoke code is:
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
Call it as:
RECT rct = new RECT();
GetWindowRect(hWnd, ref rct);
No - if you didn't create the form, you have to P/Invoke GetWindowRect. I don't believe there is a managed equivalent.
The answer is as others have stated, probably "No, you cannot take a screenshot of a random window from an hwnd without native methods.". Couple of caveats before I show it:
Forewarning:
For anyone who wants to use this code, note that the size given from the VisibleClipBounds is only inside the window, and does not include the border or title bar. It's the drawable area. If you had that, you might be able to do this without p/invoke.
(If you could calculate the border of the browser window, you could use the VisibleClipBounds. If you wanted, you could use the SystemInformation object to get important info like Border3DSize, or you could try to calculate it by creating a dummy form and deriving the border and title bar height from that, but that all sounds like the black magic that bugs are made of.)
This is equivalent to Ctrl+Printscreen of the window. This also does not do the niceties that the WatiN screenshot capability does, such as scroll the browser and take an image of the whole page. This is suitable for my project, but may not be for yours.
Enhancements:
This could be changed to be an extension method if you're in .NET 3 and up-land, and an option for the image type could be added pretty easily (I default to ImageFormat.Bmp for this example).
Code:
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
public class Screenshot
{
class NativeMethods
{
// http://msdn.microsoft.com/en-us/library/ms633519(VS.85).aspx
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
// http://msdn.microsoft.com/en-us/library/a5ch4fda(VS.80).aspx
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
}
/// <summary>
/// Takes a screenshot of the browser.
/// </summary>
/// <param name="b">The browser object.</param>
/// <param name="filename">The path to store the file.</param>
/// <returns></returns>
public static bool SaveScreenshot(Browser b, string filename)
{
bool success = false;
IntPtr hWnd = b.hWnd;
NativeMethods.RECT rect = new NativeMethods.RECT();
if (NativeMethods.GetWindowRect(hWnd, ref rect))
{
Size size = new Size(rect.Right - rect.Left,
rect.Bottom - rect.Top);
// Get information about the screen
using (Graphics browserGraphics = Graphics.FromHwnd(hWnd))
// apply that info to a bitmap...
using (Bitmap screenshot = new Bitmap(size.Width, size.Height,
browserGraphics))
// and create an Graphics to manipulate that bitmap.
using (Graphics imageGraphics = Graphics.FromImage(screenshot))
{
int hWndX = rect.Left;
int hWndY = rect.Top;
imageGraphics.CopyFromScreen(hWndX, hWndY, 0, 0, size);
screenshot.Save(filename, ImageFormat.Bmp);
success = true;
}
}
// otherwise, fails.
return success;
}
}

Categories

Resources