In C# Windows.Forms I want to intercept the paste-windowmessage for a combobox. As this doesn't work by overriding the WndProc-method of the combobox, because I would need to override the WndProc of the textbox inside the combobox, I decided to create a custom class of type NativeWindow which overrides the WndProc. I assign the handle and release it, when the combobox-handle gets destroyed. But when Dispose for the combobox is called the problem is that I get an InvalidOperationException saying that an invalid cross-thread operation occured and that the combobox was accessed from a thread other than the thread it was created on. Any ideas what is going wrong here?
In the following you'll see, how my classes look like:
public class MyCustomComboBox : ComboBox
{
private WinHook hook = null;
public MyCustomComboBox()
: base()
{
this.hook = new WinHook(this);
}
private class WinHook : NativeWindow
{
public WinHook(MyCustomComboBox parent)
{
parent.HandleCreated += new EventHandler(this.Parent_HandleCreated);
parent.HandleDestroyed += new EventHandler(this.Parent_HandleDestroyed);
}
protected override void WndProc(ref Message m)
{
// do something
base.WndProc(ref m);
}
private void Parent_HandleCreated(object sender, EventArgs e)
{
MyCustomComboBox cbx = (MyCustomComboBox)sender;
this.AssignHandle(cbx.Handle);
}
private void Parent_HandleDestroyed(object sender, EventArgs e)
{
this.ReleaseHandle();
}
}
}
Per Hans' suggestion, I modified the code to use CB_GETCOMBOBOXINFO from one of his own examples.
public class PastelessComboBox : ComboBox {
private class TextWindow : NativeWindow {
[StructLayout(LayoutKind.Sequential)]
private struct RECT {
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private struct COMBOBOXINFO {
public Int32 cbSize;
public RECT rcItem;
public RECT rcButton;
public int buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
[DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessageCb(IntPtr hWnd, int msg, IntPtr wp, out COMBOBOXINFO lp);
public TextWindow(ComboBox cb) {
COMBOBOXINFO info = new COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
SendMessageCb(cb.Handle, 0x164, IntPtr.Zero, out info);
this.AssignHandle(info.hwndEdit);
}
protected override void WndProc(ref Message m) {
if (m.Msg == (0x0302)) {
MessageBox.Show("No pasting allowed!");
return;
}
base.WndProc(ref m);
}
}
private TextWindow textWindow;
protected override void OnHandleCreated(EventArgs e) {
textWindow = new TextWindow(this);
base.OnHandleCreated(e);
}
protected override void OnHandleDestroyed(EventArgs e) {
textWindow.ReleaseHandle();
base.OnHandleDestroyed(e);
}
}
Related
I'm not able to find an event to fire when I hover my ComboBox Items.
I'm using windows form to build an application.
I found a something similar for WPF:
how to change label text when I hover mouse over a combobox item?.
How can I do it the similar way in Windows Forms, or is there an alternate way?
Class ComboBoxListEx:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class ComboBoxListEx : ComboBox
{
private int listItem = -1;
private const int CB_GETCURSEL = 0x0147;
public event EventHandler<ListItemSelectionChangedEventArgs> ListItemSelectionChanged;
protected virtual void OnListItemSelectionChanged(ListItemSelectionChangedEventArgs e)
=> this.ListItemSelectionChanged?.Invoke(this, e);
public ComboBoxListEx() { }
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case CB_GETCURSEL:
int selItem = m.Result.ToInt32();
if (listItem != selItem)
{
listItem = selItem;
OnListItemSelectionChanged(new ListItemSelectionChangedEventArgs(
listItem, listItem < 0 ? string.Empty : this.GetItemText(this.Items[listItem]))
);
}
break;
default:
// Add Case switches to handle other events
break;
}
}
public class ListItemSelectionChangedEventArgs : EventArgs
{
public ListItemSelectionChangedEventArgs(int idx, string text)
{
this.ItemIndex = idx;
this.ItemText = text;
}
public int ItemIndex { get; private set; }
public string ItemText { get; private set; }
}
}
private void comboBoxListEx1_ListItemSelectionChanged(object sender, ComboBoxListEx.ListItemSelectionChangedEventArgs e)
{
label15.Text = e.ItemText;
}
You can create a Custom Control, derived from ComboBox, override its WndProc method to intercept the CB_GETCURSEL message.
Call base.WndProc(ref m) first. When the message is processed, the Message object's m.Result property is set to a value (as IntPtr) that represents the Item currently tracked in the ListBox (the Item highlighted when the Mouse Pointer hovers it).
► Note: prior to .Net Framework 4.8, the CB_GETCURSEL message result is not bubbled up automatically: we must send LB_GETCURSEL to the child ListBox to get the index of the Item currently highlighted.
The ListBox handle is retrieved using GetComboBoxInfo: it could be also accessed using reflection (the private ChildListAutomationObject property returns the ListBox AutomationElement, which provides the handle), or sending a CB_GETCOMBOBOXINFO message (but it's the same as calling GetComboBoxInfo()).
This custom ComboBox raises an Event, ListItemSelectionChanged, with a custom EventArgs object, ListItemSelectionChangedEventArgs, which exposes two public properties: ItemIndex and ItemText, set to the Index and Text of the hovered item.
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class ComboBoxListEx : ComboBox
{
private const int CB_GETCURSEL = 0x0147;
private int listItem = -1;
IntPtr listBoxHandle = IntPtr.Zero;
public event EventHandler<ListItemSelectionChangedEventArgs> ListItemSelectionChanged;
protected virtual void OnListItemSelectionChanged(ListItemSelectionChangedEventArgs e)
=> this.ListItemSelectionChanged?.Invoke(this, e);
public ComboBoxListEx() { }
// .Net Framework prior to 4.8 - get the handle of the ListBox
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
listBoxHandle = GetComboBoxListInternal(this.Handle);
}
protected override void WndProc(ref Message m)
{
int selItem = -1;
base.WndProc(ref m);
switch (m.Msg) {
case CB_GETCURSEL:
selItem = m.Result.ToInt32();
break;
// .Net Framework prior to 4.8
// case CB_GETCURSEL can be left there or removed: it's always -1
case 0x0134:
selItem = SendMessage(listBoxHandle, LB_GETCURSEL, 0, 0);
break;
default:
// Add Case switches to handle other events
break;
}
if (listItem != selItem) {
listItem = selItem;
OnListItemSelectionChanged(new ListItemSelectionChangedEventArgs(
listItem, listItem < 0 ? string.Empty : GetItemText(Items[listItem]))
);
}
}
public class ListItemSelectionChangedEventArgs : EventArgs
{
public ListItemSelectionChangedEventArgs(int idx, string text) {
ItemIndex = idx;
ItemText = text;
}
public int ItemIndex { get; private set; }
public string ItemText { get; private set; }
}
// -------------------------------------------------------------
// .Net Framework prior to 4.8
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, int lParam);
private const int LB_GETCURSEL = 0x0188;
[StructLayout(LayoutKind.Sequential)]
internal struct COMBOBOXINFO
{
public int cbSize;
public Rectangle rcItem;
public Rectangle rcButton;
public int buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
public void Init() => this.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
}
internal static IntPtr GetComboBoxListInternal(IntPtr cboHandle)
{
var cbInfo = new COMBOBOXINFO();
cbInfo.Init();
GetComboBoxInfo(cboHandle, ref cbInfo);
return cbInfo.hwndList;
}
}
Works like this:
I want to send message to other process already running. What wrong...? why I am not able to receive messages
My sender code is as below
public partial class RegisterWindowMessage : Form
{
[DllImport("User32.dll", EntryPoint = "SendMessage")]
//private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
private static extern int SendMessage(IntPtr hWnd, int Msg, string s, int i);
const int WM_SETTEXT = 0X000C;
public RegisterWindowMessage()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Process[] procs = Process.GetProcesses();
foreach (Process p in procs)
{
if (p.ProcessName.Equals("TaskScheduling"))
{
IntPtr hWnd = p.MainWindowHandle;
Thread.Sleep(1000);
SendMessage(hWnd, WM_SETTEXT, "This is the new Text!!!", 0);
MessageBox.Show("Inside");
}
}
}
private void button2_Click(object sender, EventArgs e)
{
}
}
My receiver code is
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
}
private void frmReceiver_KeyDown(object sender, KeyEventArgs e)
{
// this.lsvMsgList.Items.Add(e.KeyValue.ToString());
}
protected override void WndProc(ref Message m)
{
MessageBox.Show(m.Msg.ToString());
MessageBox.Show(m.LParam.ToString());
MessageBox.Show(m.WParam.ToString());
if (m.LParam.ToInt32() == 1)
{
}
else
{
base.WndProc(ref m);
}
}
}
I want to know why i am not able to receive message. Let me know where I am wrong
I been searching around for a solution for my problem but for now I wasn't able to get any sucessfull code for what I want to do. So, I have a form without border that is filled with 2 custom panels, so there is no way to the user click on the frame, thinking in that, I implement a code that when user click on a panel, this will call a function on my form that recive by parameter the event of the mouse.
This is the code of my Panel (note that both of my panels in the frame are the same class, it's just 2 diferent instances)
public class MyPanel : System.Windows.Forms.Panel{
(...)
private void MyPanel_MouseDown(object sender, MouseEventArgs e)
{
BarraSms.getInstance().mouseDown(e);
}
private void MyPanel_MouseMove(object sender, MouseEventArgs e)
{
BarraSms.getInstance().mouseMove(e);
}
}
And this is the code of my form:
public partial class BarraSms : Form
{
private Point mousePoint;
(...)
public void mouseDown(MouseEventArgs e) {
mousePoint = new Point(-e.X, -e.Y);
}
public void mouseMove(MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
Point mousePos = Control.MousePosition;
mousePos.Offset(mousePoint .X, mousePoint .Y);
this.Location = mousePos;
}
}
}
Is there something that I'm missing?
Thank you in advance for the help.
The working code (update), problem solved by: x4rf41
The MyPanel Class :
MouseMove += MyPanel_MouseMove; // added in class constructer
the BarraSms class (Form)
public partial class BarraSms : Form
{
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
public void mouseMove(MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
ReleaseCapture();
SendMessage(this.Handle, WM_NCLBUTTONDOWN, new IntPtr(HT_CAPTION), IntPtr.Zero);
Point loc = this.Location;
writeCoordToBin(loc.X, loc.Y);
}
}
}
There is much better solution for that using the windows api function.
The way you use it, you will have a problem when you move the form very fast and the mouse goes out of the panel. I had the exact same problem.
try this:
using System.Runtime.InteropServices;
and in your Form class
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
public void mouseMove(MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
ReleaseCapture();
SendMessage(this.Handle, WM_NCLBUTTONDOWN, new IntPtr(HT_CAPTION), IntPtr.Zero);
}
}
no need for a mouseDown event for this
Your private methods in MyPanel cannot be called by the framework. You need to declare them as follows:
protected override void OnMouseDown(MouseEventArgs e)
{
var parent = this.Parent as BarraSms;
parent.mouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
var parent = this.Parent as BarraSms;
parent.mouseMove(e);
}
Try this:
public void mouseMove(MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
Point currentPos = Location;
currentPos.Offset(e.X + mousePoint.X, e.Y + mousePoint.Y);
this.Location = currentPos;
}
//Or simply use Location.Offset(e.X + mousePoint.X, e.Y + mousePoint.Y);
}
You have to use Location (the current location of the form), not the Control.MousePosition which is the location of mouse on screen.
UPDATE: Looks like you don't even know how to register event handlers, try modifying your panel class like this:
public class MyPanel : System.Windows.Forms.Panel{
//(...)
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
BarraSms.getInstance().mouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
BarraSms.getInstance().mouseMove(e);
}
}
How do I achieve this in a WinForms container control when the scroll bars are visible?
Highlighted here (Google Chrome browser):
EDIT: This cursor is the only one that is visible on a screenshot. I hope it's clear what i mean.
EDIT:
Tried this on my control. Does not work.
const int WM_MBUTTONDOWN = 0x207;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MBUTTONDOWN)
DefWndProc(ref m);
else
base.WndProc(ref m);
}
Here's what I have so far. It exits "reader mode" if I release the middle button, and I haven't implemented scrolling within the control (I used a textbox), but it may give you something to start with.
[DllImport("comctl32.dll", SetLastError=true, EntryPoint="#383")]
static extern void DoReaderMode(ref READERMODEINFO prmi);
public delegate bool TranslateDispatchCallbackDelegate(ref MSG lpmsg);
public delegate bool ReaderScrollCallbackDelegate(ref READERMODEINFO prmi, int dx, int dy);
[StructLayout(LayoutKind.Sequential)]
public struct READERMODEINFO
{
public int cbSize;
public IntPtr hwnd;
public int fFlags;
public IntPtr prc;
public ReaderScrollCallbackDelegate pfnScroll;
public TranslateDispatchCallbackDelegate fFlags2;
public IntPtr lParam;
}
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public UInt32 message;
public IntPtr wParam;
public IntPtr lParam;
public UInt32 time;
public POINT pt;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left, top, right, bottom;
}
private bool TranslateDispatchCallback(ref MSG lpMsg)
{
return false;
}
private bool ReaderScrollCallback(ref READERMODEINFO prmi, int dx, int dy)
{
// TODO: Scroll around within your control here
return false;
}
private void EnterReaderMode()
{
READERMODEINFO readerInfo = new READERMODEINFO
{
hwnd = this.textBox1.Handle,
fFlags = 0x00,
prc = IntPtr.Zero,
lParam = IntPtr.Zero,
fFlags2 = new TranslateDispatchCallbackDelegate(this.TranslateDispatchCallback),
pfnScroll = new ReaderScrollCallbackDelegate(this.ReaderScrollCallback)
};
readerInfo.cbSize = Marshal.SizeOf(readerInfo);
DoReaderMode(ref readerInfo);
}
private void textBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Middle)
{
EnterReaderMode();
}
}
The RichTextBox control does it by default when you press the mouse wheel button.
Edit: Sorry I misunderstood and thought you were asking about doing it within a textbox not a container control
First, sorry for my bad english :)
Second, I can know when the form is being moved/resized, using this code:
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_WINDOWPOSCHANGING)
{
WINDOWPOS winPos = new WINDOWPOS();
winPos = (WINDOWPOS)Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS));
//Here I just need to change the values of the WINDOWPOS structure
Marshal.StructureToPtr(winPos, m.LParam, true);
}
}
The WM_WINDOWPOSCHANGING message is sent also when the user is minimizing or maximizing the window. But how I can know when the user is maximizing/minimizing, not moving/resizing? I tried get the WindowState property, but it didn't work :(
The code of the WINDOWPOS structure is:
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
Any help?
You get WM_SYSCOMMAND when the user clicks one of the buttons in the title bar: http://msdn.microsoft.com/en-us/library/ms646360(VS.85).aspx
You can trap the WM_SYSCOMMAND by overriding WndProc(). But it can easily be done as well with an event handler for the Resize event:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
mPrevState = this.WindowState;
}
FormWindowState mPrevState;
protected override void OnResize(EventArgs e) {
base.OnResize(e);
if (mPrevState != this.WindowState) {
mPrevState = this.WindowState;
// Do something
//..
}
}
}