You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义窗体系统菜单时WndProc订阅出现CS1540错误求助

解决CS1540错误:无法访问受保护的成员Form.WndProc

错误原因

CS1540错误的核心是**Form.WndProc是protected虚方法,不是可订阅的事件**,你不能用+=语法直接绑定它。当前代码尝试访问受保护成员,但上下文没有权限,因此触发该错误。

解决方案

方案1:创建自定义Form子类(推荐)

通过继承Form类并重写WndProc,实现系统菜单消息的处理,这是WinForms中处理窗口消息的标准方式。

// 自定义带系统菜单的窗体类
public class CustomMenuForm : Form
{
    // 系统菜单ID常量
    protected const int SYSMENU_CUSTOM_ITEM_ID = 0x1000;
    protected const int SYSMENU_PAUSE_TIMER_ID = 0x1001;
    protected const int SYSMENU_REFRESH_NOW_ID = 0x1002;
    private const int WM_SYSCOMMAND = 0x0112;
    private const int MF_SEPARATOR = 0x0800;
    private const int MF_STRING = 0x0000;

    // 外部传入业务逻辑委托
    public Action EraseConfigAction { get; set; }
    public Action ToggleTimerPauseAction { get; set; }
    public Action RefreshNowAction { get; set; }

    public CustomMenuForm()
    {
        HandleCreated += CustomMenuForm_HandleCreated;
    }

    private void CustomMenuForm_HandleCreated(object sender, EventArgs e)
    {
        // 初始化系统菜单
        IntPtr systemMenuHandle = GetSystemMenu(this.Handle, false);
        AppendMenu(systemMenuHandle, MF_SEPARATOR, 0, string.Empty);
        AppendMenu(systemMenuHandle, MF_STRING, SYSMENU_CUSTOM_ITEM_ID, "Erase Config");
        AppendMenu(systemMenuHandle, MF_STRING, SYSMENU_PAUSE_TIMER_ID, "Pause Timer");
        AppendMenu(systemMenuHandle, MF_STRING, SYSMENU_REFRESH_NOW_ID, "Refresh Now");
    }

    protected override void WndProc(ref Message m)
    {
        // 处理系统菜单点击消息
        if (m.Msg == WM_SYSCOMMAND)
        {
            switch (m.WParam.ToInt32())
            {
                case SYSMENU_CUSTOM_ITEM_ID:
                    EraseConfigAction?.Invoke();
                    break;
                case SYSMENU_PAUSE_TIMER_ID:
                    ToggleTimerPauseAction?.Invoke();
                    break;
                case SYSMENU_REFRESH_NOW_ID:
                    RefreshNowAction?.Invoke();
                    this.Close();
                    break;
            }
        }
        // 调用基类处理其他消息
        base.WndProc(ref m);
    }

    // 导入Win32 API
    [DllImport("user32.dll")]
    private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

    [DllImport("user32.dll")]
    private static extern bool AppendMenu(IntPtr hMenu, uint uFlags, uint uIDNewItem, string lpNewItem);
}

使用该子类创建窗体:

private void CreateCustomForms(int formCount)
{
    for (int i = 0; i < formCount; i++)
    {
        var form = new CustomMenuForm
        {
            Icon = this.Icon
        };
        // 绑定业务逻辑
        form.EraseConfigAction = EraseConfig;
        form.ToggleTimerPauseAction = ToggleTimerPause;
        form.RefreshNowAction = () =>
        {
            Program.refreshRequested = true;
            refreshTimer.Stop();
        };
        form.Show();
    }
}

方案2:使用IMessageFilter拦截消息

如果不想修改窗体继承关系,可以通过全局消息过滤器拦截目标窗体的系统菜单消息:

// 系统菜单消息过滤器
public class SystemMenuMessageFilter : IMessageFilter
{
    private readonly Form _targetForm;
    private readonly Action _eraseConfig;
    private readonly Action _togglePause;
    private readonly Action _refreshNow;

    private const int WM_SYSCOMMAND = 0x0112;
    private const int SYSMENU_CUSTOM_ITEM_ID = 0x1000;
    private const int SYSMENU_PAUSE_TIMER_ID = 0x1001;
    private const int SYSMENU_REFRESH_NOW_ID = 0x1002;

    public SystemMenuMessageFilter(Form targetForm, Action eraseConfig, Action togglePause, Action refreshNow)
    {
        _targetForm = targetForm;
        _eraseConfig = eraseConfig;
        _togglePause = togglePause;
        _refreshNow = refreshNow;
    }

    public bool PreFilterMessage(ref Message m)
    {
        // 仅处理目标窗体的系统菜单消息
        if (m.HWnd == _targetForm.Handle && m.Msg == WM_SYSCOMMAND)
        {
            switch (m.WParam.ToInt32())
            {
                case SYSMENU_CUSTOM_ITEM_ID:
                    _eraseConfig?.Invoke();
                    return true; // 标记消息已处理,不再传递
                case SYSMENU_PAUSE_TIMER_ID:
                    _togglePause?.Invoke();
                    return true;
                case SYSMENU_REFRESH_NOW_ID:
                    _refreshNow?.Invoke();
                    _targetForm.Close();
                    return true;
            }
        }
        return false; // 其他消息正常传递
    }
}

修改原SetupSystemMenu方法:

private const int SYSMENU_CUSTOM_ITEM_ID = 0x1000;
private const int SYSMENU_PAUSE_TIMER_ID = 0x1001;
private const int SYSMENU_REFRESH_NOW_ID = 0x1002;
private const int MF_SEPARATOR = 0x0800;
private const int MF_STRING = 0x0000;

private void SetupSystemMenu(Form form)
{
    form.Icon = this.Icon;

    form.HandleCreated += (sender, e) =>
    {
        // 初始化系统菜单
        IntPtr systemMenuHandle = GetSystemMenu(form.Handle, false);
        AppendMenu(systemMenuHandle, MF_SEPARATOR, 0, string.Empty);
        AppendMenu(systemMenuHandle, MF_STRING, SYSMENU_CUSTOM_ITEM_ID, "Erase Config");
        AppendMenu(systemMenuHandle, MF_STRING, SYSMENU_PAUSE_TIMER_ID, "Pause Timer");
        AppendMenu(systemMenuHandle, MF_STRING, SYSMENU_REFRESH_NOW_ID, "Refresh Now");

        // 添加消息过滤器
        var filter = new SystemMenuMessageFilter(form, EraseConfig, ToggleTimerPause,
            () => { Program.refreshRequested = true; refreshTimer.Stop(); });
        Application.AddMessageFilter(filter);

        // 窗体关闭时移除过滤器,避免内存泄漏
        form.FormClosed += (s, args) => Application.RemoveMessageFilter(filter);
    };
}

// 保留Win32 API导入
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

[DllImport("user32.dll")]
private static extern bool AppendMenu(IntPtr hMenu, uint uFlags, uint uIDNewItem, string lpNewItem);

方案对比

  • 自定义Form子类:适合批量创建同类型窗体,代码结构清晰,每个窗体独立处理自身消息,无全局依赖。
  • 消息过滤器:适合快速给现有窗体扩展功能,无需修改继承关系,但需注意在窗体关闭时移除过滤器防止内存泄漏。

内容的提问来源于stack exchange,提问作者Dominic Legendre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:30:19