0

I have a User Control that contains one Panel filling the entire UC. Within the panel there is a PctureBox and a Label. My issue is trying to make it so that no matter which control my cursor moves across, the backcolor on the panel will change as an indicator. I've made it possible in this way (see code below), but I'm sure this isn't the best way of doing it? Keep in mind I'm going to add maybe a hundred of these, so I'm aiming for it to be fairly optimized.

private void PMain_MouseMove(object sender, MouseEventArgs e)
{
    Panel pan = sender as Panel;
    pan.BackColor = Color.DimGray;
}

private void PMain_MouseLeave(object sender, EventArgs e)
{
    Panel pan = sender as Panel;
    pan.BackColor = originalBackColor;
}

private void PbIcon_MouseMove(object sender, MouseEventArgs e)
{
    pMain.BackColor = Color.DimGray;
}

private void PbIcon_MouseLeave(object sender, EventArgs e)
{
    pMain.BackColor = originalBackColor;
}

private void LTitle_MouseMove(object sender, MouseEventArgs e)
{
    pMain.BackColor = Color.DimGray;
}

private void LTitle_MouseLeave(object sender, EventArgs e)
{
    pMain.BackColor = originalBackColor;
}
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
MadsTheMan
  • 705
  • 1
  • 10
  • 32
  • 2
    https://stackoverflow.com/q/15186828/11683 – GSerg Nov 23 '19 at 16:27
  • Thank you, @GSerg. I'll look into that, looks like something that could be used. Not entirely sure how to use it for my use case yet, but I'm sure I'll figure it out. – MadsTheMan Nov 24 '19 at 08:08

2 Answers2

1

If I'm getting you correctly you are trying to do something like the following

enter image description here

in order to do that without having to do it for every control that your user control has, you can insert a thread WH_GETMESSAGE hook to your main thread and check WM_MOUSEHOVER, WM_MOUSELEAVE, WM_MOUSEMOVE messages for your user control and its children

Below is the code sample for UserControl1 which has one Panel filling the entire UC, one PictureBox (koala in it) and one Label (written label1 on it)

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace WindowsFormsApp1
{
    public partial class UserControl1 : UserControl
    {
        const int WH_GETMESSAGE = 0x03;
        const int WM_MOUSEHOVER = 0x02A1;
        const int WM_MOUSELEAVE = 0x02A3;
        const int WM_MOUSEMOVE = 0x0200;

        private delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", EntryPoint = "SetWindowsHookEx", SetLastError = true)]
        static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll")]
        static extern bool UnhookWindowsHookEx(IntPtr hHook);

        [DllImport("user32.dll")]
        static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);

        [DllImport("user32.dll")]
        static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);

        IntPtr _hook;
        HookProc _hookProc;
        public UserControl1()
        {
            InitializeComponent();

            this.HandleCreated += (sender, e) =>
            {
                _hookProc = new HookProc(GetMsgHookProc);
                _hook = SetWindowsHookEx(
                    WH_GETMESSAGE,
                    _hookProc,
                    GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
                    GetWindowThreadProcessId(this.Handle, IntPtr.Zero));
            };
            this.Disposed += (sender, e) => 
            {
                UnhookWindowsHookEx(_hook);
            };
        }


        private bool IsOurChild(Control ctl)
        {
            if (ctl == null)
                return false;
            if (ctl.Handle == this.Handle || ctl.Parent?.Handle == this.Handle)
                return true;
            return IsOurChild(ctl.Parent);
        }

        private int GetMsgHookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode < 0)
                return CallNextHookEx(_hook, nCode, wParam, lParam);

            Message m = Marshal.PtrToStructure<Message>(lParam);
            Control ctl = Control.FromHandle(m.HWnd);
            if (IsOurChild(ctl))
            {
                if (m.Msg == WM_MOUSEHOVER || m.Msg == WM_MOUSEMOVE)
                    this.BackColor = Color.Red;
                if (m.Msg == WM_MOUSELEAVE)
                    this.BackColor = Color.Blue;
            }

            return CallNextHookEx(_hook, nCode, wParam, lParam);
        }
    }
}
Gurhan Polat
  • 696
  • 5
  • 12
  • Indeed, that GIF example is exactly what I'm trying to accomplish. Spot on. I will give this a try as soon as possible and get back to you with the results. Looks promising, though I must admit that I don't understand all of the code - I just get the general gist of it. Let me know if there is something I should know or if it requires anything like for example specific properties set to a certain value. – MadsTheMan Nov 24 '19 at 13:46
  • 1
    What part of the code you didn't understand? Point out and i will try to explain. If you need more detailed explanation, just say, i will edit my post accordingly. But you can look the following links to get an idea of the concepts win32 message loop (https://learn.microsoft.com/en-us/windows/win32/winmsg/about-messages-and-message-queues), win32 hook mechanism (https://learn.microsoft.com/en-us/windows/win32/winmsg/about-hooks) – Gurhan Polat Nov 24 '19 at 15:31
  • Several parts essentially, probably due to the fact that I've never used `DllImports` and I have no experience in `hooks` and `messages`, but I'll have a look at the docs you sent, I'm sure it'll be helpful. With that said, I added this code to my user control and it works **beautifully**, so with that, I'll accept this as an answer. Though I tried to add the `User Control` to a `FlowLayout` and I noticed the effect is no longer there. It only seems to work directly in the main form. I don't really know how to approach this at this point. I'll see if the docs will make things clearer for me. – MadsTheMan Nov 25 '19 at 09:04
  • 1
    I have edited my answer according to your second problem, can you please try it again. the problem was in IsOurChild function, you may just replace it with the new one – Gurhan Polat Nov 25 '19 at 10:41
  • Perfect! Thank you. This solved the problem and I can confirm that it works in a FlowLayout. I have not yet tested how well this performs when adding several of this User Control, but so far so good. – MadsTheMan Nov 25 '19 at 14:13
0

Maybe you can do like this. Example:

Designer

//SomeButton
this.SomeButton.Click += delegate(object sender, EventArgs e) 
                    { SomeUserControl_Event(sender, e); };
//SomeLabel
this.SomeButton.Click += delegate(object sender, EventArgs e) 
                    { SomeUserControl_Event(sender, e); };

Cs

private void SomeUserControl_Event(object sender, EventArgs e)
{
    pMain.BackColor = originalBackColor;
}

Its only a example

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
GiFarina
  • 1
  • 1