I am using sendkeys in C# .NET. I have letters, arrow keys and enter working. I cant figure out how to send a right click for the context menu. I know i can press a key on my keyboard to do it but i have no idea how to send the msg. How do i? i googled and saw
new MenuItem().PerformClick();
as a solution however i didnt see any affect. The keys are being sent to another application.
You can wrap the user32.dll, I got the general idea from here
EDIT:
I added in posX and posY, which would be the mouse coordinates.
using System;
using System.Runtime.InteropServices;
namespace WinApi
{
public class Mouse
{
[DllImport("user32.dll")]
private static extern void mouse_event(UInt32 dwFlags,UInt32 dx,UInt32 dy,UInt32 dwData,IntPtr dwExtraInfo);
private const UInt32 MouseEventRightDown = 0x0008;
private const UInt32 MouseEventRightUp = 0x0010;
public static void SendRightClick(UInt32 posX, UInt32 posY)
{
mouse_event(MouseEventRightDown, posX, posY, 0, new System.IntPtr());
mouse_event(MouseEventRightUp, posX, posY, 0, new System.IntPtr());
}
}
}
Assuming you are referring to the key positioned a few places right of the spacebar, which performs the same operation as the right mouse button in some situations, {MENU} may be the special key you want to send. It is not implemented in some SendKeys variations, and I am unsure of the latest version of C#.NET.
You cannot send mouse input using the .NET SendKeys class. At least, not that I know of nor that's documented. The best way to do this is to switch to the WinAPI and use the SendInput method. You can use this in .NET using DllImport for the function (in "user32.dll") and StructLayout for the structures.
Then you will want to call it like this:
INPUT pressRight;
pressRight.type = MOUSE; // = 0
pressRight.mi.dx = 0;
pressRight.mi.dy = 0;
pressRight.mi.mouseData = 0;
pressRight.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; // = 8
pressRight.mi.time = 0;
pressRight.mi.dwExtraInfo = IntPtr.Zero;
INPUT releaseRight = pressRight;
releaseRight.mi.dwFlags = MOUSEEVENTF_RIGHTUP; // = 10
INPUT[] inputs = new INPUT[2];
inputs[0] = pressRight;
inputs[1] = releaseRight;
SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT)));
The {MENU} key is not always avialable, as noted by #Sparr. However, shift-F10 brings up the context menu in most Windows applications. So SendKeys.SendWait("+{F10}"); should work.
Related
I want to make a mouse macroer. Which can both do simulated mouse events, or using my computers own cursor on screen.
The macro shall be created by typing in methods in the IDE. These methods shall then execute mouse events on a certain .exe´s window. By using coordinates.
For example this is my goal of a method executing a simulated or not simulated mouse left click on a certain .exe´s window:
Psuedo code:
//Following method left clicks with the offset (x, y)
//from the windows top left corner. If the bool isSimulated
//is set to true the click will be simulated else the computers
//own mouse cursor will be moved and execute the mouse event.
LeftMouseClickOnWindow(x, y, isSimulated);
To chrisp the problem even more, simulated mouse clicks should function while the window is minimized or when unfocused.
I am wondering what the best approach to create this kind of util is.
Is user32.dll´s functions a good approach?
Is it easier to do it in C++ rather than C#?
Any advices, sources, example codes and comments is warmly appreciated!
Both C++ and C# are great. AutoHotKey can do the job, but I'm like you - I love to write my own stuff. Another option is AutoIt, and you can use its dll in your C# project... but then you have to make sure that it's installed on every system... not a luxury that I've encountered often.
Here's something to play around with. Hopefully it'll get you going... note that it's C#. Before you run this code, make sure that you don't have anything important open where your mouse is at... this will move 20 times in a diagonal to the lower right and perform a click every time it moves. You don't want this to close your stuff accidentally. So, just minimize it all, prior to running this.
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ConsoleApplication
{
class Program
{
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
private const int MOUSEEVENTF_LEFTDOWN = 0x02;
private const int MOUSEEVENTF_LEFTUP = 0x04;
//private const int MOUSEEVENTF_RIGHTDOWN = 0x08;
//private const int MOUSEEVENTF_RIGHTUP = 0x10;
public void DoMouseStuff()
{
Cursor.Current = new Cursor(Cursor.Current.Handle);
var point = new Point(Cursor.Position.X, Cursor.Position.Y);
for (int i = 0; i < 20; i++, point.X += 10, point.Y += 10)
{
Cursor.Position = point;
Program.mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, (uint)Cursor.Position.X, (uint)Cursor.Position.Y, 0, 0);
System.Threading.Thread.Sleep(100);
}
}
static void Main(string[] args)
{
var prog = new Program();
prog.DoMouseStuff();
}
}
}
You'll need to set references for System.Windows.Forms & System.Drawing, if you don't have those set already. I made it as a console app, so, setting for me was required. As you notice, I included System.Threading.Thread.Sleep(100);... this is so that you can see what's going on. So, I'm basically slowing down the whole thing. It moves and it clicks every time it moves (which is approximately once every 100 milliseconds).
Familiarize yourself with the Cursor and user32.dll.
Last, but not least, here's MSDN documentation on mouse & keyboard simulation: http://msdn.microsoft.com/en-us/library/ms171548.aspx
I'm trying to create a DateTimePicker with week numbers displayed, as shown here (Code project example).
It works fairly well, except for one minor detail; The calender popping up when trying to select a date is not the right size. As you can see, the calendar area is sort of "cramped", especially along the right edge.
I can click the bottom right corner here, and drag it out a little - just enough to expand it so that it looks right:
I can't seem to find any way to force the calendar to be the correct/full size from the beginning, or to resize it.
Finally found a solution that seems to work - at least for now.
It seems there are two windows in the calendar part of the DateTimePicker. Apparently my code would automatically find the correct size for the inner one (more or less at least?), but not the outer one.
A bit of research has led to the code below. The following links provide some useful and relevant info:
GetWindowLong function (Used for getting info about the window to edit)
GetParent function (Finding the outer window, so we could apply settings to that too)
The trick was to add a little to the height and width of the (inner) window, then apply the same height and width to the outer window (which I access using the GetParrent() function). I found the "correct" size by trial and error: When the size matched what was needed for the contents of the calendar, it could not be resized any longer.
Yes, this feels a little like a hack, and no, I haven't been able to verify that it works perfectly on other computers than my own yet. I'm a little worried about having to give specific values for height and width, but I'm hoping this won't be affected by screen resolutions or whatever else.
Hope someone else in a similar situation will find the code useful.
(The following can directly replace a regular DateTimePicker to show week numbers in the calendar)
using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class DatePickerWithWeekNumbers : DateTimePicker
{
[DllImport("User32.dll")]
private static extern int GetWindowLong(IntPtr handleToWindow,
int offsetToValueToGet);
[DllImport("User32.dll")]
private static extern int SetWindowLong(IntPtr h,
int index,
int value);
private const int McmFirst = 0x1000;
private const int McmGetminreqrect = (McmFirst + 9);
private const int McsWeeknumbers = 0x4;
private const int DtmFirst = 0x1000;
private const int DtmGetmonthcal = (DtmFirst + 8);
[DllImport("User32.dll")]
private static extern IntPtr SendMessage(IntPtr h,
int msg,
int param,
int data);
[DllImport("User32.dll")]
private static extern IntPtr GetParent(IntPtr h);
[DllImport("User32.dll")]
private static extern int SendMessage(IntPtr h,
int msg,
int param,
ref Rectangle data);
[DllImport("User32.dll")]
private static extern int MoveWindow(IntPtr h,
int x,
int y,
int width,
int height,
bool repaint);
[Browsable(true), DesignerSerializationVisibility(
DesignerSerializationVisibility.Visible)]
public bool DisplayWeekNumbers { get; set; }
protected override void OnDropDown(EventArgs e)
{
// Hex value to specify that we want the style-attributes
// for the window:
const int offsetToGetWindowsStyles = (-16);
IntPtr pointerToCalenderWindow = SendMessage(Handle,
DtmGetmonthcal,
0,
0);
int styleForWindow = GetWindowLong(pointerToCalenderWindow,
offsetToGetWindowsStyles);
// Check properties for the control - matches available
// property in the graphical properties for the DateTimePicker:
if (DisplayWeekNumbers)
{
styleForWindow = styleForWindow | McsWeeknumbers;
}
else
{
styleForWindow = styleForWindow & ~McsWeeknumbers;
}
// Get the size needed to display the calendar (inner window)
var rect = new Rectangle();
SendMessage(pointerToCalenderWindow, McmGetminreqrect, 0, ref rect);
// Add to size as needed (I don't know why
// this was not correct initially!)
rect.Width = rect.Width + 28;
rect.Height = rect.Height + 6;
// Set window styles..
SetWindowLong(pointerToCalenderWindow,
offsetToGetWindowsStyles,
styleForWindow);
// Dont move the window - just resize it as needed:
MoveWindow(pointerToCalenderWindow,
0,
0,
rect.Right,
rect.Bottom,
true);
// Now access the parrent window..
var parentWindow = GetParent(pointerToCalenderWindow);
// ...and resize that the same way:
MoveWindow(parentWindow, 0, 0, rect.Right, rect.Bottom, true);
base.OnDropDown(e);
}
}
For me, setting MCS_WEEKNUMBERS via the DateTimePicker's DTM_SETMCSTYLE message automatically resulted in the correct size of the MonthCal control:
SendMessage(Handle, DTM_FIRST + 11, 0, SendMessage(Handle, DTM_FIRST + 12, 0, 0) | MCS_WEEKNUMBERS);
Where DTM_FIRST = 0x1000 and MCS_WEEKNUMBERS = 0x4 as in Kjartan's solution. DTM_FIRST + 11 is DTM_SETMCSTYLE and DTM_FIRST + 12 is DTM_GETMCSTYLE in Microsoft's documentation.
Unlike Kjartan's solution, this call must be used before the first dropdown, but right at form initialization didn't work for me in some cases, so I delayed it to when the form was already created and visible in these cases. One call is enough, the DateTimePicker will save the style for future dropdowns.
Ok, Try to comment line in Program.cs
Application.EnableVisualStyles();
and then try to execute.
I'm trying to make Teamviewer like piece of software for fun, which allows one person to view another person's screen and click and all that. Anyway, I have most all of the socket stuff done, but I don't know how to get the mouse clicks to work correctly. Here is the code I found online for moving the mouse programmatically:
public static class VirtualMouse
{
// import the necessary API function so .NET can
// marshall parameters appropriately
[DllImport("user32.dll")]
static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
// constants for the mouse_input() API function
private const int MOUSEEVENTF_MOVE = 0x0001;
private const int MOUSEEVENTF_LEFTDOWN = 0x0002;
private const int MOUSEEVENTF_LEFTUP = 0x0004;
private const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
private const int MOUSEEVENTF_RIGHTUP = 0x0010;
private const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;
private const int MOUSEEVENTF_MIDDLEUP = 0x0040;
private const int MOUSEEVENTF_ABSOLUTE = 0x8000;
// simulates movement of the mouse. parameters specify changes
// in relative position. positive values indicate movement
// right or down
public static void Move(int xDelta, int yDelta)
{
mouse_event(MOUSEEVENTF_MOVE, xDelta, yDelta, 0, 0);
}
// simulates movement of the mouse. parameters specify an
// absolute location, with the top left corner being the
// origin
public static void MoveTo(int x, int y)
{
mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, x, y, 0, 0);
}
// simulates a click-and-release action of the left mouse
// button at its current position
public static void LeftClick()
{
mouse_event(MOUSEEVENTF_LEFTDOWN, Control.MousePosition.X, Control.MousePosition.Y, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, Control.MousePosition.X, Control.MousePosition.Y, 0, 0);
}
}
Now I want to move the mouse using the MoveTo method, but it requires crazy high numbers for any movement. Is there anyway I can match coordinates for moving here to the position on screen in pixels? Sorry if this seems like an obvious question, but I've googled for almost an hour and I can't find any discussion of what units are being used for the mouse x and y position, so I can't set up any sort of formula to match clicks on one panel to clicks on the user's screen.
From Microsoft's documentation:
If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain
normalized absolute coordinates between 0 and 65,535. The event
procedure maps these coordinates onto the display surface. Coordinate
(0,0) maps onto the upper-left corner of the display surface,
(65535,65535) maps onto the lower-right corner.
You can use that to convert the input in pixels to the desired value, like this:
var inputXinPixels = 200;
var inputYinPixels = 200;
var screenBounds = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
var outputX = inputXinPixels * 65535 / screenBounds.Width;
var outputY = inputYinPixels * 65535 / screenBounds.Height;
MoveTo(outputX, outputY);
Please keep in mind that this may not be correct for multiple monitors. Also notice that the documention says:
This function has been superseded. Use SendInput instead.
Addendum: As pointed by J3soon the above formula might not be the best. Based on research done for AutoHokey the internal the following code works better:
var outputX = (inputXinPixels * 65536 / screenBounds.Width) + 1;
var outputY = (inputYinPixels * 65536 / screenBounds.Height) + 1;
See AutoHotkey source code for reference.
If I were in your position I would use Cursor.Position. The following code works as expected:
System.Windows.Forms.Cursor.Position = new System.Drawing.Point(200, 200);
Yes, it places the mouse pointer in the coordinates (200, 200) pixels of the screen [Tested on LinqPad].
Addendum: I had a look at what does System.Windows.Forms.Cursor.Position use internally - On Mono on Windows at least. It is a call to SetCursorPos. No weird coordinate conversion needed.
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;
}
}
There must be a better way than a constrained numeric updown control.
The easiest way to enter numbers (especially non-integer numbers) in Windows Mobile (or in a regular Windows application) is to just have a text box that the users type into, and then validate that they've entered a proper number.
The problem with this approach in Windows Mobile is that the default SIP (Soft Input Panel aka little pop-up keyboard) looks like this:
alt text http://img510.imageshack.us/img510/6210/sipreg.jpg
On a real Windows Mobile device, the SIP looks even smaller than this, and it is a gigantic pain in the keister to hit the little number keys at the top correctly. What you want to use for this purpose is the Numeric mode, which you get by clicking the "123" button in the upper left corner, and looks like this:
alt text http://img16.imageshack.us/img16/6128/sipnum.jpg
The problem with this is that there is no (simple) way programatically to make this mode of the SIP appear instead of the regular keyboard. To get the SIP to appear in numeric mode, add a reference to your project to Microsoft.WindowsCE.Forms, and then add this code as a class named "SIPHandler" (you will have to change the namespace to your project's namespace):
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using Microsoft.WindowsCE.Forms;
namespace DeviceApplication1
{
/// <summary>
/// Handles showing and hiding of Soft Input Panel (SIP). Better to use these
/// methods than having an InputControl on a form. InputControls behave oddly
/// if you have multiple forms open.
/// </summary>
public class SIPHandler
{
public static void ShowSIP()
{
SipShowIM(1);
}
public static void ShowSIPNumeric()
{
SipShowIM(1);
SetKeyboardToNumeric();
}
public static void ShowSIPRegular()
{
SipShowIM(1);
SetKeyboardToRegular();
}
public static void HideSIP()
{
SipShowIM(0);
}
private static void SetKeyboardToRegular()
{
// Find the SIP window
IntPtr hWnd = FindWindow("SipWndClass", null);
// Go one level below as the actual SIP window is a child
hWnd = GetWindow(hWnd, GW_CHILD);
// Obtain its context and get a color sample
// The premise here is that the numeric mode is controlled by a virtual button in the top left corner
// Whenever the numeric mode is active, the button background will be of COLOR_WINDOW_TEXT
IntPtr hDC = GetDC(hWnd);
int pixel = GetPixel(hDC, 2, 2);
// Notice that we cannot simply compare the color to the system color as the system color is 24 bit (or palette)
// and the real color is dithered to 15-16 bits for most devices, so white (0xff, 0xff, 0xff) becomes
// almost white (oxf8, 0xfc, 0xf8)
// ken's hack: here we only want to simulate the click if the keyboard is in numeric mode, in
// which case the back color will be WindowText
//int clrText = (SystemColors.Window.R) | (SystemColors.Window.G << 8) | (SystemColors.Window.B << 16);
int clrText = (SystemColors.WindowText.R) | (SystemColors.WindowText.G << 8) | (SystemColors.WindowText.B << 16);
SetPixel(hDC, 2, 2, clrText);
int pixelNew = GetPixel(hDC, 2, 2);
// Restore the original pixel
SetPixel(hDC, 2, 2, pixel);
if (pixel == pixelNew)
{
// Simulate stylus click
Message msg = Message.Create(hWnd, WM_LBUTTONDOWN, new IntPtr(1), new IntPtr(0x00090009));
MessageWindow.SendMessage(ref msg);
msg = Message.Create(hWnd, WM_LBUTTONUP, new IntPtr(0), new IntPtr(0x00090009));
MessageWindow.SendMessage(ref msg);
}
// Free resources
ReleaseDC(hWnd, hDC);
}
private static void SetKeyboardToNumeric()
{
// Find the SIP window
IntPtr hWnd = FindWindow("SipWndClass", null);
// Go one level below as the actual SIP window is a child
hWnd = GetWindow(hWnd, GW_CHILD);
// Obtain its context and get a color sample
// The premise here is that the numeric mode is controlled by a virtual button in the top left corner
// Whenever the numeric mode is active, the button background will be of COLOR_WINDOW_TEXT
IntPtr hDC = GetDC(hWnd);
int pixel = GetPixel(hDC, 2, 2);
// Notice that we cannot simply compare the color to the system color as the system color is 24 bit (or palette)
// and the real color is dithered to 15-16 bits for most devices, so white (0xff, 0xff, 0xff) becomes
// almost white (oxf8, 0xfc, 0xf8)
int clrText = (SystemColors.Window.R) | (SystemColors.Window.G << 8) | (SystemColors.Window.B << 16);
SetPixel(hDC, 2, 2, clrText);
int pixelNew = GetPixel(hDC, 2, 2);
// Restore the original pixel
SetPixel(hDC, 2, 2, pixel);
if (pixel == pixelNew)
{
// Simulate stylus click
Message msg = Message.Create(hWnd, WM_LBUTTONDOWN, new IntPtr(1), new IntPtr(0x00090009));
MessageWindow.SendMessage(ref msg);
msg = Message.Create(hWnd, WM_LBUTTONUP, new IntPtr(0), new IntPtr(0x00090009));
MessageWindow.SendMessage(ref msg);
}
// Free resources
ReleaseDC(hWnd, hDC);
}
[DllImport("coredll.dll")]
private extern static bool SipShowIM(int dwFlag);
[DllImport("coredll.dll")]
private extern static IntPtr FindWindow(string wndClass, string caption);
[DllImport("coredll.dll")]
private extern static IntPtr GetWindow(IntPtr hWnd, int nType);
[DllImport("coredll.dll")]
private extern static int GetPixel(IntPtr hdc, int nXPos, int nYPos);
[DllImport("coredll.dll")]
private extern static void SetPixel(IntPtr hdc, int nXPos, int nYPos, int clr);
[DllImport("coredll.dll")]
private extern static IntPtr GetDC(IntPtr hWnd);
[DllImport("coredll.dll")]
private extern static void ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("coredll.dll")]
private static extern bool SipSetCurrentIM(byte[] clsid);
const int WM_LBUTTONDOWN = 0x0201;
const int WM_LBUTTONUP = 0x0202;
const int GW_CHILD = 5;
}
}
Sorry about the length. To pop the SIP up in numeric mode, you just use this line:
SIPHandler.ShowSIPNumeric();
or to make it appear in regular keyboard mode:
SIPHandler.ShowSIPRegular();
And to hide it again:
SIPHandler.HideSIP();
The basic trick behind this code is to sort of "peek" the color in the upper left corner to determine whether the SIP is already in regular keyboard or numeric mode, and then to simulate a mouse click (if necessary) in the same corner to ensure that the SIP is in the mode desired.
Note: this is "borrowed" web code, but I no longer know where I got it from. If anyone on SO knows where this hack came from, please let me know and I'll be happy to attribute it to the original author.
Update: well, after 2 seconds of Googling, I've found that the proximate source of this code was Daniel Moth:
http://www.danielmoth.com/Blog/InputPanelEx.cs
... who credits Alex Feinman with the original:
http://www.alexfeinman.com/download.asp?doc=IMSwitch.zip
Thanks, guys! This code actually brought me to tears once (I was chopping onions at the time, but that couldn't have been it).
The MaskedTextBox might be of use. Failing that, I recommend using an ordinary TextBox with an OnTextChange event handler that checks to make sure the value entered is actually a number. Any non-numerical characters, and you can bang out a message box, or simply remove those characters completely, depending on your needs.
The NumericUpDown controls are slow to use sometimes, but they have intrinsic data validation which in some cases is quite useful. If the control is one the user is not going to use often, consider using it. Otherwise the MaskedTextBox or TextBox is the way to go.
Another approach to this problem is to use a multi-level ContextMenu, where the first layer of options covers ranges of numbers, and the second layers let the users pick specific values, like this:
alt text http://img19.imageshack.us/img19/6329/dropdowns.jpg
You can create the full menu structure ahead of time (kind of a pain) or just load the structure dynamically depending on the range of values and the resolutions required. You can do this with hundreds of menu items in much less than a second, even on Windows Mobile devices.
This approach also works very well for entering monetary values.