I am using HelpProvider to show help for my control.
I input the help string for HelpProvider control. But this does not show the string properly.
P/S:
My language is Vietnam, which is a unicode font.
Here is my text when progamming: "Chúc mừng năm mới"
Here is the text when shown:
It's an old bug and it's because of two problems:
The default font which is used by underlying API of the HelpProvider doesn't support unicode characters
The underlying API of the HelpProvider doesn't support Unicode.
After fixing these two problems, you can show Unicode characters correctly:
HelpExtensions.ShowPopup2(button1, "متن آزمایشی", Control.MousePosition);
The first problem is in Help class(.NET 4.X, .NET 5) which has created the HH_POPUP but hasn't specified any font for it. As a result a default font which doesn't support Unicode characters will be used.
A possible fix is using a default font like SystemFonts.CaptionFont which supports Unicode characters.
For the second problem, you need to change a setting in Windows, to do so:
Go to Control Panel → Region → Administrative tab, then in the section "Language for non-Unicode programs", click on "Change system locale ..." button and then in the next dialog, choose the language of your choice, for example Persian.
Or to support other languages as well you can choose: "Beta: Use Unicode UTF-8 for worldwide language support" which is Beta.
And here is HelpExtensions class:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public static class HelpExtensions
{
public static void ShowPopup2(Control parent, string caption, Point location, Font font = null, Color? backColor = null, Color? foreColor = null)
{
font = font ?? SystemFonts.CaptionFont;
backColor = backColor ?? Color.FromKnownColor(KnownColor.Window);
foreColor = foreColor ?? Color.FromKnownColor(KnownColor.WindowText);
var popup = new HH_POPUP();
popup.clrBackground = new COLORREF(backColor.Value);
popup.clrForeground = new COLORREF(foreColor.Value);
popup.pt = new POINT(location);
var pszText = Marshal.StringToCoTaskMemAuto(caption);
popup.pszText = pszText;
var pszFont = Marshal.StringToCoTaskMemAuto(
$"{font.Name}, {font.Size}, , " +
$"{(font.Bold ? "BOLD" : "")}" +
$"{(font.Italic ? "ITALIC" : "")}" +
$"{(font.Underline ? "UNDERLINE" : "")}");
popup.pszFont = pszFont;
try
{
HtmlHelp(parent.Handle, null, HTMLHelpCommand.HH_DISPLAY_TEXT_POPUP, popup);
}
finally
{
Marshal.FreeCoTaskMem(pszText);
Marshal.FreeCoTaskMem(pszFont);
}
}
[Flags()]
public enum HTMLHelpCommand : uint
{
HH_DISPLAY_TOPIC = 0,
HH_DISPLAY_TOC = 1,
HH_DISPLAY_INDEX = 2,
HH_DISPLAY_SEARCH = 3,
HH_DISPLAY_TEXT_POPUP = 0x000E,
HH_HELP_CONTEXT = 0x000F,
HH_CLOSE_ALL = 0x0012
}
[DllImport("hhctrl.ocx", SetLastError = true, EntryPoint = "HtmlHelpW", CharSet = CharSet.Unicode)]
static extern int HtmlHelp(IntPtr hWndCaller,
[MarshalAs(UnmanagedType.LPWStr)] string pszFile,
HTMLHelpCommand uCommand,
[MarshalAs(UnmanagedType.LPStruct)] HH_POPUP dwData);
[StructLayout(LayoutKind.Sequential)]
struct COLORREF
{
int ColorRef;
public COLORREF(int lRGB)
{
ColorRef = lRGB & 0x00ffffff;
}
public COLORREF(Color color) : this(color.ToArgb())
{
}
}
[StructLayout(LayoutKind.Sequential)]
class POINT
{
public int x;
public int y;
public POINT(int x, int y)
{
this.x = x;
this.y = y;
}
public POINT(Point p) : this(p.X, p.Y)
{
}
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public RECT(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public RECT(Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom)
{
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
class HH_POPUP
{
internal int cbStruct = Marshal.SizeOf(typeof(HH_POPUP));
internal IntPtr hinst = IntPtr.Zero;
internal int idString = 0;
internal IntPtr pszText;
internal POINT pt;
internal COLORREF clrForeground = new COLORREF(-1);
internal COLORREF clrBackground = new COLORREF(-1);
internal RECT rcMargins = new RECT(-1, -1, -1, -1);
internal IntPtr pszFont;
}
}
HelpProvider2 Component
I've created a HelpProvider2 component which supports Unicode characters. It also exposes Font, ForeColor and BackColor properties:
Download or clone
Repository: r-aghaei/HelpProvider2Example.git
Download master.zip
Related
I have a WPF application that takes a screen shot of the running Handbrake executable using a class called ScreenCapture that I copied from stack overflow.
public class ScreenCapture
{
[DllImport("user32.dll")]
static extern int GetWindowRgn(IntPtr hWnd, IntPtr hRgn);
//Region Flags - The return value specifies the type of the region that the function obtains. It can be one of the following values.
const int ERROR = 0;
const int NULLREGION = 1;
const int SIMPLEREGION = 2;
const int COMPLEXREGION = 3;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);
[DllImport("gdi32.dll")]
static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }
public int X
{
get { return Left; }
set { Right -= (Left - value); Left = value; }
}
public int Y
{
get { return Top; }
set { Bottom -= (Top - value); Top = value; }
}
public int Height
{
get { return Bottom - Top; }
set { Bottom = value + Top; }
}
public int Width
{
get { return Right - Left; }
set { Right = value + Left; }
}
public System.Drawing.Point Location
{
get { return new System.Drawing.Point(Left, Top); }
set { X = value.X; Y = value.Y; }
}
public System.Drawing.Size Size
{
get { return new System.Drawing.Size(Width, Height); }
set { Width = value.Width; Height = value.Height; }
}
public static implicit operator System.Drawing.Rectangle(RECT r)
{
return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
}
public static implicit operator RECT(System.Drawing.Rectangle r)
{
return new RECT(r);
}
public static bool operator ==(RECT r1, RECT r2)
{
return r1.Equals(r2);
}
public static bool operator !=(RECT r1, RECT r2)
{
return !r1.Equals(r2);
}
public bool Equals(RECT r)
{
return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
}
public override bool Equals(object obj)
{
if (obj is RECT)
return Equals((RECT)obj);
else if (obj is System.Drawing.Rectangle)
return Equals(new RECT((System.Drawing.Rectangle)obj));
return false;
}
public override int GetHashCode()
{
return ((System.Drawing.Rectangle)this).GetHashCode();
}
public override string ToString()
{
return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
}
}
public Bitmap GetScreenshot(IntPtr ihandle)
{
IntPtr hwnd = ihandle;//handle here
RECT rc;
GetWindowRect(new HandleRef(null, hwnd), out rc);
Bitmap bmp = new Bitmap(rc.Right - rc.Left, rc.Bottom - rc.Top, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap;
try
{
hdcBitmap = gfxBmp.GetHdc();
}
catch
{
return null;
}
bool succeeded = PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!succeeded)
{
gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
}
IntPtr hRgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, hRgn);
Region region = Region.FromHrgn(hRgn);//err here once
if (!region.IsEmpty(gfxBmp))
{
gfxBmp.ExcludeClip(region);
gfxBmp.Clear(Color.Transparent);
}
gfxBmp.Dispose();
return bmp;
}
public void WriteBitmapToFile(string filename, Bitmap bitmap)
{
bitmap.Save(filename, ImageFormat.Bmp);
}
So when the button click handler below is called a screenshot of the handbrake window is taken.
I write it to the harddrive to make sure its ok:
handbrake screen shot.
I create an instance of a CLR class library ClassLibrary1::Class1 and call the method "DoSomething" passing it the System.Drawing.Bitmap object.
private void button4_Click(object sender, RoutedEventArgs e)
{
string wName = "HandBrake";
IntPtr hWnd = IntPtr.Zero;
foreach (Process pList in Process.GetProcesses())
{
if (pList.MainWindowTitle.Contains(wName))
{
hWnd = pList.MainWindowHandle;
var sc = new ScreenCapture();
SetForegroundWindow(hWnd);
var bitmap = sc.GetScreenshot(hWnd);
sc.WriteBitmapToFile("handbrake.bmp", bitmap);
Bitmap image1 = (Bitmap)System.Drawing.Image.FromFile("handbrake.bmp", true);
ClassLibrary1.Class1 opencv = new ClassLibrary1.Class1();
opencv.DoSomething(image1);
}
}
}
Inside DoSomething I attempt to convert the System.Drawing.Bitmap to a OpenCV class cv::Mat. I call cv::imwrite to make sure the bitmap is still ok, unfortunately somethings gone wrong: mangled handbrake screenshot
void Class1::DoSomething(Bitmap ^mybitmap)
{
cv::Mat *imgOriginal;
// Lock the bitmap's bits.
Rectangle rect = Rectangle(0, 0, mybitmap->Width, mybitmap->Height);
Imaging::BitmapData^ bmpData = mybitmap->LockBits(rect, Imaging::ImageLockMode::ReadWrite, mybitmap->PixelFormat);
try
{
// Get the address of the first line.
IntPtr ptr = bmpData->Scan0;
// Declare an array to hold the bytes of the bitmap.
// This code is specific to a bitmap with 24 bits per pixels.
int bytes = Math::Abs(bmpData->Stride) * mybitmap->Height;
array<Byte>^rgbValues = gcnew array<Byte>(bytes);
// Copy the RGB values into the array.
System::Runtime::InteropServices::Marshal::Copy(ptr, rgbValues, 0, bytes);
imgOriginal = new cv::Mat(mybitmap->Height, mybitmap->Width, CV_8UC3, (void *)ptr, std::abs(bmpData->Stride));
}
finally { mybitmap->UnlockBits(bmpData); }//Remember to unlock!!!
cv::imwrite("from_mat.bmp", *imgOriginal);
}
Can anybody spot my error?
Since your image is stretched horizontally, I'm betting that you have the wrong pixel format selected. (It's not stretched vertically, nor skewed diagonally, so the stride is correct.) CV_8UC3 specifies 24 bits per pixel, but I think that your BMP file is using 32 bits per pixel.
Switch your pixel format to CV_8UC4, or better yet, read the number of bits per pixel from the image and select the correct CV format based on that.
Side note: Since you're doing sc.WriteBitmapToFile() followed by opencv.DoSomething(Image.FromFile(), the entire bit about how you're capturing the screenshot is irrelevant. You're reading the bitmap from a file; that's all that matters.
I'm trying to do screen captures by capturing a specific window and in order to accurately figure out the size of the window to capture I want to use DwmGetWindowAttribute(). When I call this function with PInvoke on Windows 10 the Rect structure is always empty even though the result value is 0 (success). The Window handle passed in is valid as well because there is fallback code that calls GetWindowRect() which works (albeit with border problems).
I'm a bit at a loss. I used this same code a while back (perhaps on Windows 8.1?) and the same code seemed to be working but now no matter what I do the call to the function always returns an empty structure.
Here's the relevant code.
Definitions:
[DllImport("dwmapi.dll")]
static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out Rect pvAttribute, int cbAttribute);
[Flags]
public enum DwmWindowAttribute : uint
{
DWMWA_NCRENDERING_ENABLED = 1,
DWMWA_NCRENDERING_POLICY,
DWMWA_TRANSITIONS_FORCEDISABLED,
DWMWA_ALLOW_NCPAINT,
DWMWA_CAPTION_BUTTON_BOUNDS,
DWMWA_NONCLIENT_RTL_LAYOUT,
DWMWA_FORCE_ICONIC_REPRESENTATION,
DWMWA_FLIP3D_POLICY,
DWMWA_EXTENDED_FRAME_BOUNDS,
DWMWA_HAS_ICONIC_BITMAP,
DWMWA_DISALLOW_PEEK,
DWMWA_EXCLUDED_FROM_PEEK,
DWMWA_CLOAK,
DWMWA_CLOAKED,
DWMWA_FREEZE_REPRESENTATION,
DWMWA_LAST
}
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public Rectangle ToRectangle()
{
return Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
}
Code to do the capture:
public static Rectangle GetWindowRectangle(IntPtr handle)
{
Rectangle rected = Rectangle.Empty;
Rect rect = new Rect();
if (Environment.OSVersion.Version.Major < 6)
{
GetWindowRect(handle, out rect);
rected = rect.ToRectangle();
}
else
{
int size = Marshal.SizeOf(typeof(Rect));
int res = DwmGetWindowAttribute(handle, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out rect, size);
Debug.WriteLine(res.ToString("x") + " " + size + " " + handle + " " + (int) DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS);
// allow returning of desktop and aero windows
if (rected.Width == 0)
{
GetWindowRect(handle, out rect);
rected = rect.ToRectangle();
Debug.WriteLine("Using GetWindowRect");
}
}
Debug.WriteLine(rected.ToString());
return rected;
}
It feels like something simple is missing here. Any ideas?
Based on the Rick Strahl original code as well as Hans Passant correction I created a more compact version of GetWindowsRectangle. I tested it on Windows 10, here's the code in case it helps someone in the future:
public static Rectangle GetWindowRectangle(IntPtr handle)
{
Rect rect = new Rect();
if (Environment.OSVersion.Version.Major >= 6)
{
int size = Marshal.SizeOf(typeof(Rect));
DwmGetWindowAttribute(handle, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out rect, size);
}
else if (Environment.OSVersion.Version.Major < 6 || rect.ToRectangle().Width == 0)
{
GetWindowRect(handle, out rect);
}
return rect.ToRectangle();
}
Use GetWindowRect instead of DwmGetWindowAttribute to receive RECT of the window.
[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
Mouse stimulation using SendInput works perfectly on MainDisplay. However when I use SendInput for extended screen (e.g. Second screen placed to the left of the main display in my case. Issues is replicable irrespective of the extended display any place around main display but with different resolution then main display):
If I use SendInput on extended screen, the mouse position has offset in both X and Y position, ever so slightly ranging from 40 to 80 points in x and 10 to 20 points in Y based on if X (width) and Y(height) of extended screen is different to main display width/height)
Thanks in advance for any support as to why difference on extended screen
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorPos(ref Win32Point pt);
[DllImport("user32.dll")]
internal static extern bool SetCursorPos(int X, int Y);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
internal enum SendInputEventType : int
{
InputMouse,
InputKeyboard
}
[DllImport("user32.dll", SetLastError = true)]
private static extern uint SendInput(uint nInputs, ref Input pInputs, int cbSize);
public struct Input
{
public uint InputType;
public MouseInput MI;
}
public struct MouseInput
{
public int Dx;
public int Dy;
public uint MouseData;
public uint DwFlags;
public uint Time;
public IntPtr DwExtraInfo;
}
public enum MouseEventInfo
{
mouseEventfMove = 0x0001,
mouseEventfLeftdown = 0x0002,
mouseEventfLeftup = 0x0004,
mouseEventfRightdown = 0x0008,
mouseEventfRightup = 0x0010,
mouseEventfWheel = 0x0800,
mouseEventfAbsolute = 0x8000,
wheelDelta = 0x0078
}
static int CalculateAbsoluteCoordinateX(int x, System.Drawing.Rectangle currentBounds)
{
return ((currentBounds.X + x) * 65536) / (currentBounds.Width);
}
static int CalculateAbsoluteCoordinateY(int y, System.Drawing.Rectangle currentBounds)
{
return (((currentBounds.Y + y) * 65536) / currentBounds.Height);
}
// for me screen at index 0 (screen no 1) is main display. Screen id 2
//placed to the left of the main display as per resolution screen i.e.at
//index 1 (Screen.AllScreens[1]) is extended display and Bound.X is a -ve value
public static int ScreenId = 2;
public static System.Drawing.Rectangle CurrentBounds
{
get
{
return SysForms.Screen.AllScreens[ScreenId - 1].Bounds;
}
}
public static void ClickLeftMouseButton(int x, int y)
{
Input mouseInput = new Input();
mouseInput.InputType = SendInputEventType.InputMouse;
mouseInput.MI.Dx = CalculateAbsoluteCoordinateX(x, CurrentBounds);
mouseInput.MI.Dy = CalculateAbsoluteCoordinateY(y, CurrentBounds);
mouseInput.MI.MouseData = 0;
mouseInput.MI.DwFlags = MouseEventInfo.mouseEventfMove | MouseEventInfo.mouseEventfAbsolute;
SendInput(1, ref mouseInput, Marshal.SizeOf(new INPUT()));
mouseInput.MI.DwFlags = MouseEventInfo.mouseEventfLeftdown;
SendInput(1, ref mouseInput, Marshal.SizeOf(new INPUT()));
mouseInput.MI.DwFlags = MouseEventFlags.mouseEventfLeftup;
SendInput(1, ref mouseInput, Marshal.SizeOf(new INPUT()));
}
//Below is code of the WPF MainWindow for testing. Two buttons with click event.
// For main display with screenid as 1 both setcursor position and sendinput
//work perfectly, as I get the MousePosition, but when I apply this to
//extended screen (currently with two screen, main display is screen 1 in my
//case and screen 2 is extended screen, they put the mouse at two different positions.
//I have my doubts the way I am using the extended screen Bounds.X, but
//haven't will able to fix the issue
int x = 600;
int y = 300;
private void btnSend_Click(object sender, RoutedEventArgs e)
{
SetCursorPos(SysForms.Screen.AllScreens[ScreenId - 1].Bounds.X + x, SysForms.Screen.AllScreens[screenId - 1].Bounds.Y + y);
}
private void btnSend1_Click(object sender, RoutedEventArgs e)
{
ClickLeftMouseButton(x, y);
}
Found the issue. While using SendInput, the conversion of x,y in absolute value must be done in relation to Main/Primary screen.
Thus the changes:
static int CalculateAbsoluteCoordinateX(int x, System.Drawing.Rectangle currentBounds)
{
return ((currentBounds.X + x) * 65536) / (SystemParameters.PrimaryScreenWidth);
}
static int CalculateAbsoluteCoordinateY(int y, System.Drawing.Rectangle currentBounds)
{
return (((currentBounds.Y + y) * 65536) / SystemParameters.PrimaryScreenHeight);
}
Let me start by saying that I am no stranger to using winapi calls to manipulate other windows, but this is the first time I have seen a window that has two identical control IDs. It seems that the color dialog hasn't changed much between windows versions and I can confirm that this behavior exists on all color dialogs from Windows Vista through to Windows 10 (possibly exists in win xp and lower as well but I can't be bothered to check).
What I am attempting to do is use winapi calls to localize the text in a color dialog control in C#. The best way I have found to do this is to use GetDlgItem() to get a handle to the control I wish to change and then use SetWindowText() to actually change the text. This works great for all controls on the color dialog except for the 'Basic colors:' and 'Custom colors:' labels, which both have a control ID of 0xFFFF (decimal value: 65535).
I use an app called WinID to do this type of work (I find it much easier than using Spy++) and you can see from the screenshots below that the ID of the two text labels do in-fact register as the same ID.
NOTE: I have
tested this using Spy++ and of course I get the same values as shown below:
I would like to know 2 things:
How is it possible for 2 controls to have the same control id?
Is there a 'better way' to get a handle to a control from an external dialog/app using winapi calls? Please keep in mind that using something like FindWindowEx(nColorDialogHandle, IntPtr.Zero, "Static", "&custom colors:"); works, but is not useful to me because I must be able to find the handle without relying on the text in English since this must also work on color dialogs from a non-English version of Windows.
Below is some sample code to demonstrate how I am currently able to change the text on a color dialog. I am happy with the code except that I am unable to get a direct handle to the 'Custom colors:' label since using GetDlgItem() with the control id of 0xFFFF seems to return a handle to the first instance of the control with that ID (in this case it always returns a handle to the 'Basic colors:' label). The only way I am able to get the 'Custom colors:' handle is by using an indirect method of looping through all controls on the color dialog until I find one with text that has not already been changed. This works fine but I would like to know if there is a more direct way to get this handle without looping through controls:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// Open the color dialog before the form actually loads
ColorDialogEx oColorDialog = new ColorDialogEx(this.CreateGraphics());
oColorDialog.FullOpen = true;
oColorDialog.ShowDialog();
}
}
public class ColorDialogEx : ColorDialog
{
private const Int32 WM_INITDIALOG = 0x0110; // Windows Message Constant
private Graphics oGraphics;
private const uint GW_HWNDLAST = 1;
private const uint GW_HWNDPREV = 3;
private string sColorPickerText = "1-Color Picker";
private string sBasicColorsText = "2-Basic colors:";
private string sDefineCustomColorsButtonText = "3-Define Custom Colors >>";
private string sOKButtonText = "4-OK";
private string sCancelButtonText = "5-Cancel";
private string sAddToCustomColorsButtonText = "6-Add to Custom Colors";
private string sColorText = "7-Color";
private string sSolidText = "|8-Solid";
private string sHueText = "9-Hue:";
private string sSatText = "10-Sat:";
private string sLumText = "11-Lum:";
private string sRedText = "12-Red:";
private string sGreenText = "13-Green:";
private string sBlueText = "14-Blue:";
private string sCustomColorsText = "15-Custom colors:";
// WinAPI definitions
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SetWindowText(IntPtr hWnd, string text);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
public static extern long GetWindowRect(int hWnd, ref Rectangle lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetTitleBarInfo(IntPtr hwnd, ref TITLEBARINFO pti);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll")]
private static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern int GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[StructLayout(LayoutKind.Sequential)]
struct TITLEBARINFO
{
public const int CCHILDREN_TITLEBAR = 5;
public uint cbSize;
public RECT rcTitleBar;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CCHILDREN_TITLEBAR + 1)]
public uint[] rgstate;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }
public int X
{
get { return Left; }
set { Right -= (Left - value); Left = value; }
}
public int Y
{
get { return Top; }
set { Bottom -= (Top - value); Top = value; }
}
public int Height
{
get { return Bottom - Top; }
set { Bottom = value + Top; }
}
public int Width
{
get { return Right - Left; }
set { Right = value + Left; }
}
public System.Drawing.Point Location
{
get { return new System.Drawing.Point(Left, Top); }
set { X = value.X; Y = value.Y; }
}
public System.Drawing.Size Size
{
get { return new System.Drawing.Size(Width, Height); }
set { Width = value.Width; Height = value.Height; }
}
public static implicit operator System.Drawing.Rectangle(RECT r)
{
return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
}
public static implicit operator RECT(System.Drawing.Rectangle r)
{
return new RECT(r);
}
public static bool operator ==(RECT r1, RECT r2)
{
return r1.Equals(r2);
}
public static bool operator !=(RECT r1, RECT r2)
{
return !r1.Equals(r2);
}
public bool Equals(RECT r)
{
return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
}
public override bool Equals(object obj)
{
if (obj is RECT)
return Equals((RECT)obj);
else if (obj is System.Drawing.Rectangle)
return Equals(new RECT((System.Drawing.Rectangle)obj));
return false;
}
public override int GetHashCode()
{
return ((System.Drawing.Rectangle)this).GetHashCode();
}
public override string ToString()
{
return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
}
}
public ColorDialogEx(Graphics g)
{
oGraphics = g;
}
protected override IntPtr HookProc(IntPtr nColorDialogHandle, int msg, IntPtr wparam, IntPtr lparam)
{
IntPtr returnValue = base.HookProc(nColorDialogHandle, msg, wparam, lparam);
if (msg == WM_INITDIALOG)
{
IntPtr[] oStaticHandleArray = new IntPtr[9];
// Change the window title
SetWindowText(nColorDialogHandle, sColorPickerText);
// Get titlebar info for calculations later
TITLEBARINFO oTITLEBARINFO = new TITLEBARINFO();
oTITLEBARINFO.cbSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(oTITLEBARINFO);
GetTitleBarInfo(nColorDialogHandle, ref oTITLEBARINFO);
// Change the text of the "Basic colors:" label
oStaticHandleArray[0] = GetDlgItem(nColorDialogHandle, 0xFFFF);
SetWindowText(oStaticHandleArray[0], sBasicColorsText);
// Change the text of the "Define Custom Colors >>" button
SetWindowText(GetDlgItem(nColorDialogHandle, 0x2CF), sDefineCustomColorsButtonText);
// Save the "OK" button size and new width
Rectangle oOKButtonRect = new Rectangle();
int nOKButtonWidth = (int)oGraphics.MeasureString(sOKButtonText, new Font("Microsoft Sans Serif", 8, FontStyle.Regular)).Width + 20; // +20 accounts for extra +10 padding on either side
// Find the "OK" Button
IntPtr nChildHandle = GetDlgItem(nColorDialogHandle, 0x1);
if (nChildHandle.ToInt32() > 0)
{
// The "OK" button was found
// Now save the current size and position
GetWindowRect(nChildHandle.ToInt32(), ref oOKButtonRect);
// We have to subtract oOKButtonRect.X value from oOKButtonRect.Width to obtain the "real" button width (same thing with subtracting Y value from Height)
oOKButtonRect.Width = oOKButtonRect.Width - oOKButtonRect.X;
oOKButtonRect.Height = oOKButtonRect.Height - oOKButtonRect.Y;
// Resize the "OK" button so that the new text fits properly
// NOTE: I cannot be sure 100% if it is correct to use the titlebar to find the position of the button or not but the math works out in all of my tests
MoveWindow(nChildHandle, oOKButtonRect.X - oTITLEBARINFO.rcTitleBar.X, oOKButtonRect.Y - oTITLEBARINFO.rcTitleBar.Y - oTITLEBARINFO.rcTitleBar.Height, nOKButtonWidth, oOKButtonRect.Height, true);
// Finally, change the button text
SetWindowText(nChildHandle, sOKButtonText);
}
// Find the "Cancel" Button
nChildHandle = GetDlgItem(nColorDialogHandle, 0x2);
if (nChildHandle.ToInt32() > 0)
{
// The "Cancel" button was found
// Now get the current size and position
Rectangle oCancelButtonRect = new Rectangle();
GetWindowRect(nChildHandle.ToInt32(), ref oCancelButtonRect);
// We have to subtract oCancelButtonRect.X value from oCancelButtonRect.Width to obtain the "real" button width (same thing with subtracting Y value from Height)
oCancelButtonRect.Width = oCancelButtonRect.Width - oCancelButtonRect.X;
oCancelButtonRect.Height = oCancelButtonRect.Height - oCancelButtonRect.Y;
// Resize the "Cancel" button so that the new text fits properly
// NOTE: I cannot be sure 100% if it correct to use the titlebar to find the position of the button or not but the math works out in all of my tests
MoveWindow(nChildHandle, oOKButtonRect.X + nOKButtonWidth - oTITLEBARINFO.rcTitleBar.X + 6, oCancelButtonRect.Y - oTITLEBARINFO.rcTitleBar.Y - oTITLEBARINFO.rcTitleBar.Height, (int)oGraphics.MeasureString(sCancelButtonText, new Font("Microsoft Sans Serif", 8, FontStyle.Regular)).Width + 20, oCancelButtonRect.Height, true);
// Finally, change the button text
SetWindowText(nChildHandle, sCancelButtonText);
}
// Change the text of the "Add to Custom Colors" button
SetWindowText(GetDlgItem(nColorDialogHandle, 0x2C8), sAddToCustomColorsButtonText);
// Change the text of the "Color" label text
oStaticHandleArray[1] = GetDlgItem(nColorDialogHandle, 0x2DA);
SetWindowText(oStaticHandleArray[1], sColorText);
// Change the text of the "Solid" label text
oStaticHandleArray[2] = GetDlgItem(nColorDialogHandle, 0x2DB);
SetWindowText(oStaticHandleArray[2], sSolidText);
// Change the text of the "Hue:" label
oStaticHandleArray[3] = GetDlgItem(nColorDialogHandle, 0x2D3);
SetWindowText(oStaticHandleArray[3], sHueText);
// Change the text of the "Sat:" label
oStaticHandleArray[4] = GetDlgItem(nColorDialogHandle, 0x2D4);
SetWindowText(oStaticHandleArray[4], sSatText);
// Change the text of the "Lum:" label
oStaticHandleArray[5] = GetDlgItem(nColorDialogHandle, 0x2D5);
SetWindowText(oStaticHandleArray[5], sLumText);
// Change the text of the "Red:" label
oStaticHandleArray[6] = GetDlgItem(nColorDialogHandle, 0x2D6);
SetWindowText(oStaticHandleArray[6], sRedText);
// Change the text of the "Green:" label
oStaticHandleArray[7] = GetDlgItem(nColorDialogHandle, 0x2D7);
SetWindowText(oStaticHandleArray[7], sGreenText);
// Change the text of the "Blue:" label
oStaticHandleArray[8] = GetDlgItem(nColorDialogHandle, 0x2D8);
SetWindowText(oStaticHandleArray[8], sBlueText);
// Change the text of the "Custom Colors:" label
SetCustomColorsText(nColorDialogHandle, oStaticHandleArray);
}
return returnValue;
}
private static string GetClassName(IntPtr nHandle)
{
// Create the stringbuilder object that is used to get the window class name from the GetClassName win api function
System.Text.StringBuilder sClassName = new System.Text.StringBuilder(100);
GetClassName(nHandle, sClassName, sClassName.Capacity);
return sClassName.ToString();
}
private static string GetWindowText(IntPtr nHandle)
{
// Create the stringbuilder object that is used to get the window text from the GetWindowText win api function
System.Text.StringBuilder sWindowText = new System.Text.StringBuilder(100);
GetWindowText(nHandle, sWindowText, sWindowText.Capacity);
return sWindowText.ToString();
}
private void SetCustomColorsText(IntPtr nHandle, IntPtr[] oStaticHandleArray)
{
// Find the last control based on the handle to the main window
IntPtr nWorkingHandle = GetWindow(FindWindowEx(nHandle, IntPtr.Zero, null, null), GW_HWNDLAST);
bool bFound = false;
do
{
// Look only for "Static" controls that we have not already changed
if (GetClassName(nWorkingHandle) == "Static" && oStaticHandleArray.Contains(nWorkingHandle) == false)
{
// Found a "Static" control
// Check to see if it is the one we are looking for
string sControlText = GetWindowText(nWorkingHandle);
if (sControlText != "")
{
// Found the "Custom Colors:" label
// Change the text of the "Custom Colors:" label
SetWindowText(nWorkingHandle, sCustomColorsText);
bFound = true;
}
}
// Working backwards we look for the previous control
nWorkingHandle = GetWindow(nWorkingHandle, GW_HWNDPREV);
// Jump out of the loop when the working handle doesn't find anymore controls
if (nWorkingHandle == IntPtr.Zero)
break;
} while (bFound == false);
}
}
}
This dialog is already localized. You can look at it with Visual Studio. Copy c:\windows\system32\en-US\comdlg32.dll.mui to, say, c:\temp\test.dll. Replace "en-US" with your local language tag. In VS use File > Open > File and pick test.dll. You'll see the resources in the MUI file, open the Dialog node and double-click the one named "CHOOSECOLOR". The resource editor opens, you can pick an item in the dialog template and look at its properties in the Property window.
Hopefully it is obvious why the STATIC control has the default IDSTATIC id (65535), there is no need for Windows to do anything to change its properties so no need to find it back. And not for you either, your user will have his own copy of the MUI file that contains the dialog with strings in his native language.
Do note that a machine usually only has MUI files for a single language. If you need to do this to, say, create screenshots for documentation then start here.
When I do this;
Point startpoint = Cursor.Position;
startpoint.Y -= 1;
DoMouse(MOUSEEVENTF.MOVE | MOUSEEVENTF.ABSOLUTE, startpoint);
The mouse doesn't just move up.. it moves a bit to the left as well. But if I do it in a loop, it only moves to the left at the first iteration.
Here is a fully working console program presenting the problem. You have to Add Reference -> .NET -> System.Drawing and System.Windows.Forms to get it to compile.
When starting the program type start to move the mouse up 5 pixels once or type start X (X being a number) to move the mouse up 5 pixels X times. You will see that each new loop the mouse will move a bit to the left; it shouldn't be doing that at all.
using System;
using System.Text.RegularExpressions;
using System.Threading;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace mousemove_temp
{
class Program
{
//Capture user input
static void Main(string[] args)
{
while (true)
{
string s = Console.ReadLine();
switch (s)
{
case("start"):
moveMouseTest(1);
break;
default:
//Get # of times to run function
Match match = Regex.Match(s, #"start (.+)", RegexOptions.IgnoreCase);
if (!match.Success || match.Groups.Count != 2) break;
//Copy # to int
int amnt = -1;
try
{
amnt = Int32.Parse(match.Groups[1].Value);
}
catch (Exception) { break; } //fail
if (amnt <= -1) break; //fail
moveMouseTest(amnt); //aaaawww yeah
break;
}
Thread.Sleep(10);
}
}
//Move the mouse
static void moveMouseTest(int repeat)
{
int countrepeat = 0;
//Loop entire function X times
while (countrepeat < repeat)
{
Point startpoint = Cursor.Position;
int amount = 5; //Move 5 pixels
int counter = 0;
//Move 1 pixel up each loop
while (counter < amount)
{
startpoint.Y -= 1;
DoMouse(MOUSEEVENTF.MOVE | MOUSEEVENTF.ABSOLUTE, startpoint);
counter++;
Thread.Sleep(100); //Slow down so you can see it only jumps left the first time
}
countrepeat++;
Console.WriteLine(String.Format("{0}/{1}", countrepeat, repeat));
Thread.Sleep(1000); //Wait a second before next loop
}
}
/*
* Function stuff
*/
//Control the Mouse
private static object mouselock = new object(); //For use with multithreading
public static void DoMouse(MOUSEEVENTF flags, Point newPoint)
{
lock (mouselock)
{
INPUT input = new INPUT();
MOUSEINPUT mi = new MOUSEINPUT();
input.dwType = InputType.Mouse;
input.mi = mi;
input.mi.dwExtraInfo = IntPtr.Zero;
// mouse co-ords: top left is (0,0), bottom right is (65535, 65535)
// convert screen co-ord to mouse co-ords...
input.mi.dx = newPoint.X * (65535 / Screen.PrimaryScreen.Bounds.Width);
input.mi.dy = newPoint.Y * (65535 / Screen.PrimaryScreen.Bounds.Height);
input.mi.time = 0;
input.mi.mouseData = 0;
// can be used for WHEEL event see msdn
input.mi.dwFlags = flags;
int cbSize = Marshal.SizeOf(typeof(INPUT));
int result = SendInput(1, ref input, cbSize);
if (result == 0)
Console.WriteLine("DoMouse Error:" + Marshal.GetLastWin32Error());
}
}
/*
* Native Methods
*/
[DllImport("user32.dll", SetLastError = true)]
static internal extern Int32 SendInput(Int32 cInputs, ref INPUT pInputs, Int32 cbSize);
[DllImport("user32.dll")]
public static extern bool GetAsyncKeyState(Int32 vKey);
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 28)]
internal struct INPUT
{
[FieldOffset(0)]
public InputType dwType;
[FieldOffset(4)]
public MOUSEINPUT mi;
[FieldOffset(4)]
public KEYBDINPUT ki;
[FieldOffset(4)]
public HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct MOUSEINPUT
{
public Int32 dx;
public Int32 dy;
public Int32 mouseData;
public MOUSEEVENTF dwFlags;
public Int32 time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct KEYBDINPUT
{
public Int16 wVk;
public Int16 wScan;
public KEYEVENTF dwFlags;
public Int32 time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct HARDWAREINPUT
{
public Int32 uMsg;
public Int16 wParamL;
public Int16 wParamH;
}
internal enum InputType : int
{
Mouse = 0,
Keyboard = 1,
Hardware = 2
}
[Flags()]
internal enum MOUSEEVENTF : int
{
MOVE = 0x1,
LEFTDOWN = 0x2,
LEFTUP = 0x4,
RIGHTDOWN = 0x8,
RIGHTUP = 0x10,
MIDDLEDOWN = 0x20,
MIDDLEUP = 0x40,
XDOWN = 0x80,
XUP = 0x100,
VIRTUALDESK = 0x400,
WHEEL = 0x800,
ABSOLUTE = 0x8000
}
[Flags()]
internal enum KEYEVENTF : int
{
EXTENDEDKEY = 1,
KEYUP = 2,
UNICODE = 4,
SCANCODE = 8
}
}
}
Can anybody tell what's going wrong?
You're doing the math wrong and as a result are getting rounding errors.
For example, 65535 / 1920 = 34.1328125. But truncation (because you are dividing an int by an int) is resulting in 34. So if on a 1920x1080 screen you had the mouse all the way at the right, you would get 1920 * (65535 / 1920) = 1920 * 34 = 65280.
This will get you better results:
input.mi.dx = (int)((65535.0f * (newPoint.X / (float)Screen.PrimaryScreen.Bounds.Width)) + 0.5f);
input.mi.dy = (int)((65535.0f * (newPoint.Y / (float)Screen.PrimaryScreen.Bounds.Height)) + 0.5f);
Though if you're determined to use P/Invoke rather than just say
Cursor.Position = new Point(newPoint.X, newPoint.Y);
then you really should use SetCursorPos - http://msdn.microsoft.com/en-us/library/windows/desktop/ms648394(v=vs.85).aspx - since that (along with GetCursorPos) is the API that .NET is using to get and set the cursor position via Cursor.Position.
Simplest way for your project is useful open-source library Windows Input Simulator (C# SendInput Wrapper - Simulate Keyboard and Mouse) on codeplex. Use it!