Метод переопределения С# во время выполнения

У меня есть два вопроса.

1) Я нашел небольшую жемчужину кода для как сделать плавная прокрутка элемента управления.

Отлично. Но он переопределяет метод WndProc, поэтому, чтобы использовать его, мне пришлось вырвать FlowLayoutPanel, который я поместил на форму во время разработки, создать подкласс FlowLayoutPanel, а затем, наконец, создать экземпляр моего нового класса, создать все свойства вручную и изменить все ссылки. чтобы элемент управления был this.Controls["ControlName"]. (Или, я думаю, я мог бы создать переменную уровня класса, которая, по сути, является тем, чем изначально был элемент управления, хотя как они позволяют вам использовать intellisense для него, если он нигде не объявлен?)

Так что теперь мне просто интересно, был ли на самом деле способ сделать это во время выполнения.

Могу ли я сделать что-то простое, как это, где 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, мы можем просто поменять местами последний бит (исключающее ИЛИ), а не И, а затем ИЛИ.


person ErikE    schedule 14.08.2010    source источник


Ответы (2)


Если я правильно понимаю ваш вопрос, все, что вы хотите сделать, это переопределить WndProc во время выполнения. Если это так, все, что вам нужно, это немного магии Win32.

Каждый элемент управления имеет «дескриптор», который идентифицирует его, чтобы операционная система могла отправлять ему сообщения. Этот дескриптор предоставляется через свойство Handle для каждого элемента управления. Базовая система Win32 фактически позволяет вам прослушивать WndProc любого элемента управления, пока у вас есть его дескриптор. Это означает, что вам не нужно наследовать от элемента управления Winforms, чтобы изменить его поведение Win32. System.Windows.Forms.NativeWindow в .NET заключает в себе эту базовую функциональность.

Вот пример того, как вы можете это сделать:

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
person Zach Johnson    schedule 14.08.2010
comment
Спасибо, Зак, но я уже добился плавной прокрутки окон. Я просто ненавидел то, как мне приходилось вырывать управление из дизайнера и делать все динамически. - person ErikE; 14.08.2010
comment
@Emtucifor: Нет проблем, я просто хотел сообщить вам, что вам не нужно наследовать от FlowLayoutPanel, чтобы переопределить его WndProc. - person Zach Johnson; 14.08.2010
comment
О, теперь я понял. Спасибо. Признаюсь, я читал не так внимательно, как следовало бы. Это полезно! На самом деле, вы правы на 100%, это ответ, который я искал. (хлопает по лбу) - person ErikE; 14.08.2010

Нет, то, о чем вы просите, невозможно. Вам придется создавать подклассы, как вы это делали раньше.

Даже если бы это было событием, вы бы не смогли сделать то, что вам нужно. Открытый интерфейс для события предоставляет только add и remove; невозможно получить или назначить фактического делегата (ов), прикрепленного к событию.

Однако, глядя на проблему с другой точки зрения, вы можете использовать IMessageFilter для достижения желаемого конечного результата.

Изменить

После повторного просмотра вашего кода IMessageFilter не будет работать, так как вы не можете изменить сообщение в пределах PreFilterMessage; вы можете только исследовать или подавить его. Лучше всего на этом этапе переопределить WndProc в родительском Form и попытаться произвести там свои манипуляции. Не похоже, что есть универсальное решение вашей проблемы.

person Adam Robinson    schedule 14.08.2010
comment
Спасибо. Я немного знаю javascript, где вы можете сделать это с помощью методов, поэтому мне было просто любопытно. Теперь мне приходит в голову, могу ли я наследовать как FlowLayoutPanel, так и UserControl, чтобы моя новая SmoothScrollingFlowLayoutPanel стала элементом, который я могу поместить в форму во время разработки? - person ErikE; 14.08.2010
comment
@Emtucifor: Нет, ни один из языков .NET не допускает множественного наследования. Однако вы можете наследовать от FlowLayoutPanel и поместить свой элемент управления в панель инструментов. Если элемент управления находится в вашем проекте, он должен появиться там автоматически. Если он находится в другом проекте, вам нужно будет щелкнуть правой кнопкой мыши панель инструментов, нажать «Выбрать элементы...», затем перейти к этой .dll и таким образом добавить элемент управления. - person Adam Robinson; 14.08.2010
comment
Это было полезно. Я знал, что подклассы UserControls будут отображаться на панели инструментов, но почему-то упустил, что там также появятся подклассы обычных элементов управления. Спасибо. - person ErikE; 16.08.2010