"cwick" is quite right, Windows posts the WM_MOUSWHEEL notification to the window that has the focus. It works when you put a button in the panel because it can get the focus. The next thing that happens is that Windows keeps looking for a Parent control to take the message. Button won't care for it, it's Parent is the panel and it will happily scroll and consume the message.
Other than doctoring with the child controls' ability to take focus (you'll have to override them and call SetStyle(ControlStyles.Selectable)), you could consider changing the way this message gets handled. Many commercial apps seemingly don't have this problem (browsers, Office apps) because they have only a few windows. WF apps usually have a lot, one for each control. Do so by processing the message early, before it gets sent to the focused control. The IMessageFilter interface allows this. This sample code scrolls the control under the mouse instead of the control that has the focus:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsApplication1 {
public partial class Form1 : Form, IMessageFilter {
public Form1() {
InitializeComponent();
Application.AddMessageFilter(this);
}
public bool PreFilterMessage(ref Message m) {
if (m.Msg == 0x20a) {
// WM_MOUSEWHEEL, find the control at screen position m.LParam
Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
IntPtr hWnd = WindowFromPoint(pos);
if (hWnd != IntPtr.Zero && hWnd != m.HWnd && Control.FromHandle(hWnd) != null) {
SendMessage(hWnd, m.Msg, m.WParam, m.LParam);
return true;
}
}
return false;
}
// P/Invoke declarations
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(Point pt);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
}
Beware that this code is active for any window in your app. Make sure you try it and verify that it won't confuse the user too much.