C# 在运行时重写方法

5

我有两个问题。

1)我找到了一个代码小宝石,可以实现控件平滑滚动

很棒。但它覆盖了WndProc方法,所以为了使用它,我必须在设计时拆下放在窗体上的FlowLayoutPanel,子类化FlowLayoutPanel,然后最终实例化我的新类并手动创建所有属性并更改对控件的所有引用为this.Controls ["ControlName"]。(或者我可以创建一个类级变量,它本质上是控件原来的样子,但当它没有在任何地方声明时,它们如何让您在智能感知上使用它?)

现在我只想知道是否实际上有一种运行时方法来实现它。

我能做些简单的事情吗,其中MainPanel是控件的名称:

MainPanel = (SmoothScrollingFlowLayoutPanel)MainPanel

这可能不是那么容易,对吧?即使如此,仍然很烦人,因为我仍然需要有子类(这可能是一个很好的设计决策,但我想要一次性自由地使用它)。所以,是否有可能将代码放入FlowLayoutPanel的父类中,就像这样:

private Delegate void WndProcHandler(ref Message m);
private WndProcHandler w;

public void SomeCode() {
   w = MainPanel.WndProc; // get reference to existing wndproc method
   MainPanel.WndProc = WndProcSmoothScroll; //replace with new method
}

private void WndProcSmoothScroll(ref Message m) { // make smooth scrolling work
   if (
      (m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL)
      && (((int)m.WParam & 0xFFFF) == 5)
   ) {
      m.WParam = (IntPtr)(((int)m.WParam & ~0xFFFF) | 4);
   }
   if (w != null) { w(); }
   base.WndProc(ref m);
  }

我知道这可能有点幼稚。我将WndProc方法视为事件,但它实际上不是事件。
2) 那么我的第二个问题是,如果WndProc是事件而不是方法,我该如何做同样的事情 - 存储事件处理程序的原始列表副本,安装自己的事件处理程序以首先运行,然后调用所有原始事件处理程序?
美味的细节
如果有人感兴趣,我注意到在平滑滚动代码中可以进行优化:
//m.WParam = (IntPtr)(((int)m.WParam & ~0xFFFF) | 4);
m.WParam = (IntPtr)((int)m.WParam ^ 1);

既然我们想将最后16位从5变为4,我们只需翻转最后一位(XOR),而不是先AND再OR。

2个回答

8
如果我正确理解了您的问题,您想要做的就是在运行时覆盖 WndProc。 如果是这样,您只需要一点 Win32 魔法即可。
每个控件都有一个“句柄”,用于标识它,以便操作系统可以向其发送消息。 这个句柄通过每个控件上的 Handle 属性公开。 实际上,底层的 Win32 系统允许您监听任何控件的 WndProc,只要您拥有它的句柄。 这意味着您不必从 Winforms 控件继承来修改其 Win32 行为。 .NET 中的 System.Windows.Forms.NativeWindow 封装了这个底层功能。
以下是实现此目的的示例:
class SmoothScrollIntercept : System.Windows.Forms.NativeWindow
{
    public SmoothScrollIntercept(IntPtr hWnd)
    {
        // assign the handle and listen to this control's WndProc
        this.AssignHandle(hWnd);
    }

    protected override void WndProc(ref Message m)
    {
        // listen to WndProc here, do things

        if ((m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL)
            && (((int)m.WParam & 0xFFFF) == 5)) 
        {
            m.WParam = (IntPtr)(((int)m.WParam & ~0xFFFF) | 4);
        }

        base.WndProc(ref m);
    } 
}

然后在代码后端,将拦截器附加到控件上:

SmoothScrollIntercept intercept = new SmoothScrollIntercept(myControl.Handle);

// myControl is now using smooth scrolling, without inheriting from the control

谢谢Zach,但我已经成功地使我的窗口平稳滚动了。我只是讨厌如何从设计师中撕掉控件并动态地完成所有事情。 - ErikE
@Emtucifor:没问题,我只是想让你知道,你不必继承自FlowLayoutPanel来重写它的WndProc - Zach Johnson
哦,现在我明白了。谢谢。我承认我没有认真阅读。这很有用!事实上,你是100%正确的,这就是我要找的答案。(拍额头) - ErikE

2
不,你所要求的是不可能的。你必须像以前一样创建子类。
即使它是一个事件,你也无法做到你想要的事情。事件的公共接口只公开了addremove;没有办法获取或分配附加到事件的实际委托。
然而,从一个不同的角度看问题,你可能可以利用IMessageFilter接口来实现你想要的最终结果。 编辑 再次查看你的代码后,IMessageFilter不起作用,因为你不能在PreFilterMessage中修改消息;你只能检查或抑制它。你现在的最佳选择是重写父Form中的WndProc并尝试在那里进行操作。看起来没有通用解决方案来解决你的问题。

谢谢。我知道一些可以使用方法完成的JavaScript,所以我只是好奇。现在我想知道,我是否可以同时继承FlowLayoutPanel和UserControl,以便我的新SmoothScrollingFlowLayoutPanel成为我可以在设计时放置在窗体上的项目? - ErikE
@Emtucifor: 不,.NET语言都不允许多重继承。但是你可以从FlowLayoutPanel继承并将你的控件放在工具箱中。如果控件在你的项目中,它应该会自动出现在那里。如果它在另一个项目中,你需要右键单击工具箱,然后点击“选择元素...”,然后浏览到那个.dll文件,并通过这种方式添加控件。 - Adam Robinson
那很有帮助。我知道子类化的UserControls会显示在工具箱中,但不知道子类化的常规控件也会出现在那里。谢谢。 - ErikE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接