How to get the new window's URL with WebBrowser - c#

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
}
}

Related

how can I return value from show dialog form?

The main form:
fDocForm fDocForm = new fDocForm()
fDocForm.ShowDialog();
if(fDocForm.DialogResult!=null)
//use of id returned from dialog form
else
MessageBox.Show("null");
in the dialog form:
private void button1_Click(object sender, EventArgs e)
{
//insert sql command and returned id of inserted recored
DialogResult = DialogResult.OK;
this.Hide();
}
how could I return the id value from the dialog form to the main form?
One option is to pass data from child form to parent form using an event which is invoked when clicking a button on the child form, data is in a validate state invoke the event then set DialogResult to OK.
The following is a conceptual example where the main form opens a child form for adding a new item of type Note.
If all you need is a single property/value this will still work by changing the delegate signature OnAddNote.
Note class
public class Note : INotifyPropertyChanged
{
private string _title;
private string _content;
private int _id;
public int Id
{
get => _id;
set
{
_id = value;
OnPropertyChanged();
}
}
public string Title
{
get => _title;
set
{
_title = value;
OnPropertyChanged();
}
}
public string Content
{
get => _content;
set
{
_content = value;
OnPropertyChanged();
}
}
public override string ToString() => Title;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Operations class
NewNote method is called from the child form Add button when data is validated
public class Operations
{
public delegate void OnAddNote(Note note);
public static event OnAddNote AddNote;
public static List<Note> NotesList = new List<Note>();
/// <summary>
/// Pass new Note to listeners
/// </summary>
/// <param name="note"></param>
public static void NewNote(Note note)
{
AddNote?.Invoke(note);
}
/// <summary>
/// Edit note, do some validation
/// </summary>
/// <param name="note"></param>
/// <returns></returns>
public static Note EditNote(Note note)
{
throw new NotImplementedException();
}
/// <summary>
/// Load mocked data, for a real application if persisting data use <see cref="LoadNotes"/>
/// </summary>
public static void Mocked()
{
NotesList.Add(new Note()
{
Title = "First",
Content = "My note"
});
}
/// <summary>
/// For a real application which persist your notes we would load from
/// - a database
/// - file (xml, json etc)
/// </summary>
/// <returns></returns>
public static List<Note> LoadNotes()
{
throw new NotImplementedException();
}
/// <summary>
/// Delete a note in <see cref="NotesList"/>
/// </summary>
/// <param name="note"></param>
public static void Delete(Note note)
{
throw new NotImplementedException();
}
public static Note FindByTitle(string title)
{
throw new NotImplementedException();
}
/// <summary>
/// Save data to the data source loaded from <see cref="LoadNotes"/>
/// </summary>
/// <returns>
/// Named value tuple
/// success - operation was successful
/// exception - if failed what was the cause
/// </returns>
public static (bool success, Exception exception) Save()
{
throw new NotImplementedException();
}
}
Child form
public partial class AddNoteForm : Form
{
public AddNoteForm()
{
InitializeComponent();
}
private void AddButton_Click(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(TitleTextBox.Text) && !string.IsNullOrWhiteSpace(NoteTextBox.Text))
{
Operations.NewNote(new Note() {Title = TitleTextBox.Text, Content = NoteTextBox.Text});
DialogResult = DialogResult.OK;
}
else
{
DialogResult = DialogResult.Cancel;
}
}
}
Main form
public partial class Form1 : Form
{
private readonly BindingSource _bindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
Shown += OnShown;
}
private void OnShown(object sender, EventArgs e)
{
Operations.AddNote += OperationsOnAddNote;
Operations.Mocked();
_bindingSource.DataSource = Operations.NotesList;
NotesListBox.DataSource = _bindingSource;
ContentsTextBox.DataBindings.Add("Text", _bindingSource, "Content");
}
private void OperationsOnAddNote(Note note)
{
_bindingSource.Add(note);
NotesListBox.SelectedIndex = NotesListBox.Items.Count - 1;
}
private void AddButton_Click(object sender, EventArgs e)
{
var addForm = new AddNoteForm();
try
{
addForm.ShowDialog();
}
finally
{
addForm.Dispose();
}
}
}
Full source
You can store the id in a public property of the dialog, and the access that property from the main form.

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);
}
}

Passing This(of my main form) to a class that handles direction change/and language change c#/Winforms

I am having trouble with this,
What i am trying to achieve is the following:
I have a main form which is the startup form (MDI Container)
and then a few forms that are the child forms, what i am trying to achieve is the following, this is code that is happening on every screen at the moment, and i want to build it into a class but, i don't know how to pass this (the click events happen on every form at the moment, and i want it to run of a menu click on the MDI Parent(container):
UPDATE
I have gotten it to work with all the ChildForms thanx #JamesBarras this is how my code works now, i have remove the button click events, for it is only running from a MenuStripItem_Click Event
#region Change Language
private void englishToolStripMenuItem_Click(object sender, EventArgs e) {
Class1 cls = new Class1();
/// This is for Main and all the forms of MainForms Children
foreach (var childForm in this.MdiChildren) {
cls.ChangeLanguage(sender, log, childForm, this, this.menuStrip, "en");
//ChangeLanguage("en", childForm);
}
}
private void arabicToolStripMenuItem_Click(object sender, EventArgs e) {
Class1 cls = new Class1();
/// This is for Main and all the forms of MainForms Children
foreach (var childForm in this.MdiChildren) {
cls.ChangeLanguage(sender, log, childForm, this, this.menuStrip, "ar");
//ChangeLanguage( "ar", childForm);
}
}
#endregion
I just created this class as this code was running on the same page as the click event as a Method, I want the class that i am using/created (ChangeLanguage) to know what this is,because on the normal form, i can say this but in a class it doesn't know what the this is on the form that i have my click events, here is my code in the class that i created:
UPDATE
I have fine tuned the Class to do exactly what i need it to do, yet, i still can't translate the MenuStripItems and i am getting the value of this but still no menustrip Translate only the direction switch works
namespace languageChange.Classes { class Class1 {
#region Methods
/// <summary>
/// This is mainly to change the language and the layout direction
/// </summary>
/// <param name="sender"></param>
/// <param name="log"></param>
/// <param name="form"></param>
/// <param name="thiss"></param>
/// <param name="strip"></param>
/// <param name="lang"></param>
public void ChangeLanguage(object sender, Logger log, Form form, Form thiss, Control strip, string lang) {
string senderText = sender.GetType().ToString();
RightToLeft direction = RightToLeft.No;
if (lang == "ar") {
direction = RightToLeft.Yes;
}
thiss.RightToLeft = direction;
CultureInfo CurrentLocale = new CultureInfo(lang);
Thread.CurrentThread.CurrentCulture = new CultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
foreach (Control childForm in form.Controls) {
ComponentResourceManager resources = new ComponentResourceManager(form.GetType());
resources.ApplyResources(childForm, "$this");
childForm.RightToLeft = direction;
if (log.isDebugEnabled) log.Debug("--------------------------------------> c = " + childForm.Name);
RefreshResources(log, lang, childForm, resources, CurrentLocale, strip);
}
}
/// <summary>
/// This is to for the Refresh all the Resources
/// </summary>
/// <param name="log"></param>
/// <param name="lang"></param>
/// <param name="ctrl"></param>
/// <param name="resources"></param>
/// <param name="CurrentLocale"></param>
/// <param name="strip"></param>
private static void RefreshResources(Logger log, string lang, Control ctrl, ComponentResourceManager resources, CultureInfo CurrentLocale, Control strip) {
ctrl.SuspendLayout();
resources.ApplyResources(ctrl, ctrl.Name, CurrentLocale);
foreach (Control control in ctrl.Controls) {
RefreshResources(log, lang, control, resources, CurrentLocale, strip); // recursion
if (strip is ToolStrip) {
RefreshResources(((ToolStrip)strip).Items, resources, CurrentLocale);
}
ctrl.ResumeLayout(true);
if (log.isTraceEnabled) log.Trace("c=" + ctrl.Name);
}
}
/// <summary>
/// Which is done here [Refer to previous Summary]
/// </summary>
/// <param name="col"></param>
/// <param name="resources"></param>
/// <param name="CurrentLocale"></param>
private static void RefreshResources(ToolStripItemCollection col, ComponentResourceManager resources, CultureInfo CurrentLocale) {
foreach (ToolStripMenuItem item in col) {
if (item is ToolStripMenuItem) {
RefreshResources(((ToolStripMenuItem)item).DropDownItems, resources, CurrentLocale);
}
resources.ApplyResources(item, item.Name, CurrentLocale);
}
}
#endregion }}
UPDATE
So i got everything to translate, and change direction except for the MenuStripItems and the Menustrip as well.
Please Help me as this is really important and i am struggling.
This is a Winforms application using Visual Studio 2013 Ultimate, with C#.
Don't mind the logger, it is something i just added to log the trace and debugger
Change the signature of ChangeLanguage(string lang) to ChangeLanguage(Form form, string lang) so that you can pass it the form you wish to alter.
subsequently you now need to refer to the form you are working on as the passed in one. So replace this in that method with form and the forms type typeof(Form1) with form.GetType()
Everywhere you want to change language on any for you can now call
Class1.ChangeLanguage(this, "en");
where this is the current form
To change language for an MDIParent you'll need to this:
private void englishToolStripMenuItem_Click(object sender, EventArgs e)
{
Class1.ChangeLanguage(log, this, "en");
foreach(var child in this.MdiChildren)
{
Class1.ChangeLanguage(log, child, "en");
}
}
Note that to change the language of the MenuStrip itself you'll need to add an overload of RefreshResources to cope with ToolStripItemCollections (they are component based not control based)
private static void RefreshResources(ToolStripItemCollection col, ComponentResourceManager resources, CultureInfo CurrentLocale)
{
foreach(ToolStripItem item in col)
{
if (item is ToolStripMenuItem)
{
RefreshResources(((ToolStripMenuItem)item).DropDownItems, resources, CurrentLocale);
}
resources.ApplyResources(item, item.Name, CurrentLocale);
}
}
and add the following code to the original RefreshResources
if (ctrl is ToolStrip)
{
RefreshResources(((ToolStrip)ctrl).Items, resources, CurrentLocale);
}

OnPaint not getting called if form doesn't have focus

I have a user control with custom painting. Constructor sets styles correctly, from what I can tell. Basic code:
public partial class LineChart2 : UserControl
{
public LineChart2()
{
InitializeComponent();
//Set control styles to eliminate flicker on redraw and to redraw on resize
this.SetStyle(
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.DoubleBuffer,
true);
SetDefaultValues();
}
protected override void OnPaint(PaintEventArgs e)
{
// breakpoint set here for verification
Paint~misc stuff(e.Graphics);
base.OnPaint(e);
}
private void UpdateGraph()
{
// this is called when the data that the control depends on changes
~update stuff();
this.Invalidate();
//this.Refresh();
}
}
The control is contained within a Panel on a standard WinForm.
I've tried both Invalidate and Refresh.
When using Invalidate(), the control will redraw properly as long as the form it is contained in has focus. Drawing is smooth. When I switch focus to another form, drawing ceases even though the events are still firing, and this.Invalidate() is still being called. The form is still fully visible on screen.
When using Refresh(), the control will redraw regardless of whether the form has focus, but the drawing constantly flickers, as if bypassing the double-buffering mechanism.
So how do I get the Invalidate message to properly invoke the OnPaint method regardless of focus?
Documentation says:
Calling the Invalidate method does not
force a synchronous paint; to force a
synchronous paint, call the Update
method after calling the Invalidate
method.
Have you tried calling Update after Invalidate?
You should not force the control do redraw (Update or Refresh) so often. The UI may get not responsive, others controls may not update, because you are giving all UI attention to the forced sync Refresh.
The right way is to draw only when UI is ready to do it. For that you need a render loop. The ApplicationLoopDoWork will be fired every time the UI is ready to draw something. The period depends on the machine speed and what is being redrawn.
The class is based on this post on Tom Miller's Blog.
Here is the class that I use to control that.
Make updates only on the ApplicationLoopDoWork call.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace Utilities.UI
{
/// <summary>
/// WinFormsAppIdleHandler implements a WinForms Render Loop (max FPS possible).
/// Reference: http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
/// </summary>
public sealed class WinFormsAppIdleHandler
{
private readonly object _completedEventLock = new object();
private event EventHandler _applicationLoopDoWork;
//PRIVATE Constructor
private WinFormsAppIdleHandler()
{
Enabled = false;
SleepTime = 10;
}
/// <summary>
/// Singleton from:
/// http://csharpindepth.com/Articles/General/Singleton.aspx
/// </summary>
private static readonly Lazy<WinFormsAppIdleHandler> lazy = new Lazy<WinFormsAppIdleHandler>(() => new WinFormsAppIdleHandler());
public static WinFormsAppIdleHandler Instance { get { return lazy.Value; } }
private bool _enabled = false;
/// <summary>
/// Gets or sets if must fire ApplicationLoopDoWork event.
/// </summary>
public bool Enabled
{
get { return _enabled; }
set {
if (value)
Application.Idle += Application_Idle;
else
Application.Idle -= Application_Idle;
_enabled = value;
}
}
/// <summary>
/// Gets or sets the minimum time betwen ApplicationLoopDoWork fires.
/// </summary>
public int SleepTime { get; set; }
/// <summary>
/// Fires while the UI is free to work. Sleeps for "SleepTime" ms.
/// </summary>
public event EventHandler ApplicationLoopDoWork
{
//Reason of using locks:
//http://stackoverflow.com/questions/1037811/c-thread-safe-events
add
{
lock (_completedEventLock)
_applicationLoopDoWork += value;
}
remove
{
lock (_completedEventLock)
_applicationLoopDoWork -= value;
}
}
/// <summary>
///Application idle loop.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Application_Idle(object sender, EventArgs e)
{
//Try to update interface
while (Enabled && IsAppIdle())
{
OnApplicationIdleDoWork(EventArgs.Empty);
//Give a break to the processor... :)
//8 ms -> 125 Hz
//10 ms -> 100 Hz
Thread.Sleep(SleepTime);
}
}
private void OnApplicationIdleDoWork(EventArgs e)
{
var handler = _applicationLoopDoWork;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Gets if the app is idle.
/// </summary>
/// <returns></returns>
public static bool IsAppIdle()
{
bool isIdle = false;
try
{
Message msg;
isIdle = !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
catch (Exception e)
{
//Should never get here... I hope...
MessageBox.Show("IsAppStillIdle() Exception. Message: " + e.Message);
}
return isIdle;
}
#region Unmanaged Get PeekMessage
// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
#endregion
}
}
You also might try Invalidate(true) to trigger child controls to repaint as well.

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