How do i pass an instance of an object which is in my main form to WndProc method
For ex:
I have a ComboBox object - objCombo. And i have to capture a certain window message before the system draws the drop down list box.
One way to do this I can have a custom combobox which derives from the ComboBox
public class CustomComboBox : ComboBox
{
//... some initialization code goes here
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CTLCOLORLISTBOX)
{
// capture the message and do some work.
//here i can get the reference to the CustomComboBox by using
//this keyword.
}
}
}
However, I want to know is there a way where in I can do this without having go through the process of creating the custom combobox and do exactly the same. i.e. capturing the Windows message inside my Form class using the reference to my combobox instance. ?
i.e.
public class MyForm : Form
{
//... some initialization code goes here including the InitializeComponent
// for form objects and other controls
private void CaptureComboWndProc(ref Message m)
{
// this method will capture only the windows message specific to objCombo ??
}
}
I hope I am being clear with the question
Thanks and Cheers
VATSA
Better way is to create a descendant of combobox. Which is very clear way of doing it.
However you can find the control from message, here's how you go.
Use Control.FromHandle to find the control to which message is posted.
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CTLCOLORLISTBOX)
{
if(Control.FromHandle(m.HWnd) == this.objCombo)
{
CaptureComboWndProc(ref m);
}
}
}
private void CaptureComboWndProc(ref Message m)
{
}
and... Finally I'd like to say don't do this please.
It is possible, the generic technique is called "sub-classing the window" in Windows GUI programming. It works by replacing the window procedure of a window. This is already done for every native window control you find back in the toolbox (ListView, ComboBox, etc), that's how they raise events. And can be done repeatedly. No need to pinvoke, the NativeWindow class supports it with its AssignHandle() method.
You have to derive your own class from NativeWindow and override the WndProc() method to implement custom message handling. Use, say, the form's Load event to attach it. Roughly:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private class ComboHooker : NativeWindow {
protected override void WndProc(ref Message m) {
if (m.Msg == 0x134) {
// etc...
}
else {
// Stop sub-classing on WM_NCDESTROY
if (m.Msg == 0x82) this.ReleaseHandle();
base.WndProc(ref m);
}
}
}
private void hookComboBoxes(Control.ControlCollection ctls) {
foreach (Control ctl in ctls) {
if (ctl.GetType() == typeof(ComboBox)) {
new ComboHooker().AssignHandle(ctl.Handle);
}
hookComboBoxes(ctl.Controls);
}
}
protected override void OnLoad(EventArgs e) {
hookComboBoxes(this.Controls);
base.OnLoad(e);
}
}
Related
I want to keep track of the pen's position from anywhere. I want WndProc to be called even if it's on the button. But, If there is a button in the form, wndProc does not occur. What should I do?
Some details:
Certain pen mouse message comes in wndProc's message. (pen mouse message's Msg is 0x0711)
If I move the pen inside the form, the value continues to come up with wndProc.
But, If there is a button in the form, wndProc does not occur on a button.
public const int PEN = 0x0711;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (PEN == m.Msg)
{
// TODO: function
}
}
This was not tested as I do not have a Pen, but in principle it the concept should work.
Use an IMessageFilter Interface implementation to detect the PEN message being sent to the form or one of its child controls and execute the desired function.
class PenFilter : IMessageFilter
{
private const int PEN = 0x0711;
private readonly Form parent;
public PenFilter(Form parent)
{
this.parent = parent;
}
bool IMessageFilter.PreFilterMessage(ref Message m)
{
Control targetControl = Control.FromChildHandle(m.HWnd);
if (targetControl != null && (targetControl == parent || parent == targetControl.FindForm()))
{
// execute your function
}
return false;
}
}
Install/remove the filter based on form activation/deactivation.
public partial class Form1 : Form
{
private PenFilter penFilter;
public Form1()
{
InitializeComponent();
penFilter = new PenFilter(this);
}
protected override void OnActivated(EventArgs e)
{
Application.AddMessageFilter(penFilter);
base.OnActivated(e);
}
protected override void OnDeactivate(EventArgs e)
{
Application.RemoveMessageFilter(penFilter);
base.OnDeactivate(e);
}
}
I have an application written in C#, It is a directory system that will display information in a slideshow fashion.
In my Form I have a Panel docked to fill the form's content. Inside that panel there are 9 panels where each one displays the information of a particular object.
Now what I want is that whenever I move the mouse I want to trigger the MouseMoveEvent of the form hosting the panel, instead of those of the big panel or the panels inside it.
Here is my code handling the form's MouseMoveEvent:
protected override void OnMouseMove(MouseEventArgs e)
{
MessageBox.Show("Moved!");
}
I know that this will not fire because the mouse cursor is inside the panel but how to trigger the event on the form anyway?
The purpose of this is to hide the current form and show another form when mouse cursor inside the form moved. It is possible?
This example works correct for me, program call TheMouseMoved() method only if I move mouse.
public partial class Form1 : Form
{
int counter = 0;
public Form1()
{
GlobalMouseHandler gmh = new GlobalMouseHandler();
gmh.TheMouseMoved += new MouseMovedEvent(gmh_TheMouseMoved);
Application.AddMessageFilter(gmh);
InitializeComponent();
}
void gmh_TheMouseMoved()
{
Point cur_pos = System.Windows.Forms.Cursor.Position;
//System.Console.WriteLine(cur_pos);
System.Console.WriteLine("{0}. [ {1},{2} ]", counter++, (cur_pos.X - this.Location.X), (cur_pos.Y - this.Location.Y));
}
}
public delegate void MouseMovedEvent();
public class GlobalMouseHandler : IMessageFilter
{
private const int WM_MOUSEMOVE = 0x0200;
public event MouseMovedEvent TheMouseMoved;
#region IMessageFilter Members
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_MOUSEMOVE)
{
if (TheMouseMoved != null)
{
TheMouseMoved();
}
}
// Always allow message to continue to the next filter control
return false;
}
#endregion
}
Try the "MouseMove" Event on the panel. If you disable the docked panel, the "MouseMove" Event of the Forms will be triggered.
I solve the problem by modifying the answer from How do I capture the mouse move event because the accepted answer is continuously firing even though the mouse is not moving according to #Randy Gamage comment.
I solved it using this code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace GlobalMouseEvents
{
public partial class Form1 : Form
{
public Form1()
{
GlobalMouseHandler gmh = new GlobalMouseHandler();
gmh.TheMouseMoved += new MouseMovedEvent(gmh_TheMouseMoved);
Application.AddMessageFilter(gmh);
InitializeComponent();
}
void gmh_TheMouseMoved()
{
Point cur_pos = System.Windows.Forms.Cursor.Position;
System.Console.WriteLine(cur_pos);
}
}
public delegate void MouseMovedEvent();
public class GlobalMouseHandler : IMessageFilter
{
private const int WM_MOUSEMOVE = 0x0200;
private System.Drawing.Point previousMousePosition = new System.Drawing.Point();
public static event EventHandler<MouseEventArgs> MouseMovedEvent = delegate { };
#region IMessageFilter Members
public bool PreFilterMessage(ref System.Windows.Forms.Message m)
{
if (m.Msg == WM_MOUSEMOVE)
{
System.Drawing.Point currentMousePoint = Control.MousePosition;
// Prevent event from firing twice.
if (previousMousePosition == new System.Drawing.Point(0, 0))
{ return; }
if (previousMousePosition != currentMousePoint)
{
previousMousePosition = currentMousePoint;
MouseMovedEvent(this, new MouseEventArgs(MouseButtons.None, 0, currentMousePoint.X, currentMousePoint.Y, 0));
}
}
// Always allow message to continue to the next filter control
return false;
}
#endregion
}
}
I needed functionality that doesn't exist in the standard ComboBox, so I wrote my own from a TextBox and a form. When the user types in the TextBox, it shows a dropdown as a separate form.
Here's some of the relevant code:
internal class FilteredDropDown : Form
{
public Control OwnerControl { get; set; }
public bool CloseOnLostFocus { get; set; }
protected override OnLostFocus(EventArgs e)
{
if (CloseOnLostFocus && !OwnerControl.IsFocused)
this.Close();
}
protected override OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e)
// highlight the moused over item in the list
}
...
}
public class FilteredCombo : TextBox
{
private FilteredDropDown dropDown;
public FilteredCombo()
{
dropDown = new FilteredDropDown();
dropDown.OwnerControl = this;
}
public void ShowDropDown()
{
if (dropDown.Visible)
return;
dropDown.RefreshFilter();
var loc = PointToScreen(new Point(0, this.Height));
dropDown.Location = loc;
dropDown.CloseOnLostFocus = false;
int selectionStart = this.SelectionStart;
int selectionLength = this.SelectionLength;
dropDown.Show(this);
this.Focus();
this.SelectionStart = selectionStart;
this.SelectionLength = selectionLength;
dropDown.CloseOnLostFocus = false;
}
protected override OnLostFocus(EventArgs e)
{
if (dropDown.Visible && !dropDown.ContainsFocus())
dropDown.Close();
}
protected override OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
ShowDropDown();
}
...
}
There's obviously a whole lot more code than that to deal with all kinds of stuff irrelevent to my question.
The problem is when I put the FilteredCombo on a modal dialog. Somehow the FilteredDropDown form doesn't receive mouse events at all when it is parented by a modal dialog.
I've read something about WinForms filtering out events on all except the current modal dialog, I suspect that is what's going on, but I have no ideas of how to fix it. Is there some way to get the mouse up/down/move/click/etc. events to work when parented by a model dialog?
I had to go digging through the ShowDialog source code, and I found that it calls user32.dll EnableWindow(Handle, false) on all the windows except the shown one. The problem was that the FilteredDropDown already existed by the time the ShowDialog() method got called. I discovered two different ways to fix this:
Don't allow the DropDown to be shown until the parent form is shown. This is a bit trickier to guarantee, so I also implemented the second way.
Re-enable the DropDown window when it is made visible:
[DllImport("user32.dll")]
private static extern bool EnableWindow(IntPtr hWnd, bool enable);
protected override void OnVisibleChanged(EventArg e)
{
base.OnVisibleChanged(e);
if (this.Visible)
{
EnableWindow(this.Handle, true);
}
}
I'm working on a large C# winforms project. After explaining thousands of times to my end users that they have to press tab instead of enter in textboxes, datagrids, and wherever, I decided to add a checkbox somewhere, so users can optionally set if they want to replace enter with tab. I don't like it myself, because I think weird stuff will happen, but I'd like to try it.
The thing is that I have lots of forms, and lots of places where I would have to set a keydown event or similar. I would like to put all of this in one place, on application level. Is there a way for this?
I guess this is not possible, since some controls will expose the keydown event diverently (for example in cells of the gridview). You could iterate through all controls in a form recursively and assign the event for the basic controls though.
The event itself then could be handled in a central place
I'll advice you to create a separate class that the constructor accept the parameters you need (like the textbox), You create global variables and assign the parameters to the variables in the constructor.
Then Create the event handler in the class and then you can put your code in the event handler using the variables.
You can then call the class wherever you need the keydown event
Form level (you can implement the behaviour in the base form and inherit from it):
this.KeyPreview = true;
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
PressedEnter();
base.OnKeyDown(e);
}
private bool PressedEnter()
{
bool res = false; // true if handled
Control ctr = GetFocusedControl();
if (ctr != null && ctr is TextBox)
{
res = this.SelectNextControl(ctr, true, true, true, true);
}
return res;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
internal static extern IntPtr GetFocus();
private Control GetFocusedControl()
{
Control focusedControl = null;
IntPtr focusedHandle = GetFocus();
if (focusedHandle != IntPtr.Zero)
// if control is not a .Net control will return null
focusedControl = Control.FromHandle(focusedHandle);
return focusedControl;
}
It probably can be done on application level too: In your main form you'll have to prefilter messages from the message loop (using message filter: Application.AddMessageFilter(your filter)), check for message WM_KEYDOWN = 0x100, check if the pressed key was ENTER, then handle it same as above.You do it only once, in your main form, it'll work on all your child forms.
In your main form class:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.mouseMessageFilter = new MouseMoveMessageFilter();
this.mouseMessageFilter.TargetForm = this;
Application.AddMessageFilter(this.mouseMessageFilter);
}
protected override void OnClosed(EventArgs e)
{
Application.RemoveMessageFilter(this.mouseMessageFilter);
base.OnClosed(e);
}
private class MouseMoveMessageFilter : IMessageFilter
{
public FormMain TargetForm { get; set; }
public bool PreFilterMessage(ref Message m)
{
if (TargetForm.IsDisposed) return false;
int numMsg = m.Msg;
int VK_RETURN=0x0D;
if (m.Msg == 0x100 &&(int)m.WParam == VK_RETURN) // WM_KEYDOWN and enter pressed
{
if (TargetForm.PressedEnter()) return true;
}
return false;
}
}
sources:
https://stackoverflow.com/a/435510/891715
http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
http://www.autohotkey.com/docs/misc/SendMessageList.htm
It's much simpler to use a MessageFilter in combination with SendKeys:
public partial class Form1 : Form, IMessageFilter
{
public Form1()
{
InitializeComponent();
Application.AddMessageFilter(this);
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == 0x100)//WM_KEYDOWN
{
if (m.WParam.ToInt32() == 0xd)//VK_RETURN = 0xd
{
SendKeys.Send("{TAB}");
return true; //Discard the Enter key
}
}
return false;
}
}
Is there any event that fires, when a win form's listview top item property changes?
You would need a Scroll event to detect that the TopItem might have changed. ListView doesn't have one. Which is probably intentional, the class contains some hacks that work around bugs in the native Windows control, hacks that use scrolling.
These hacks should however not matter much in your case since you only look for a change in the TopItem. You'll want to override the WndProc() method so you can get the LVN_ENDSCROLL message. This worked well although I didn't test it thoroughly. Add a new class to your project and paste the code below. Compile. Drop the new control from the top of the toolbox onto your form. Implement the TopItemChanged event.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class MyListView : ListView {
public event EventHandler TopItemChanged;
protected virtual void OnTopItemChanged(EventArgs e) {
var handler = TopItemChanged;
if (handler != null) handler(this, e);
}
protected override void WndProc(ref Message m) {
// Trap LVN_ENDSCROLL, delivered with a WM_REFLECT + WM_NOTIFY message
if (m.Msg == 0x204e) {
var notify = (NMHDR)Marshal.PtrToStructure(m.LParam, typeof(NMHDR));
if (notify.code == -181 && !this.TopItem.Equals(lastTopItem)) {
OnTopItemChanged(EventArgs.Empty);
lastTopItem = this.TopItem;
}
}
base.WndProc(ref m);
}
private ListViewItem lastTopItem = null;
private struct NMHDR {
public IntPtr hwndFrom;
public IntPtr idFrom;
public int code;
}
}
There is no event specifically for the TopItem property. However you should be able to get the same effect by caching the previous TopItem result and comparing it on other events which are indicators of item reordering: Paint and DrawItem for example.
private void WatchTopItemChanged(ListView listView, Action callOnChanged) {
var lastTopItem = listView.TopItem;
listView.DrawItem += delegate {
if (lastTopItem != listView.TopItem) {
lastTopItem = listView.TopItem;
callOnChanged();
}
};
}