first I would like to stress that I am looking for a Windows DESKTOP WPF solution, so please no android. Thank you.
Second, I am fairly new to WPF and designing software for Windows Tablet, so please bear with me... And please do be explicit in your answers.
Third, the target software will run on a Windows Tablet (Win 8.0/8.1) and is being developed with Visual Studio 2013 Pro (C#, Desktop App, WPF).
OK, here is my problem:
I have been using a Textbox inherited class to show the Soft Keyboard when the user touches the Textbox (class code bellow). This part actually works well.
My problem is that the Soft Keyboard may popup over my actual control. In WinRT this would not happen since the OS seems to scroll the said control up until it becomes visible, but with DESKTOP app no such feature exists.
So my question is: does anyone know how to solve this overlap issue?
How could I (in Win desktop app) replicate the normal WinRT behavior?
Should I be using some other API to call the Soft Keyboard?
Code for the "TouchTextbox" class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace WPF_DT_Soft_Keyboard
{
class TouchTextbox : TextBox
{
[DllImport("user32.dll")]
public static extern int FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);
public const int WM_SYSCOMMAND = 0x0112;
public const int SC_CLOSE = 0xF060;
protected override void OnGotFocus(System.Windows.RoutedEventArgs e)
{
base.OnGotFocus(e);
ShowSoftKeyboard();
}
protected override void OnLostFocus(System.Windows.RoutedEventArgs e)
{
base.OnLostFocus(e);
HideSoftKeyboard();
}
protected override void OnPreviewKeyUp(System.Windows.Input.KeyEventArgs e)
{
base.OnPreviewKeyUp(e);
if (e.Key == System.Windows.Input.Key.Enter) HideSoftKeyboard();
}
private void ShowSoftKeyboard()
{
System.Diagnostics.Process.Start(#"C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exe");
}
private void HideSoftKeyboard()
{
int iHandle = FindWindow("IPTIP_Main_Window", "");
if (iHandle > 0)
{
SendMessage(iHandle, WM_SYSCOMMAND, SC_CLOSE, 0);
}
}
}
}
Ok, I think I finally found a decent solution.
I have encapsulated it into the TouchTextbox class bellow.
For it to work properly, the TouchTextbox must be contained withing a [ScrollViewer] control.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Threading;
namespace WPF_DT_Soft_Keyboard
{
class TouchTextbox : TextBox
{
/// <summary>
/// Private Container
/// </summary>
DispatcherTimer mBringIntoViewTimer = new DispatcherTimer();
/// <summary>
/// DLL imports
/// </summary>
[DllImport("user32.dll")]
public static extern int FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);
[DllImport("user32.dll")]
public static extern int MoveWindow(int hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
public static extern int SetWindowPos(int hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("Kernel32.dll")]
public static extern uint GetLastError();
/// <summary>
/// Constante
/// </summary>
public const int WM_SYSCOMMAND = 0x0112;
public const int SC_CLOSE = 0xF060;
public const int HWND_TOP = 0;
public const int SWP_NOSIZE = 0x0001;
public const int SWP_NOZORDER = 0x0004;
public const int SWP_SHOWWINDOW = 0x0040;
public const int BRING_INTO_VIEW_DELAY = 500;
/// <summary>
/// TouchTextbox
/// </summary>
public TouchTextbox()
{
mBringIntoViewTimer.IsEnabled = false;
mBringIntoViewTimer.Tick += MyTimer_Tick;
mBringIntoViewTimer.Interval = new TimeSpan(0, 0, 0, 0, BRING_INTO_VIEW_DELAY);
}
/// <summary>
/// MyTimer_Tick
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MyTimer_Tick(object sender, EventArgs e)
{
mBringIntoViewTimer.IsEnabled = false;
this.BringIntoView();
}
/// <summary>
/// OnGotFocus
/// </summary>
/// <param name="e"></param>
protected override void OnGotFocus(System.Windows.RoutedEventArgs e)
{
base.OnGotFocus(e);
ShowSoftKeyboard();
mBringIntoViewTimer.IsEnabled = true;
}
///// <summary>
///// OnLostFocus
///// </summary>
///// <param name="e"></param>
//protected override void OnLostFocus(System.Windows.RoutedEventArgs e)
//{
// base.OnLostFocus(e);
// HideSoftKeyboard();
//}
/// <summary>
/// OnPreviewKeyUp
/// </summary>
/// <param name="e"></param>
protected override void OnPreviewKeyUp(System.Windows.Input.KeyEventArgs e)
{
base.OnPreviewKeyUp(e);
if (e.Key == System.Windows.Input.Key.Enter) HideSoftKeyboard();
}
/// <summary>
/// ShowSoftKeyboard
/// </summary>
private void ShowSoftKeyboard()
{
Microsoft.Win32.Registry.CurrentUser.OpenSubKey(#"Software\Microsoft\TabletTip\1.7", true).SetValue("EdgeTargetDockedState", "1", Microsoft.Win32.RegistryValueKind.DWord);
System.Diagnostics.Process.Start(#"C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exe");
}
/// <summary>
/// HideSoftKeyboard
/// </summary>
private void HideSoftKeyboard()
{
int iHandle = FindWindow("IPTIP_Main_Window", "");
if (iHandle > 0)
{
SendMessage(iHandle, WM_SYSCOMMAND, SC_CLOSE, 0);
}
}
}
}
Related
So I've started fiddling around with VSB to, generally be better. I really want to learn, but I feel either the information I can find is outdated, or the code just doesn't work for me, for whatever reason. But, to the problem:
I want to be able to: Be able to click on a tab inside my VSB project, once that tab is clicked, there's a panel. Inside that panel, I want for example notepad to open, maximized to the panel window, docked and not able to move it (notepad).
I would like to do the same for other programs as well. My current code is the basic that opens notepad in a new window. I've only just begun poking at VSB so my knowledge is very limited.
I was able to do this in VSB (No C#) but not for C3
Current Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Software_Solution_C__Project__v._10._0._0
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
AboutBox1 myForm = new AboutBox1();
myForm.ShowDialog();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
Process.Start("mspaint.exe");
}
}
}
I tried to google, I tried different solutions I found, tried to find my way around, but it either crashed or gave endless error messages rendering me unable to do it.
Edit:
I've also tried the following code:
namespace Software_Solution_C__Project__v._10._0._0
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
AboutBox1 myForm = new AboutBox1();
myForm.ShowDialog();
}
private const int WM_SYSCOMMAND = 274; private const int SC_MAXIMIZE = 61488;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
private void panel1_Paint(object sender, PaintEventArgs e)
{
Process proc;
proc = Process.Start("Notepad.exe");
proc.WaitForInputIdle();
SetParent(proc.MainWindowHandle, panel1.Handle);
//SendMessage(proc.MainWindowHandle, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
}
}
}
The problem here is, notepad does open in the panel, but not stretched / docked to fit window, and if I move the window, another instance of notepad opens. And if I close notepad, it just reopens again.
After layers of hard work, I've built a "panel1" in "form1" to realize this function.
You can refer to the following code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
/// <summary>
/// Embed external exe
/// </summary>
public class EmbeddedExeTool
{
[DllImport("User32.dll", EntryPoint = "SetParent")]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", EntryPoint = "ShowWindow")]
private static extern int ShowWindow(IntPtr hwnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("user32.dll")]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
IntPtr WindowHandle = IntPtr.Zero;
private const int WS_THICKFRAME = 262144;
private const int WS_BORDER = 8388608;
private const int GWL_STYLE = -16;
private const int WS_CAPTION = 0xC00000;
private Process proApp = null;
private Control ContainerControl = null;
private const int WS_VISIBLE = 0x10000000;
[DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)]
private static extern IntPtr SetWindowLongPtr32(HandleRef hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Auto)]
private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, int dwNewLong);
private IntPtr SetWindowLong(HandleRef hWnd, int nIndex, int dwNewLong)
{
if (IntPtr.Size == 4)
{
return SetWindowLongPtr32(hWnd, nIndex, dwNewLong);
}
return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
}
/// <summary>
/// Load an external exe program into the program container
/// </summary>
/// <param name="control">To display the container control of the exe</param>
/// <param name="exepath">The full absolute path of the exe</param>
public void LoadEXE(Control control, string exepath)
{
ContainerControl = control;
control.SizeChanged += Control_SizeChanged;
ProcessStartInfo info = new ProcessStartInfo(exepath);
info.WindowStyle = ProcessWindowStyle.Minimized;
info.UseShellExecute = false;
info.CreateNoWindow = false;
proApp = Process.Start(info);
Application.Idle += Application_Idle;
EmbedProcess(proApp, control);
}
/// <summary>
/// Load an external exe program into the program container
/// </summary>
/// <param name="form">The form to display the exe</param>
/// <param name="exepath">The full absolute path of the exe</param>
public void LoadEXE(Form form, string exepath)
{
ContainerControl = form;
form.SizeChanged += Control_SizeChanged;
proApp = new Process();
proApp.StartInfo.UseShellExecute = false;
proApp.StartInfo.CreateNoWindow = false;
proApp.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
proApp.StartInfo.FileName = exepath;
proApp.StartInfo.Arguments = Process.GetCurrentProcess().Id.ToString();
proApp.Start();
Application.Idle += Application_Idle;
EmbedProcess(proApp, form);
}
/// <summary>
/// Make sure the application embeds this container
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Application_Idle(object sender, EventArgs e)
{
if (this.proApp == null || this.proApp.HasExited)
{
this.proApp = null;
Application.Idle -= Application_Idle;
return;
}
if (proApp.MainWindowHandle == IntPtr.Zero) return;
Application.Idle -= Application_Idle;
EmbedProcess(proApp, ContainerControl);
}
/// <summary>
/// Embed the specified program into the specified control
/// </summary>
private void EmbedProcess(Process app, Control control)
{
// Get the main handle
if (app == null || app.MainWindowHandle == IntPtr.Zero || control == null) return;
try
{
// Put it into this form
SetParent(app.MainWindowHandle, control.Handle);
// Remove border and whatnot
SetWindowLong(new HandleRef(this, app.MainWindowHandle), GWL_STYLE, WS_VISIBLE);
ShowWindow(app.MainWindowHandle, (int)ProcessWindowStyle.Maximized);
MoveWindow(app.MainWindowHandle, 0, 0, control.Width, control.Height, true);
}
catch (Exception ex3)
{
Console.WriteLine(ex3.Message);
}
}
/// <summary>
/// Embed container resize event
/// </summary>
private void Control_SizeChanged(object sender, EventArgs e)
{
if (proApp == null)
{
return;
}
if (proApp.MainWindowHandle != IntPtr.Zero && ContainerControl != null)
{
MoveWindow(proApp.MainWindowHandle, 0, 0, ContainerControl.Width, ContainerControl.Height, true);
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
EmbeddedExeTool exetool = new EmbeddedExeTool();
exetool.LoadEXE(panel1, "notepad.exe"); //The specific path to embed the external exe
}
/// <summary>
/// The dock of the panel needs to be set to full
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void panel1_Paint(object sender, PaintEventArgs e)
{
}
}
}
If you have any questions, we can discuss them together.
I have a small desktop app where I'm using a mousehook to monitor a mouse click event inside and outside a window's form. So I want a Custom Control to raise an event whenever a mouse is click out of that control, but when the control itself clicked, or child controls within this custom control clicked, I want to ignore the event, or act differently.
The problem is all mousehooks I found so far, has no way of knowing the control window that is about to receive a click event.
I need to know which control window is about to receive the click event ahead of time, so that I can differentiate whether it is a child control of the Custom Control or any other control or outside of the form.
Here is the mousehook class I'm using. I found it online.
using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Threading;
using Microsoft;
namespace library
{
/// <summary>
/// Abstract base class for Mouse and Keyboard hooks
/// </summary>
public abstract class GlobalHook
{
/// <summary>
///
/// </summary>
#region Windows API Code
[StructLayout(LayoutKind.Sequential)]
protected class POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
protected class MouseHookStruct
{
public POINT pt;
public int hwnd;
public int wHitTestCode;
public int dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
protected class MouseLLHookStruct
{
public POINT pt;
public int mouseData;
public int flags;
public int time;
public int dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
protected class KeyboardHookStruct
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
protected static extern int SetWindowsHookEx(
int idHook,
HookProc lpfn,
IntPtr hMod,
int dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
protected static extern int UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
protected static extern int CallNextHookEx(
int idHook,
int nCode,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32")]
protected static extern int ToAscii(
int uVirtKey,
int uScanCode,
byte[] lpbKeyState,
byte[] lpwTransKey,
int fuState);
[DllImport("user32")]
protected static extern int GetKeyboardState(byte[] pbKeyState);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
protected static extern short GetKeyState(int vKey);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetThreadDesktop(uint dwThreadId);
protected delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
protected const int WH_MOUSE_LL = 14;
protected const int WH_KEYBOARD_LL = 13;
protected const int WH_MOUSE = 7;
protected const int WH_KEYBOARD = 2;
protected const int WM_MOUSEMOVE = 0x200;
protected const int WM_LBUTTONDOWN = 0x201;
protected const int WM_RBUTTONDOWN = 0x204;
protected const int WM_MBUTTONDOWN = 0x207;
protected const int WM_LBUTTONUP = 0x202;
protected const int WM_RBUTTONUP = 0x205;
protected const int WM_MBUTTONUP = 0x208;
protected const int WM_LBUTTONDBLCLK = 0x203;
protected const int WM_RBUTTONDBLCLK = 0x206;
protected const int WM_MBUTTONDBLCLK = 0x209;
protected const int WM_MOUSEWHEEL = 0x020A;
protected const int WM_KEYDOWN = 0x100;
protected const int WM_KEYUP = 0x101;
protected const int WM_SYSKEYDOWN = 0x104;
protected const int WM_SYSKEYUP = 0x105;
protected const byte VK_SHIFT = 0x10;
protected const byte VK_CAPITAL = 0x14;
protected const byte VK_NUMLOCK = 0x90;
protected const byte VK_LSHIFT = 0xA0;
protected const byte VK_RSHIFT = 0xA1;
protected const byte VK_LCONTROL = 0xA2;
protected const byte VK_RCONTROL = 0x3;
protected const byte VK_LALT = 0xA4;
protected const byte VK_RALT = 0xA5;
protected const byte LLKHF_ALTDOWN = 0x20;
#endregion
/// <summary>
///
/// </summary>
#region Private Variables
protected int _hookType;
protected int _handleToHook;
protected bool _isStarted;
protected HookProc _hookCallback;
#endregion
/// <summary>
///
/// </summary>
#region Properties
public bool IsStarted
{
///
get
{
return _isStarted;
}
}
#endregion
/// <summary>
///
/// </summary>
#region Constructor
public GlobalHook()
{
///
Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
}
#endregion
/// <summary>
///
/// </summary>
#region Methods
public void Start()
{
///
if (!_isStarted && _hookType != 0)
{
// Make sure we keep a reference to this delegate!
// If not, GC randomly collects it, and a NullReference exception is thrown
_hookCallback = new HookProc(HookCallbackProcedure);
//
IntPtr pp = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]);
///
_handleToHook = SetWindowsHookEx(
_hookType,
_hookCallback,
(IntPtr)0/*Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0])*/,
/*0*/(int)GetThreadDesktop((uint)Thread.CurrentThread.ManagedThreadId));
/// Were we able to sucessfully start hook?
if (_handleToHook != 0)
{
///
_isStarted = true;
}
}
}
public void Stop()
{
///
if (_isStarted)
{
///
UnhookWindowsHookEx(_handleToHook);
///
_isStarted = false;
}
}
protected virtual int HookCallbackProcedure(int nCode, IntPtr wParam, IntPtr lParam)
{
// This method must be overriden by each extending hook
return 0;
}
protected void Application_ApplicationExit(object sender, EventArgs e)
{
///
if (_isStarted)
{
///
Stop();
}
}
#endregion
}
/// <summary>
/// Captures global mouse events
/// </summary>
public class MouseHook : GlobalHook
{
/// <summary>
///
/// </summary>
#region MouseEventType Enum
private enum MouseEventType
{
None,
MouseDown,
MouseUp,
DoubleClick,
MouseWheel,
MouseMove
}
#endregion
/// <summary>
///
/// </summary>
#region Events
public event MouseEventHandler MouseDown;
public event MouseEventHandler MouseUp;
public event MouseEventHandler MouseMove;
public event MouseEventHandler MouseWheel;
public event EventHandler Click;
public event EventHandler DoubleClick;
#endregion
/// <summary>
///
/// </summary>
#region Constructor
public MouseHook()
{
_hookType = WH_MOUSE_LL;
}
#endregion
/// <summary>
///
/// </summary>
#region Methods
protected override int HookCallbackProcedure(int nCode, IntPtr wParam, IntPtr lParam)
{
///
if (nCode > -1 && (MouseDown != null || MouseUp != null || MouseMove != null))
{
///
MouseLLHookStruct mouseHookStruct =
(MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
///
MouseButtons button = GetButton((Int32)wParam);
MouseEventType eventType = GetEventType((Int32)wParam);
///
MouseEventArgs e = new MouseEventArgs(
button,
(eventType == MouseEventType.DoubleClick ? 2 : 1),
mouseHookStruct.pt.x,
mouseHookStruct.pt.y,
(eventType == MouseEventType.MouseWheel ?
(short)((mouseHookStruct.mouseData >> 16) & 0xffff) : 0));
// Prevent multiple Right Click events (this probably happens for popup menus)
if (button == MouseButtons.Right && mouseHookStruct.flags != 0)
{
///
eventType = MouseEventType.None;
}
///
switch (eventType)
{
///
//Control cont = Control.FromHandle(wParam);
///
case MouseEventType.MouseDown:
///
if (MouseDown != null)
{
///
MouseDown(this, e);
}
break;
case MouseEventType.MouseUp:
if (Click != null)
{
///
Click(this, new EventArgs());
}
if (MouseUp != null)
{
///
MouseUp(this, e);
}
break;
case MouseEventType.DoubleClick:
if (DoubleClick != null)
{
///
DoubleClick(this, new EventArgs());
}
break;
case MouseEventType.MouseWheel:
if (MouseWheel != null)
{
///
MouseWheel(this, e);
}
break;
case MouseEventType.MouseMove:
if (MouseMove != null)
{
///
MouseMove(this, e);
}
break;
default:
break;
}
}
///
return CallNextHookEx(_handleToHook, nCode, wParam, lParam);
}
private MouseButtons GetButton(Int32 wParam)
{
///
switch (wParam)
{
///
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_LBUTTONDBLCLK:
return MouseButtons.Left;
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_RBUTTONDBLCLK:
return MouseButtons.Right;
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_MBUTTONDBLCLK:
return MouseButtons.Middle;
default:
return MouseButtons.None;
}
}
private MouseEventType GetEventType(Int32 wParam)
{
///
switch (wParam)
{
///
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
return MouseEventType.MouseDown;
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
return MouseEventType.MouseUp;
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
return MouseEventType.DoubleClick;
case WM_MOUSEWHEEL:
return MouseEventType.MouseWheel;
case WM_MOUSEMOVE:
return MouseEventType.MouseMove;
default:
return MouseEventType.None;
}
}
#endregion
}
}
I need to be able to read keyboard input while my form is in the background (not necessarily hidden). There are simmilar questions here but I need to be albe to determine witch key is pressed and I need to get its value as "Key" not int (stuff like 0x00357 and I have no idea what that is). Specifically I need to check if key determined by user is currently pressed. How can I do that?
A few years later than you asked, but this is what I used when creating an app that would listen to key presses, and anything in a predefined list would be mirrored to other background processes to enable multi-boxing in a game.
There is a great keyboard listener at Ciantic's Github, and in case the link is invalid then here's the code -
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using System.Windows.Threading;
using System.Collections.Generic;
namespace Ownskit.Utils
{
/// <summary>
/// Listens keyboard globally.
///
/// <remarks>Uses WH_KEYBOARD_LL.</remarks>
/// </summary>
public class KeyboardListener : IDisposable
{
/// <summary>
/// Creates global keyboard listener.
/// </summary>
public KeyboardListener()
{
// Dispatcher thread handling the KeyDown/KeyUp events.
this.dispatcher = Dispatcher.CurrentDispatcher;
// We have to store the LowLevelKeyboardProc, so that it is not garbage collected runtime
hookedLowLevelKeyboardProc = (InterceptKeys.LowLevelKeyboardProc)LowLevelKeyboardProc;
// Set the hook
hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc);
// Assign the asynchronous callback event
hookedKeyboardCallbackAsync = new KeyboardCallbackAsync(KeyboardListener_KeyboardCallbackAsync);
}
private Dispatcher dispatcher;
/// <summary>
/// Destroys global keyboard listener.
/// </summary>
~KeyboardListener()
{
Dispose();
}
/// <summary>
/// Fired when any of the keys is pressed down.
/// </summary>
public event RawKeyEventHandler KeyDown;
/// <summary>
/// Fired when any of the keys is released.
/// </summary>
public event RawKeyEventHandler KeyUp;
#region Inner workings
/// <summary>
/// Hook ID
/// </summary>
private IntPtr hookId = IntPtr.Zero;
/// <summary>
/// Asynchronous callback hook.
/// </summary>
/// <param name="character">Character</param>
/// <param name="keyEvent">Keyboard event</param>
/// <param name="vkCode">VKCode</param>
private delegate void KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character);
/// <summary>
/// Actual callback hook.
///
/// <remarks>Calls asynchronously the asyncCallback.</remarks>
/// </summary>
/// <param name="nCode"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
{
string chars = "";
if (nCode >= 0)
if (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYUP ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYUP)
{
// Captures the character(s) pressed only on WM_KEYDOWN
chars = InterceptKeys.VKCodeToString((uint)Marshal.ReadInt32(lParam),
(wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN));
hookedKeyboardCallbackAsync.BeginInvoke((InterceptKeys.KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), chars, null, null);
}
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
}
/// <summary>
/// Event to be invoked asynchronously (BeginInvoke) each time key is pressed.
/// </summary>
private KeyboardCallbackAsync hookedKeyboardCallbackAsync;
/// <summary>
/// Contains the hooked callback in runtime.
/// </summary>
private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc;
/// <summary>
/// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events.
/// </summary>
/// <param name="keyEvent">Keyboard event</param>
/// <param name="vkCode">VKCode</param>
/// <param name="character">Character as string.</param>
void KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character)
{
switch (keyEvent)
{
// KeyDown events
case InterceptKeys.KeyEvent.WM_KEYDOWN:
if (KeyDown != null)
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, false, character));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYDOWN:
if (KeyDown != null)
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, true, character));
break;
// KeyUp events
case InterceptKeys.KeyEvent.WM_KEYUP:
if (KeyUp != null)
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, false, character));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYUP:
if (KeyUp != null)
dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, true, character));
break;
default:
break;
}
}
#endregion
#region IDisposable Members
/// <summary>
/// Disposes the hook.
/// <remarks>This call is required as it calls the UnhookWindowsHookEx.</remarks>
/// </summary>
public void Dispose()
{
InterceptKeys.UnhookWindowsHookEx(hookId);
}
#endregion
}
/// <summary>
/// Raw KeyEvent arguments.
/// </summary>
public class RawKeyEventArgs : EventArgs
{
/// <summary>
/// VKCode of the key.
/// </summary>
public int VKCode;
/// <summary>
/// WPF Key of the key.
/// </summary>
public Key Key;
/// <summary>
/// Is the hitted key system key.
/// </summary>
public bool IsSysKey;
/// <summary>
/// Convert to string.
/// </summary>
/// <returns>Returns string representation of this key, if not possible empty string is returned.</returns>
public override string ToString()
{
return Character;
}
/// <summary>
/// Unicode character of key pressed.
/// </summary>
public string Character;
/// <summary>
/// Create raw keyevent arguments.
/// </summary>
/// <param name="VKCode"></param>
/// <param name="isSysKey"></param>
/// <param name="Character">Character</param>
public RawKeyEventArgs(int VKCode, bool isSysKey, string Character)
{
this.VKCode = VKCode;
this.IsSysKey = isSysKey;
this.Character = Character;
this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode);
}
}
/// <summary>
/// Raw keyevent handler.
/// </summary>
/// <param name="sender">sender</param>
/// <param name="args">raw keyevent arguments</param>
public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args);
#region WINAPI Helper class
/// <summary>
/// Winapi Key interception helper class.
/// </summary>
internal static class InterceptKeys
{
public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
public static int WH_KEYBOARD_LL = 13;
/// <summary>
/// Key event
/// </summary>
public enum KeyEvent : int {
/// <summary>
/// Key down
/// </summary>
WM_KEYDOWN = 256,
/// <summary>
/// Key up
/// </summary>
WM_KEYUP = 257,
/// <summary>
/// System key up
/// </summary>
WM_SYSKEYUP = 261,
/// <summary>
/// System key down
/// </summary>
WM_SYSKEYDOWN = 260
}
public static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
#region Convert VKCode to string
// Note: Sometimes single VKCode represents multiple chars, thus string.
// E.g. typing "^1" (notice that when pressing 1 the both characters appear,
// because of this behavior, "^" is called dead key)
[DllImport("user32.dll")]
private static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl);
[DllImport("user32.dll")]
private static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
private static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetKeyboardLayout(uint dwLayout);
[DllImport("User32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("User32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll")]
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("kernel32.dll")]
private static extern uint GetCurrentThreadId();
private static uint lastVKCode = 0;
private static uint lastScanCode = 0;
private static byte[] lastKeyState = new byte[255];
private static bool lastIsDead = false;
/// <summary>
/// Convert VKCode to Unicode.
/// <remarks>isKeyDown is required for because of keyboard state inconsistencies!</remarks>
/// </summary>
/// <param name="VKCode">VKCode</param>
/// <param name="isKeyDown">Is the key down event?</param>
/// <returns>String representing single unicode character.</returns>
public static string VKCodeToString(uint VKCode, bool isKeyDown)
{
// ToUnicodeEx needs StringBuilder, it populates that during execution.
System.Text.StringBuilder sbString = new System.Text.StringBuilder(5);
byte[] bKeyState = new byte[255];
bool bKeyStateStatus;
bool isDead = false;
// Gets the current windows window handle, threadID, processID
IntPtr currentHWnd = GetForegroundWindow();
uint currentProcessID;
uint currentWindowThreadID = GetWindowThreadProcessId(currentHWnd, out currentProcessID);
// This programs Thread ID
uint thisProgramThreadId = GetCurrentThreadId();
// Attach to active thread so we can get that keyboard state
if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID , true))
{
// Current state of the modifiers in keyboard
bKeyStateStatus = GetKeyboardState(bKeyState);
// Detach
AttachThreadInput(thisProgramThreadId, currentWindowThreadID, false);
}
else
{
// Could not attach, perhaps it is this process?
bKeyStateStatus = GetKeyboardState(bKeyState);
}
// On failure we return empty string.
if (!bKeyStateStatus)
return "";
// Gets the layout of keyboard
IntPtr HKL = GetKeyboardLayout(currentWindowThreadID);
// Maps the virtual keycode
uint lScanCode = MapVirtualKeyEx(VKCode, 0, HKL);
// Keyboard state goes inconsistent if this is not in place. In other words, we need to call above commands in UP events also.
if (!isKeyDown)
return "";
// Converts the VKCode to unicode
int relevantKeyCountInBuffer = ToUnicodeEx(VKCode, lScanCode, bKeyState, sbString, sbString.Capacity, (uint)0, HKL);
string ret = "";
switch (relevantKeyCountInBuffer)
{
// Dead keys (^,`...)
case -1:
isDead = true;
// We must clear the buffer because ToUnicodeEx messed it up, see below.
ClearKeyboardBuffer(VKCode, lScanCode, HKL);
break;
case 0:
break;
// Single character in buffer
case 1:
ret = sbString[0].ToString();
break;
// Two or more (only two of them is relevant)
case 2:
default:
ret = sbString.ToString().Substring(0, 2);
break;
}
// We inject the last dead key back, since ToUnicodeEx removed it.
// More about this peculiar behavior see e.g:
// http://www.experts-exchange.com/Programming/System/Windows__Programming/Q_23453780.html
// http://blogs.msdn.com/michkap/archive/2005/01/19/355870.aspx
// http://blogs.msdn.com/michkap/archive/2007/10/27/5717859.aspx
if (lastVKCode != 0 && lastIsDead)
{
System.Text.StringBuilder sbTemp = new System.Text.StringBuilder(5);
ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, sbTemp, sbTemp.Capacity, (uint)0, HKL);
lastVKCode = 0;
return ret;
}
// Save these
lastScanCode = lScanCode;
lastVKCode = VKCode;
lastIsDead = isDead;
lastKeyState = (byte[])bKeyState.Clone();
return ret;
}
private static void ClearKeyboardBuffer(uint vk, uint sc, IntPtr hkl)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder(10);
int rc;
do {
byte[] lpKeyStateNull = new Byte[255];
rc = ToUnicodeEx(vk, sc, lpKeyStateNull, sb, sb.Capacity, 0, hkl);
} while(rc < 0);
}
#endregion
}
#endregion
}
From the author, this is the usage -
Usage in WPF:
App.xaml:
<Application ...
Startup="Application_Startup"
Exit="Application_Exit">
...
App.xaml.cs:
public partial class App : Application
{
KeyboardListener KListener = new KeyboardListener();
private void Application_Startup(object sender, StartupEventArgs e)
{
KListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
}
void KListener_KeyDown(object sender, RawKeyEventArgs args)
{
Console.WriteLine(args.Key.ToString());
Console.WriteLine(args.ToString()); // Prints the text of pressed button, takes in account big and small letters. E.g. "Shift+a" => "A"
}
private void Application_Exit(object sender, ExitEventArgs e)
{
KListener.Dispose();
}
}
If you also need to listen for KeyUp events then you can create a listener for that as well. The RawKeyEventArgs that is generated contains the key name, character pressed, and virtual key code. You can use any of these to compare with your existing list that you're wanting to respond to, and react accordingly. If you need to verify what key presses will look like then you can display a message box of anything entered and build yourself a list from there.
I hope this helps!
This question already has answers here:
Out of Memory from capuring screenshots
(2 answers)
Closed 8 years ago.
In form1 designer I added a timer and first set it to 1000ms then I changed it to 100ms and then in 100ms after it was saving screenshots to the hard disk after some minutes I got exception out of memory.
Then I changed it to 500ms and then again out of memory after few minutes.
I think when it's 1000ms it's ok.
This is the class for the screenshots:
using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
namespace WindowsFormsApplication1
{
/// <summary>
/// Provides functions to capture the entire screen, or a particular window, and save it to a file.
/// </summary>
public class ScreenCapture
{
/// <summary>
/// Creates an Image object containing a screen shot of the entire desktop
/// </summary>
/// <returns></returns>
public Image CaptureScreen()
{
return CaptureWindow(User32.GetDesktopWindow());
}
/// <summary>
/// Creates an Image object containing a screen shot of a specific window
/// </summary>
/// <param name="handle">The handle to the window. (In windows forms, this is obtained by the Handle property)</param>
/// <returns></returns>
public Image CaptureWindow(IntPtr handle)
{
// get te hDC of the target window
IntPtr hdcSrc = User32.GetWindowDC(handle);
// get the size
User32.RECT windowRect = new User32.RECT();
User32.GetWindowRect(handle, ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
// create a device context we can copy to
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
// create a bitmap we can copy it to,
// using GetDeviceCaps to get the width/height
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
// select the bitmap object
IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
// bitblt over
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY);
// restore selection
GDI32.SelectObject(hdcDest, hOld);
// clean up
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle, hdcSrc);
// get a .NET image object for it
Image img = Image.FromHbitmap(hBitmap);
// free up the Bitmap object
GDI32.DeleteObject(hBitmap);
return img;
}
/// <summary>
/// Captures a screen shot of a specific window, and saves it to a file
/// </summary>
/// <param name="handle"></param>
/// <param name="filename"></param>
/// <param name="format"></param>
public void CaptureWindowToFile(IntPtr handle, string filename, ImageFormat format)
{
Image img = CaptureWindow(handle);
img.Save(filename, format);
}
/// <summary>
/// Captures a screen shot of the entire desktop, and saves it to a file
/// </summary>
/// <param name="filename"></param>
/// <param name="format"></param>
public void CaptureScreenToFile(string filename, ImageFormat format)
{
Image img = CaptureScreen();
img.Save(filename, format);
}
/// <summary>
/// Helper class containing Gdi32 API functions
/// </summary>
private class GDI32
{
public const int SRCCOPY = 0x00CC0020; // BitBlt dwRop parameter
[DllImport("gdi32.dll")]
public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest,
int nWidth, int nHeight, IntPtr hObjectSource,
int nXSrc, int nYSrc, int dwRop);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth,
int nHeight);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern bool DeleteDC(IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
}
/// <summary>
/// Helper class containing User32 API functions
/// </summary>
private class User32
{
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
}
}
}
And this is how i use it in form1:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
int count;
ScreenCapture sc;
public Form1()
{
InitializeComponent();
count = 0;
sc = new ScreenCapture();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void timer1_Tick(object sender, EventArgs e)
{
count++;
sc.CaptureScreenToFile(#"c:\temp\screens3\" + count.ToString("D6") + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
private void button1_Click(object sender, EventArgs e)
{
timer1.Enabled = true;
}
}
}
Is there any way to keep saving the screenshots at 100ms or 500ms without getting out of memory ?
While you did a good job at cleaning up your unmanaged resources you still need to clean up your managed resources. Image is notorious for holding on to resources that does not put memory pressure on to the GC. This causes a garbage collection to not automatically happen as often as with "normal" classes.
Try disposing of your image objects:
public void CaptureWindowToFile(IntPtr handle, string filename, ImageFormat format)
{
using(Image img = CaptureWindow(handle))
{
img.Save(filename, format);
}
}
public void CaptureScreenToFile(string filename, ImageFormat format)
{
using(Image img = CaptureScreen())
{
img.Save(filename, format);
}
}
I have a form which has a Combo Box Control. I have selected the drop down style property to DropDown. I have also set the DropDown Width to 250. I have set the auto complete mode to suggest and the auto complete source to listitems. it works absolutely fine when i click on the drop down. but when i type in somethin, the auto complete mode activates a drop down which has a small width.
any help appreciate. i wanna know how to increase the width of the auto complete drop down via code so that the list items are viewed properly. I am using C#.
I had asked this a couple of months back but didn't get a proper answer. now the customer wants it bad :(
??
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
/// <summary>
/// Represents an ComboBox with additional properties for setting the
/// size of the AutoComplete Drop-Down window.
/// </summary>
public class ComboBoxEx : ComboBox
{
private int acDropDownHeight = 106;
private int acDropDownWidth = 170;
//<EditorBrowsable(EditorBrowsableState.Always), _
[Browsable(true), Description("The width, in pixels, of the auto complete drop down box"), DefaultValue(170)]
public int AutoCompleteDropDownWidth
{
get { return acDropDownWidth; }
set { acDropDownWidth = value; }
}
//<EditorBrowsable(EditorBrowsableState.Always), _
[Browsable(true), Description("The height, in pixels, of the auto complete drop down box"), DefaultValue(106)]
public int AutoCompleteDropDownHeight
{
get { return acDropDownHeight; }
set { acDropDownHeight = value; }
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
ACWindow.RegisterOwner(this);
}
#region Nested type: ACWindow
/// <summary>
/// Provides an encapsulation of an Auto complete drop down window
/// handle and window proc.
/// </summary>
private class ACWindow : NativeWindow
{
private static readonly Dictionary<IntPtr, ACWindow> ACWindows;
#region "Win API Declarations"
private const UInt32 WM_WINDOWPOSCHANGED = 0x47;
private const UInt32 WM_NCDESTROY = 0x82;
private const UInt32 SWP_NOSIZE = 0x1;
private const UInt32 SWP_NOMOVE = 0x2;
private const UInt32 SWP_NOZORDER = 0x4;
private const UInt32 SWP_NOREDRAW = 0x8;
private const UInt32 SWP_NOACTIVATE = 0x10;
private const UInt32 GA_ROOT = 2;
private static readonly List<ComboBoxEx> owners;
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr GetAncestor(IntPtr hWnd, UInt32 gaFlags);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern void GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy,
uint uFlags);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
#region Nested type: EnumThreadDelegate
private delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
#endregion
#region Nested type: RECT
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public readonly int Left;
public readonly int Top;
public readonly int Right;
public readonly int Bottom;
public Point Location
{
get { return new Point(Left, Top); }
}
}
#endregion
#endregion
private ComboBoxEx owner;
static ACWindow()
{
ACWindows = new Dictionary<IntPtr, ACWindow>();
owners = new List<ComboBoxEx>();
}
/// <summary>
/// Creates a new ACWindow instance from a specific window handle.
/// </summary>
private ACWindow(IntPtr handle)
{
AssignHandle(handle);
}
/// <summary>
/// Registers a ComboBoxEx for adjusting the Complete Dropdown window size.
/// </summary>
public static void RegisterOwner(ComboBoxEx owner)
{
if ((owners.Contains(owner)))
{
return;
}
owners.Add(owner);
EnumThreadWindows(GetCurrentThreadId(), EnumThreadWindowCallback, IntPtr.Zero);
}
/// <summary>
/// This callback will receive the handle for each window that is
/// associated with the current thread. Here we match the drop down window name
/// to the drop down window name and assign the top window to the collection
/// of auto complete windows.
/// </summary>
private static bool EnumThreadWindowCallback(IntPtr hWnd, IntPtr lParam)
{
if ((GetClassName(hWnd) == "Auto-Suggest Dropdown"))
{
IntPtr handle = GetAncestor(hWnd, GA_ROOT);
if ((!ACWindows.ContainsKey(handle)))
{
ACWindows.Add(handle, new ACWindow(handle));
}
}
return true;
}
/// <summary>
/// Gets the class name for a specific window handle.
/// </summary>
private static string GetClassName(IntPtr hRef)
{
var lpClassName = new StringBuilder(256);
GetClassName(hRef, lpClassName, 256);
return lpClassName.ToString();
}
/// <summary>
/// Overrides the NativeWindow's WndProc to handle when the window
/// attributes changes.
/// </summary>
protected override void WndProc(ref Message m)
{
if ((m.Msg == WM_WINDOWPOSCHANGED))
{
// If the owner has not been set we need to find the ComboBoxEx that
// is associated with this dropdown window. We do it by checking if
// the upper-left location of the drop-down window is within the
// ComboxEx client rectangle.
if ((owner == null))
{
Rectangle ownerRect = default(Rectangle);
var acRect = new RECT();
foreach (ComboBoxEx cbo in owners)
{
GetWindowRect(Handle, ref acRect);
ownerRect = cbo.RectangleToScreen(cbo.ClientRectangle);
if ((ownerRect.Contains(acRect.Location)))
{
owner = cbo;
break; // TODO: might not be correct. Was : Exit For
}
}
owners.Remove(owner);
}
if (((owner != null)))
{
SetWindowPos(Handle, IntPtr.Zero, -5, 0, owner.AutoCompleteDropDownWidth,
owner.AutoCompleteDropDownHeight, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
}
if ((m.Msg == WM_NCDESTROY))
{
ACWindows.Remove(Handle);
}
base.WndProc(ref m);
}
}
#endregion
}
This is what I did and it actually works really well. Good to find an answer atlast :)
This answer is an addition to reggie's answer.
If you want the user to be able to resize the auto-complete dropdown, then add the following code inside the WndProc method:
private const int WM_SIZING = 0x214;
if (m.Msg == WM_SIZING) {
if (owner != null) {
RECT rr = (RECT) Marshal.PtrToStructure(m.LParam, typeof(RECT));
owner.acDropDownWidth = (rr.Right - rr.Left);
owner.acDropDownHeight = (rr.Bottom - rr.Top);
}
}
kind of a bad design decision to do that. Why not set it to a static large size to start with? You can always use one of the events to get the text width and then use that to set the combobox width. Possibly the onPaint? easier way might be to create your own combobox class that inherits from combo box and then override the methods to do this yourself.