Static function as eventhandler in xaml - c#

I am using this code to simulate the tab functionality in my silverlight application.
I would really like to avoid writing the function a lot of times because it has to be used on quite a lot of textboxes throughout the application. I have created a static class
public static class TabInsert
{
private const string Tab = " ";
public static void textBox_KeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
if (e.Key == Key.Tab)
{
int selectionStart = textBox.SelectionStart;
textBox.Text = String.Format("{0}{1}{2}",
textBox.Text.Substring(0, textBox.SelectionStart),
Tab,
textBox.Text.Substring(textBox.SelectionStart + textBox.SelectionLength, (textBox.Text.Length) - (textBox.SelectionStart + textBox.SelectionLength))
);
e.Handled = true;
textBox.SelectionStart = selectionStart + Tab.Length;
}
}
}
so that I can access it from various places liek this textBox.KeyDown += TabInsert.textBox_KeyDown;
Is there a way I can do this in XAML?

You can create a Behavior (System.Windows.Interactivity namespace) to easily attach to textboxes that in the OnAttached() override subscribes to the event and does the handling as you do and unsubscribes in OnDetaching().
Something like:
public class TabInsertBehavior : Behavior<TextBox>
{
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>
/// Override this to hook up functionality to the AssociatedObject.
/// </remarks>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.KeyDown += textBox_KeyDown;
}
private const string Tab = " ";
public static void textBox_KeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
if (e.Key == Key.Tab)
{
int selectionStart = textBox.SelectionStart;
textBox.Text = String.Format("{0}{1}{2}",
textBox.Text.Substring(0, textBox.SelectionStart),
Tab,
textBox.Text.Substring(textBox.SelectionStart + textBox.SelectionLength, (textBox.Text.Length) - (textBox.SelectionStart + textBox.SelectionLength))
);
e.Handled = true;
textBox.SelectionStart = selectionStart + Tab.Length;
}
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
/// </summary>
/// <remarks>
/// Override this to unhook functionality from the AssociatedObject.
/// </remarks>
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.KeyDown -= textBox_KeyDown;
}
}

Unfortunately, there is no direct way to do this in XAML.The event handlers you write in the code behind must be instance methods and cannot be static methods. These methods must be defined by the partial class within the CLR namespace identified by x:Class. You cannot qualify the name of an event handler to instruct a XAML processor to look for an event handler for event wiring in a different class scope.

Related

Custom control: Adding custom properties from another class [duplicate]

Im trying to create a custom control that inherits NumericUpDown to show a settable unit.
This is (visually) what I've got so far:
My Code: Looks a bit long, but isnt doing that much
class NumericUpDownUnit : NumericUpDown
{
public event EventHandler ValueChanged;
/// <summary>
/// Constructor creates a label
/// </summary>
public NumericUpDownUnit()
{
this.TextChanged += new EventHandler(TextChanged_Base);
this.Maximum = 100000000000000000;
this.DecimalPlaces = 5;
this.Controls.Add(lblUnit);
lblUnit.BringToFront();
UpdateUnit();
}
public void TextChanged_Base(object sender, EventArgs e)
{
if(ValueChanged != null)
{
this.ValueChanged(sender, e);
}
}
/// <summary>
/// My designer property
/// </summary>
private Label lblUnit = new Label();
[Description("The text to show as the unit.")]
public string Unit
{
get
{
return this.lblUnit.Text;
}
set
{
this.lblUnit.Text = value;
UpdateUnit();
}
}
/// <summary>
/// When unit has changed, calculate new label-size
/// </summary>
public void UpdateUnit()
{
System.Drawing.Size size = TextRenderer.MeasureText(lblUnit.Text, lblUnit.Font);
lblUnit.Padding = new Padding(0, 0, 0, 3);
lblUnit.Size = new System.Drawing.Size(size.Width, this.Height);
lblUnit.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
lblUnit.BackColor = System.Drawing.Color.Transparent;
lblUnit.Location = new System.Drawing.Point(this.Width-lblUnit.Width-17, 0);
}
/// <summary>
/// If text ends with seperator, skip updating text as it would parse without decimal palces
/// </summary>
protected override void UpdateEditText()
{
if (!this.Text.EndsWith(".") && !this.Text.EndsWith(","))
Text = Value.ToString("0." + new string('#', DecimalPlaces));
}
/// <summary>
/// Culture fix
/// </summary>
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (e.KeyChar.Equals('.') || e.KeyChar.Equals(','))
{
e.KeyChar = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator.ToCharArray()[0];
}
base.OnKeyPress(e);
}
/// <summary>
/// When size changes, call UpdateUnit() to recalculate the lable-size
/// </summary>
protected override void OnResize(EventArgs e)
{
UpdateUnit();
base.OnResize(e);
}
/// <summary>
/// Usability | On enter select everything
/// </summary>
protected override void OnEnter(EventArgs e)
{
this.Select(0, this.Text.Length);
base.OnMouseEnter(e);
}
/// <summary>
/// If, when leaving, text ends with a seperator, cut it out
/// </summary>
protected override void OnLeave(EventArgs e)
{
if(this.Text.EndsWith(System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator))
{
this.Text = this.Text.Substring(0, this.Text.Length - 1);
}
base.OnLeave(e);
}
}
My problem:
The lable is currently covering the end of the box. So if a big value comes in (or the size is low) it gets covered by the label as seen in here:
I know that the NumericUpDown has something like a scroll-function when a typed in value is longer than the size of the inputbox. This is triggered at the end of the box.
Is there in any way the possibility of setting up something like padding for the text inside the box? For example setting the padding on the right to the size of my label?
I like this custom control pretty much but this one last thing is annoying.
Unfortunately I dont know how to lookup the properties of an existing control as for example there is a method called UpdateEditText(). Maybe someone can tell me how to lookup this base functions/properties.
Thanks a lot!
NumericUpDown is a control which inherits from UpDownBase composite control. It contains an UpDownEdit and an UpDownButtons control. The UpDownEdit is a TextBox. You can change appearance of the control and its children. For example, you can add a Label to the textbox control and dock it to the right of TextBox, then set text margins of textbox by sending an EM_SETMARGINS message to get such result:
Code
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ExNumericUpDown : NumericUpDown
{
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hwnd, int msg, int wParam, int lParam);
private const int EM_SETMARGINS = 0xd3;
private const int EC_RIGHTMARGIN = 2;
private Label label;
public ExNumericUpDown() : base()
{
var textBox = Controls[1];
label = new Label() { Text = "MHz", Dock = DockStyle.Right, AutoSize = true };
textBox.Controls.Add(label);
}
public string Label
{
get { return label.Text; }
set { label.Text = value; if (IsHandleCreated) SetMargin(); }
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetMargin();
}
private void SetMargin()
{
SendMessage(Controls[1].Handle, EM_SETMARGINS, EC_RIGHTMARGIN, label.Width << 16);
}
}

Removing PropertyChanged-item from BindingList causes list to Reset

User defined class inherits from INotifyPropertyChanged.
In user defined class some property broadcasts PropertyChanged event.
During that event object itself is removed from BindingList.
Event continues execution and BindingList gets ListChangedType.Reset event.
What can be done to avoid Reset event ?
(Haven't seen answer to this question, so decided to add both - question and answer)
Google for "BindingList Child_PropertyChanged"
gives following code snipet:
http://referencesource.microsoft.com/#System/compmod/system/componentmodel/BindingList.cs,e757be5fba0e6000,references
Means that if event is received by BindingList, and given item is not in BindingList - it will trigger list reset.
But because PropertyChanged.Invoke collect whole list of events before starting to broadcast before execution - event will be invoked anyway independently of whether item is in list or not.
Let following code snipet to demonstrate error:
Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Reflection;
using System.Security;
namespace Eventing
{
public class ThisItem : MulticastNotifyPropertyChanged
{
void Test()
{
}
String _name;
public String name2
{
get {
return _name;
}
set
{
_name = value;
Console.WriteLine("---------------------------");
Console.WriteLine("Invoke test #2");
Console.WriteLine("---------------------------");
Invoke(this, new PropertyChangedEventArgs("name"));
}
}
public String name1
{
get {
return _name;
}
set
{
_name = value;
#if TESTINVOKE
Console.WriteLine("---------------------------");
Console.WriteLine("Invoke test #1");
Console.WriteLine("---------------------------");
InvokeFast(this, new PropertyChangedEventArgs("name"));
#endif
}
}
};
class Program
{
static public BindingList<ThisItem> testList;
static void Main(string[] args)
{
testList = new BindingList<ThisItem>();
ThisItem t = new ThisItem();
testList.ListChanged += testList_ListChanged;
t.PropertyChanged += t_PropertyChanged;
t.PropertyChanged += t_PropertyChanged2;
testList.Add(t);
t.name1 = "testing";
Console.WriteLine("---------------------------");
t.PropertyChanged -= t_PropertyChanged;
t.PropertyChanged -= t_PropertyChanged2;
t.PropertyChanged += t_PropertyChanged;
testList.Add(t);
t.PropertyChanged += t_PropertyChanged2;
t.name2 = "testing";
}
static void testList_ListChanged(object sender, ListChangedEventArgs e)
{
Console.WriteLine("3) List changed: " + e.ListChangedType.ToString() + ((e.ListChangedType == ListChangedType.Reset) ? " (*** UPS! ***)": ""));
}
static void t_PropertyChanged2(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("2) t_PropertyChanged2: " + e.PropertyName);
}
static void t_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("1) t_PropertyChanged: " + e.PropertyName);
testList.Remove((ThisItem)sender);
}
}
}
MulticastNotifyPropertyChanged.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace System
{
/// <summary>
/// class which implements INotifyPropertyChanged in such manner that event can be broadcasted in safe manner -
/// even if given item is removed from BindingList, event in BindingList (Child_PropertyChanged) won't be
/// triggered.
/// </summary>
public class MulticastNotifyPropertyChanged : INotifyPropertyChanged
{
/// <summary>
/// List of all registered events. List can change during event broadcasting.
/// </summary>
List<PropertyChangedEventHandler> _PropertyChangedHandlers = new List<PropertyChangedEventHandler>();
/// <summary>
/// Next broadcasted event index.
/// </summary>
int iFuncToInvoke;
#if TESTINVOKE
PropertyChangedEventHandler _PropertyChangedAllInOne;
#endif
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add
{
#if TESTINVOKE
_PropertyChangedAllInOne += value;
#endif
_PropertyChangedHandlers.Add(value);
}
remove
{
#if TESTINVOKE
_PropertyChangedAllInOne -= value;
#endif
int index = _PropertyChangedHandlers.IndexOf(value);
if (index == -1)
return;
if (iFuncToInvoke >= index) //Scroll back event iterator if needed.
iFuncToInvoke--;
#if TESTINVOKE
Console.WriteLine("Unregistering event. Iterator value: " + iFuncToInvoke.ToString());
#endif
_PropertyChangedHandlers.Remove(value);
}
}
/// <summary>
/// Just an accessor, so no cast would be required on client side.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged
{
add
{
((INotifyPropertyChanged)this).PropertyChanged += value;
}
remove
{
((INotifyPropertyChanged)this).PropertyChanged -= value;
}
}
/// <summary>
/// Same as normal Invoke, except this plays out safe - if item is removed from BindingList during event broadcast -
/// event won't be fired in removed item direction.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void Invoke(object sender, PropertyChangedEventArgs e)
{
for (iFuncToInvoke = 0; iFuncToInvoke < _PropertyChangedHandlers.Count; iFuncToInvoke++)
{
#if TESTINVOKE
Console.WriteLine("Invoke: " + iFuncToInvoke.ToString());
#endif
_PropertyChangedHandlers[iFuncToInvoke].Invoke(sender, e);
}
}
#if TESTINVOKE
public void InvokeFast( object sender, PropertyChangedEventArgs e )
{
_PropertyChangedAllInOne.Invoke(sender, e);
}
#endif
}
} //namespace System
Will result in following execution flow:
3) List changed: ItemAdded
---------------------------
Invoke test #1
---------------------------
1) t_PropertyChanged: name
Unregistering event. Iterator value: 0
3) List changed: ItemDeleted
2) t_PropertyChanged2: name
3) List changed: Reset (*** UPS! ***)
---------------------------
Unregistering event. Iterator value: -1
Unregistering event. Iterator value: -1
3) List changed: ItemAdded
---------------------------
Invoke test #2
---------------------------
Invoke: 0
1) t_PropertyChanged: name
Unregistering event. Iterator value: 0
3) List changed: ItemDeleted
Invoke: 1
2) t_PropertyChanged2: name
This solved list eventing - however it can create more problems with items adding to BindingList durign event. This code can probably we fixed also to not to broadcast events to newly added items (like normal BindingList does), but this is something you can work out on your own if needed.

How to get the new window's URL with WebBrowser

I want to crawl a webpage. The problem is, this webpage has an encrypted link which can only be clicked. Using webBrowser.Navigate wont work. I managed to simulate a clicking action and it opened a new window. Now what I want is to get the new window's url.
private void Form1_Load(object sender, EventArgs e)
{
webBrowser1.Navigate(#"http://www.downarchive.ws/software/downloaders/795011-easy-mp3-downloader-4536.html");
}
private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
HtmlElementCollection links = webBrowser1.Document.GetElementsByTagName("a");
foreach (HtmlElement link in links)
{
if (link.GetAttribute("href").Contains(#"http://www.downarchive.ws/engine/go.php?url="))
{
link.InvokeMember("Click");
break;
}
}
}
private void webBrowser1_NewWindow(object sender, CancelEventArgs e)
{
var webBrowser = (WebBrowser)sender;
MessageBox.Show(webBrowser.Url.ToString());
}
1) You don't need to invoke click. It is better to use Navigate method.
When you invoke click, the link may open in new window, some additional javascript may be executed, etc.
2) If you need to get the url after all redirections, there is DocumentCompleted event:
WebBrowserDocumentCompletedEventHandler onDocumentCompleted = (sender, e) => {
Uri theUlrThatYouNeed = e.Url;
webBrowser1.DocumentCompleted -= onDocumentCompleted;
};
webBrowser1.DocumentCompleted += onDocumentCompleted;
webBrowser1.Navigate("your encrypted url");
3) If the link is open in external IE window, it's gone for you - you can't control the external browser and receive events from it. Sometimes, redirections can open new window. To prevent this, you can use the extended WebBrowser class:
namespace ExtendedWebBrowser {
[ComImport, TypeLibType(TypeLibTypeFlags.FHidden),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D")]
public interface DWebBrowserEvents2 {
/// <summary>
///
/// </summary>
/// <param name="ppDisp">
/// An interface pointer that, optionally, receives the IDispatch interface
/// pointer of a new WebBrowser object or an InternetExplorer object.
/// </param>
/// <param name="Cancel">
/// value that determines whether the current navigation should be canceled
/// </param>
/// <param name="dwFlags">
/// The flags from the NWMF enumeration that pertain to the new window
/// See http://msdn.microsoft.com/en-us/library/bb762518(VS.85).aspx.
/// </param>
/// <param name="bstrUrlContext">
/// The URL of the page that is opening the new window.
/// </param>
/// <param name="bstrUrl">The URL that is opened in the new window.</param>
[DispId(0x111)]
void NewWindow3(
[In, Out, MarshalAs(UnmanagedType.IDispatch)] ref object ppDisp,
[In, Out] ref bool Cancel,
[In] uint dwFlags,
[In, MarshalAs(UnmanagedType.BStr)] string bstrUrlContext,
[In, MarshalAs(UnmanagedType.BStr)] string bstrUrl);
}
public partial class WebBrowserEx : WebBrowser {
AxHost.ConnectionPointCookie cookie;
DWebBrowserEvent2Helper helper;
[Browsable(true)]
public event EventHandler<WebBrowserNewWindowEventArgs> NewWindow3;
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
public WebBrowserEx() {
}
/// <summary>
/// Associates the underlying ActiveX control with a client that can
/// handle control events including NewWindow3 event.
/// </summary>
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void CreateSink() {
base.CreateSink();
helper = new DWebBrowserEvent2Helper(this);
cookie = new AxHost.ConnectionPointCookie(
this.ActiveXInstance, helper, typeof(DWebBrowserEvents2));
}
/// <summary>
/// Releases the event-handling client attached in the CreateSink method
/// from the underlying ActiveX control
/// </summary>
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void DetachSink() {
if (cookie != null) {
cookie.Disconnect();
cookie = null;
}
base.DetachSink();
}
/// <summary>
/// Raises the NewWindow3 event.
/// </summary>
protected virtual void OnNewWindow3(WebBrowserNewWindowEventArgs e) {
if (this.NewWindow3 != null) {
this.NewWindow3(this, e);
}
}
private class DWebBrowserEvent2Helper : StandardOleMarshalObject, DWebBrowserEvents2 {
private WebBrowserEx parent;
public DWebBrowserEvent2Helper(WebBrowserEx parent) {
this.parent = parent;
}
/// <summary>
/// Raise the NewWindow3 event.
/// If an instance of WebBrowser2EventHelper is associated with the underlying
/// ActiveX control, this method will be called When the NewWindow3 event was
/// fired in the ActiveX control.
/// </summary>
public void NewWindow3(ref object ppDisp, ref bool Cancel, uint dwFlags,
string bstrUrlContext, string bstrUrl) {
var e = new WebBrowserNewWindowEventArgs(bstrUrl, Cancel);
this.parent.OnNewWindow3(e);
Cancel = e.Cancel;
}
}
}
public class WebBrowserNewWindowEventArgs : EventArgs {
public String Url { get; set; }
public Boolean Cancel { get; set; }
public WebBrowserNewWindowEventArgs(String url, Boolean cancel) {
this.Url = url;
this.Cancel = cancel;
}
}
}
void WebBrowser_NewWindow(object sender, WebBrowserNewWindowEventArgs e) {
if (!string.IsNullOrEmpty(e.Url)) {
//Prevent new window
e.Cancel = true;
// Navigate to url from new window
Navigate(e.Url);
}
}
So, if you replace your WebBrowser control on its improved version, you will be able to prevent new window.
I know the question is very old but I solved it this way: add new reference, in COM choose Microsoft Internet Controls and in the code, before the click that opens a new window add the following:
SHDocVw.WebBrowser_V1 axBrowser = (SHDocVw.WebBrowser_V1)webBrowser1.ActiveXInstance;
axBrowser.NewWindow += axBrowser_NewWindow;
and then add the following method:
void axBrowser_NewWindow(string URL, int Flags, string TargetFrameName, ref object PostData, string Headers, ref bool Processed)
{
Processed = true;
webBrowser1.Navigate(URL);
}
You can use document events to retrieve the element under the mouse which is clicked and store it as currentElement, then on NewWindow event of the webbrowser, read the href of the currentElement and navigate to it.
// after the document loaded
webBrowser1.Document.MouseMove += Document_MouseMove;
//On document mouse move, set the current Element
HtmlElement curElement;
void Document_MouseMove(object sender, HtmlElementEventArgs e)
{
curElement = webBrowser1.Document.GetElementFromPoint(e.ClientMousePosition);
}
// Now you have the clicked element
void webBrowser1_NewWindow(object sender, CancelEventArgs e)
{
e.Cancel = true;
if (curElement != null && curElement.TagName == "A")
{
string href = curElement.GetAttribute("href");
// do whatever
}
}

How to forward messages (eg. mouse wheel) to another Control without stealing focus and without P/Invoke?

I want to forward a message (such as WM_MOUSEWHEEL) when I'm over this control with the mouse, without stealing the focus. This problem can be easily solved intercepting the message with an IMessageFilter (to be added to the application message pump) and forwarding it with the P/Invoke(d) SendMessage(). The question is: can I do the same without using P/Invoke (solutions I've found in StackOverflow use P/Invoke)? If not, why?
The code below is my solution with P/Invoke. I use it with just new MessageForwarder(control, 0x20A).
/// <summary>
/// This class implements a filter for the Windows.Forms message pump allowing a
/// specific message to be forwarded to the Control specified in the constructor.
/// Adding and removing of the filter is done automatically.
/// </summary>
public class MessageForwarder : IMessageFilter
{
#region Fields
private Control _Control;
private Control _PreviousParent;
private HashSet<int> _Messages;
private bool _IsMouseOverControl;
#endregion // Fields
#region Constructors
public MessageForwarder(Control control, int message)
: this(control, new int[] { message }) { }
public MessageForwarder(Control control, IEnumerable<int> messages)
{
_Control = control;
_Messages = new HashSet<int>(messages);
_PreviousParent = control.Parent;
_IsMouseOverControl = false;
control.ParentChanged += new EventHandler(control_ParentChanged);
control.MouseEnter += new EventHandler(control_MouseEnter);
control.MouseLeave += new EventHandler(control_MouseLeave);
control.Leave += new EventHandler(control_Leave);
if (control.Parent != null)
Application.AddMessageFilter(this);
}
#endregion // Constructors
#region IMessageFilter members
public bool PreFilterMessage(ref Message m)
{
if (_Messages.Contains(m.Msg) && _Control.CanFocus && !_Control.Focused
&& _IsMouseOverControl)
{
SendMessage(_Control.Handle, m.Msg, m.WParam, m.LParam);
return true;
}
return false;
}
#endregion // IMessageFilter
#region Event handlers
void control_ParentChanged(object sender, EventArgs e)
{
if (_Control.Parent == null)
Application.RemoveMessageFilter(this);
else
{
if (_PreviousParent == null)
Application.AddMessageFilter(this);
}
_PreviousParent = _Control.Parent;
}
void control_MouseEnter(object sender, EventArgs e)
{
_IsMouseOverControl = true;
}
void control_MouseLeave(object sender, EventArgs e)
{
_IsMouseOverControl = false;
}
void control_Leave(object sender, EventArgs e)
{
_IsMouseOverControl = false;
}
#endregion // Event handlers
#region Support
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
#endregion // Support
}
EDIT: Full solution in my answer
Found a method: you have to inherit NativeWindow, assign the handle of the chosen control to it an call the protected WndProc after you have intercepted a message in any way you prefer (in my case, the inherited class is even an IMessageFilter so I can easily plug it to the application). I use it with new MessageForwarder(anycontrol, 0x20A) to redirect mouse wheel.
So it's possible to intercept and forward messages to any control without p/invoke. It was well hidden though.
/// <summary>
/// This class implements a filter for the Windows.Forms message pump allowing a
/// specific message to be forwarded to the Control specified in the constructor.
/// Adding and removing of the filter is done automatically.
/// </summary>
public class MessageForwarder : NativeWindow, IMessageFilter
{
#region Fields
private Control _Control;
private Control _PreviousParent;
private HashSet<int> _Messages;
private bool _IsMouseOverControl;
#endregion // Fields
#region Constructors
public MessageForwarder(Control control, int message)
: this(control, new int[] { message }) { }
public MessageForwarder(Control control, IEnumerable<int> messages)
{
_Control = control;
AssignHandle(control.Handle);
_Messages = new HashSet<int>(messages);
_PreviousParent = control.Parent;
_IsMouseOverControl = false;
control.ParentChanged += new EventHandler(control_ParentChanged);
control.MouseEnter += new EventHandler(control_MouseEnter);
control.MouseLeave += new EventHandler(control_MouseLeave);
control.Leave += new EventHandler(control_Leave);
if (control.Parent != null)
Application.AddMessageFilter(this);
}
#endregion // Constructors
#region IMessageFilter members
public bool PreFilterMessage(ref Message m)
{
if (_Messages.Contains(m.Msg) && _Control.CanFocus && !_Control.Focused
&& _IsMouseOverControl)
{
m.HWnd = _Control.Handle;
WndProc(ref m);
return true;
}
return false;
}
#endregion // IMessageFilter
#region Event handlers
void control_ParentChanged(object sender, EventArgs e)
{
if (_Control.Parent == null)
Application.RemoveMessageFilter(this);
else
{
if (_PreviousParent == null)
Application.AddMessageFilter(this);
}
_PreviousParent = _Control.Parent;
}
void control_MouseEnter(object sender, EventArgs e)
{
_IsMouseOverControl = true;
}
void control_MouseLeave(object sender, EventArgs e)
{
_IsMouseOverControl = false;
}
void control_Leave(object sender, EventArgs e)
{
_IsMouseOverControl = false;
}
#endregion // Event handlers
}
I've found a much simpler solution that can be applied only if the message you are trying to forward has a corresponding event. For example, for the mousewheel event:
// Redirect the mouse wheel event from panel1 to panel2.
// When the panel1 is focused and the mouse wheel is used the panel2 will scroll.
private void panel1_MouseWheel(object sender, MouseEventArgs e)
{
// Get the MouseWheel event handler on panel2
System.Reflection.MethodInfo onMouseWheel =
panel2.GetType().GetMethod("OnMouseWheel",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
// Call the panel2 mousehwweel event with the same parameters
onMouseWheel.Invoke(panel2, new object[] { e });
}
It really depends on the kind of events and their number. How about just passing events such as mouse movement to your parent control (e.g. to make the control behave "transparent")?
One of the event handlers in your control could look like this (code out of my head without testing):
private void MyControl_MouseMove(object sender, MouseEventArgs e)
{
if(Parent == null)
return;
// add this control's offsets first so the coordinates fit to the parent control
e.X += this.Top;
e.Y += this.Left;
if(parent.MouseMove != null)
parent.MouseMove(sender, e);
}

OnMouseEnter for all controls on a form

I have OnMouseEnter and OnMouseLeave event handlers setup for my form. When the mouse moves over the form I want to set the opacity to 100% and when it moves away I want to set it to 25%. It works well, except when the mouse moves over one of the buttons on the form. The OnMouseLeave event fires and hides the form again. Is there a good way to handle this, without having to wire up OnMouseEnter for every control on the form?
EDIT: I'm going to leave this answer here, even though it can't be made to work reliably. The reason: to prevent somebody else from trying the same thing. See end of message for the reason it won't work.
You can do this fairly easily for the client rectangle by getting the cursor position and checking to see if it's within the Form's client area:
private void Form1_MouseLeave(object sender, EventArgs e)
{
Point clientPos = PointToClient(Cursor.Position);
if (!ClientRectangle.Contains(clientPos))
{
this.Opacity = 0.25;
}
}
This assumes that none of your child controls will be changing the opacity.
However, you'll find that it's a less than perfect solution, because when the mouse goes to the title bar, the Form goes to 0.25%. You could fix that by checking to see if the mouse position is within the window rect (using the Bounds property), but then your window will remain opaque if the mouse moves off the title bar and out of the window.
You have a similar problem when entering the title bar from outside.
I think you'll have to handle the WM_NCMOUSEENTER and WM_NCMOUSELEAVE messages in order to make this work reliably.
Why it can't work:
Even handling the non-client area notifications can fail. It's possible for the mouse to enter on a child control, which would prevent the Form from being notified.
I think it is impossible to do, without handling the MouseEnter and MouseLeave events of all the children, but you do not have to wire them manually.
Here is some code I copied & pasted from a project of mine. It does almost what you described here. I actually copied the idea and the framework from this site.
In the constructor I call the AttachMouseOnChildren() to attach the events.
The OnContainerEnter and OnContainerLeave are used to handle the mouse entering/leaving the form itself.
#region MouseEnter & Leave
private bool _childControlsAttached = false;
/// <summary>
/// Attach enter & leave events to child controls (recursive), this is needed for the ContainerEnter &
/// ContainerLeave methods.
/// </summary>
private void AttachMouseOnChildren() {
if (_childControlsAttached) {
return;
}
this.AttachMouseOnChildren(this.Controls);
_childControlsAttached = true;
}
/// <summary>
/// Attach the enter & leave events on a specific controls collection. The attachment
/// is recursive.
/// </summary>
/// <param name="controls">The collection of child controls</param>
private void AttachMouseOnChildren(System.Collections.IEnumerable controls) {
foreach (Control item in controls) {
item.MouseLeave += new EventHandler(item_MouseLeave);
item.MouseEnter += new EventHandler(item_MouseEnter);
this.AttachMouseOnChildren(item.Controls);
}
}
/// <summary>
/// Will be called by a MouseEnter event, with any of the controls within this
/// </summary>
void item_MouseEnter(object sender, EventArgs e) {
this.OnMouseEnter(e);
}
/// <summary>
/// Will be called by a MouseLeave event, with any of the controls within this
/// </summary>
void item_MouseLeave(object sender, EventArgs e) {
this.OnMouseLeave(e);
}
/// <summary>
/// Flag if the mouse is "entered" in this control, or any of its children
/// </summary>
private bool _containsMouse = false;
/// <summary>
/// Is called when the mouse entered the Form, or any of its children without entering
/// the form itself first.
/// </summary>
protected void OnContainerEnter(EventArgs e) {
// No longer transparent
this.Opacity = 1;
}
/// <summary>
/// Is called when the mouse leaves the form. When the mouse leaves the form via one of
/// its children, this will also call OnContainerLeave
/// </summary>
/// <param name="e"></param>
protected void OnContainerLeave(EventArgs e) {
this.Opacity = DEFAULT_OPACITY;
}
/// <summary>
/// <para>Is called when a MouseLeave occurs on this form, or any of its children</para>
/// <para>Calculates if OnContainerLeave should be called</para>
/// </summary>
protected override void OnMouseLeave(EventArgs e) {
Point clientMouse = PointToClient(Control.MousePosition);
if (!ClientRectangle.Contains(clientMouse)) {
this._containsMouse = false;
OnContainerLeave(e);
}
}
/// <summary>
/// <para>Is called when a MouseEnter occurs on this form, or any of its children</para>
/// <para>Calculates if OnContainerEnter should be called</para>
/// </summary>
protected override void OnMouseEnter(EventArgs e) {
if (!this._containsMouse) {
_containsMouse = true;
OnContainerEnter(e);
}
}
#endregion
I think one way to reliably handle the mouse events you're interested is to set up an IMessageFilter on your Application object from which you can intercept all mouse messages (WM_MOUSEMOVE etc ..) even if they are sent to child controls of the form.
Here's some demo code:
using System;
using System.Windows.Forms;
namespace Test
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
public static Form frm = null;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
frm = new Form1 {Opacity = 0.25};
frm.Controls.Add(new Button{Dock = DockStyle.Fill, Text = "Ok"});
Application.AddMessageFilter(new MouseMoveFilter());
Application.Run(frm);
}
}
public class MouseMoveFilter : IMessageFilter
{
#region IMessageFilter Members
private const int WM_MOUSELEAVE = 0x02A3;
private const int WM_NCMOUSEMOVE = 0x0A0;
private const int WM_MOUSEMOVE = 0x0200;
private const int WM_NCMOUSELEAVE = 0x2A2;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_NCMOUSEMOVE:
case WM_MOUSEMOVE:
Program.frm.Opacity = 1;
break;
case WM_NCMOUSELEAVE:
case WM_MOUSELEAVE:
if (!Program.frm.Bounds.Contains(Control.MousePosition))
Program.frm.Opacity = 0.25;
break;
}
return false;
}
#endregion
}
}
Alternatively you can inherit from Form class and override PreProcessMessage() to accomplish the same thing ...

Categories