Как остановить UserControl (урожденный ScrollableControl) от вызова ScrollWindow? - PullRequest
13 голосов
/ 25 апреля 2011

.NET UserControl (от ScrollableControl) имеет возможность отображать горизонтальную и вертикальную полосы прокрутки.

Вызывающий может установить видимость и диапазон этих горизонтальных и вертикальных полос прокрутки:

UserControl.AutoScroll = true;
UserControl.AutoScrollMinSize = new Size(1000, 4000); //1000x4000 scroll area

Примечание: UserControl (т.е. ScrollableControl) использует стандартный механизм Windows, задающий стили окон WS_HSCROLL и WS_VSCROLL для отображения полос прокрутки. То есть: они не создают отдельные элементы управления прокруткой Windows или .NET, размещая их справа / снизу окна. В Windows имеется стандартный механизм отображения одной или обеих полос прокрутки.

Если пользователь прокручивает элемент управления, UserControl отправляется сообщение WM_HSCROLL или WM_VSCROLL. В ответ на эти сообщения я хочу, чтобы ScrollableControl сделал недействительной клиентскую область, что происходит в родном Win32:

switch (uMsg) 
{ 
   case WM_VSCROLL:
       ...
       GetScrollInfo(...);
       ...
       SetScrollInfo(...);
       ...

       InvalidateRect(g_hWnd, 
              null, //erase entire client area
              true, //background needs erasing too (trigger WM_ERASEBKGND));
       break;
 }

Мне нужно, чтобы вся клиентская область была аннулирована. Проблема в том, что UserControl (т.е. ScrollableControl) вызывает ScrollWindow API-функцию:

protected void SetDisplayRectLocation(int x, int y)
{
    ...
    if ((nXAmount != 0) || ((nYAmount != 0) && base.IsHandleCreated))
    {
        ...
        SafeNativeMethods.ScrollWindowEx(new HandleRef(this, base.Handle), nXAmount, nYAmount, null, ref rectClip, NativeMethods.NullHandleRef, ref prcUpdate, 7);
    }
    ...
}

Вместо того, чтобы вызывать InvalidateRect для всего клиентского прямоугольника, ScrollableControl пытается " сохранить " существующего содержимого в клиентской области. Например, пользователь прокручивает вверх , текущий клиентский контент толкается вниз на ScrollWindowEx, а затем только недопущенная область становится недействительной, вызывая WM_PAINT:

enter image description here

На приведенной выше диаграмме область шахматной доски - это содержимое, которое недопустимо и должно быть окрашено во время следующего WM_PAINT.

В моем случае это не хорошо; верхняя часть моего элемента управления содержит «заголовок» (например, заголовки столбцов списка). Прокрутка этого контента дальше вниз неверна:

enter image description here

и это вызывает визуальное искажение.

Я хочу, чтобы ScrollableControl не использовал ScrollWindowEx, а вместо этого просто сделал недействительной всю клиентскую область.

я попытался переопределить OnScroll защищенный метод:

protected override void OnScroll(ScrollEventArgs se)
{
   base.OnScroll(se);

   this.Invalidate();
}

Но это вызывает двойную ничью.

Примечание: я мог бы использовать двойную буферизацию для маскировки проблемы, но это не реальное решение

  • двойная буферизация не должна использоваться во время сеанса удаленного рабочего стола / терминала
  • это бесполезно расходует ресурсы процессора
  • это не тот вопрос, который я задаю

Я решил использовать Control вместо UserControl (то есть до ScrollableControl в цепочке наследования) и вручную добавить элемент управления HScroll или VScroll .NET - но это также нежелательно:

  • Windows уже обеспечивает стандартный вид для положения полос прокрутки (дублировать нетривиально)
  • это много функций, которые нужно воспроизводить с нуля, когда я хочу только InvalidateRect вместо ScrollWindowEx

Поскольку я могу видеть и публиковать код, внутренний для ScrollableControl, я знаю, что нет свойства отключить использование ScrollWindow, но есть свойство для отключения использования ScrollWindow?


Обновление:

Я попытался переопределить метод-нарушитель и использовать отражатель для кражи всего кода:

protected override void SetDisplayRectLocation(int x, int y)
{
    ...
    Rectangle displayRect = this.displayRect;
    ...
    this.displayRect.X = x;
    this.displayRect.Y = y;
    if ((nXAmount != 0) || ((nYAmount != 0) && base.IsHandleCreated))
    {
        ...
        SafeNativeMethods.ScrollWindowEx(new HandleRef(this, base.Handle), nXAmount, nYAmount, null, ref rectClip, NativeMethods.NullHandleRef, ref prcUpdate, 7);
    }
    ...
}

Проблема в том, что SetDisplayRectLocation читает и записывает в закрытую переменную-член (displayRect). Если Microsoft не изменит C #, чтобы предоставить потомкам доступ к закрытым членам: я не могу этого сделать.


Обновление Два

я понял, что копирование-вставка реализации ScrollableControl, исправление одной проблемы означает, что мне также придется копировать-вставлять всю цепочку наследования до UserControl

...
   ScrollableControl2 : Control, IArrangedElement, IComponent, IDisposable
      ContainerControl2 : ScrollableControl2, IContainerControl
         UserControl2 : ContainerControl2

Я бы действительно предпочел работать с объектно-ориентированным дизайном, а не против него.

Ответы [ 3 ]

7 голосов
/ 06 июля 2013

У меня была такая же проблема, спасибо за публикацию этого.Возможно, я нашел решение вашей проблемы.Мое решение состоит в том, чтобы перегрузить WndProc для обработки сообщений прокрутки, отключить перерисовку при вызове обработчика базового класса, а затем принудительно перерисовать все окно после обработки сообщения.Это решение работает нормально:

    private void sendRedrawMessage( bool redrawFlag )
    {
        const int WM_SETREDRAW = 0x000B;

        IntPtr wparam = new IntPtr( redrawFlag ? 1 : 0 );
        Message msg = Message.Create( Handle, WM_SETREDRAW, wparam, IntPtr.Zero );
        NativeWindow.FromHandle( Handle ).DefWndProc( ref msg );
    }

    protected override void WndProc( ref Message m )
    {
        switch ( m.Msg )
        {
            case 276: // WM_HSCROLL
            case 277: // WM_VSCROLL
                sendRedrawMessage( false );
                base.WndProc( ref m );
                sendRedrawMessage( true );
                Refresh(); // Invalidate all
                return;
        }

        base.WndProc( ref m );
    }

Я думал о том, чтобы попробовать это из-за предложения перегрузить WndProc в сочетании с вашим наблюдением о том, что вы не можете перегрузить SetDisplayRectLocation.Я подумал, что отключение WM_PAINT во время обработки события прокрутки в UserControl может сработать.

Надеюсь, это поможет.

Том

1 голос
/ 03 марта 2012

Вы пытались связаться с программистом из Microsoft? Я уверен, что если вы обратитесь в Microsoft, вы можете задать им свой вопрос, возможно, даже получить поддержку по телефону.

Вот ссылка для поддержки .NET Framework: нажмите здесь . В нем упоминается, что вы можете связаться со специалистами службы поддержки .NET по электронной почте, телефону или через Интернет.

0 голосов
/ 02 августа 2015

Решение Тома великолепно, но я думаю, что есть возможность для небольшой оптимизации.Без двух методов Тома, когда я вызываю прокрутку, например, щелкая конечную точку полосы прокрутки, мой onPaint видит один вызов.Когда я добавляю два метода Тома, мой onPaint начинает получать два вызова для тех же позиций полосы прокрутки.Казалось, что для меня решение состоит в том, чтобы проигнорировать заключительный SB_ENDSCROLL, который происходит, это операции прокрутки.После этого я перестал видеть дублирующие краски в том же месте прокрутки.

private void sendRedrawMessage(bool redrawFlag)
{
    const int WM_SETREDRAW = 0x000B;

    IntPtr wparam = new IntPtr(redrawFlag ? 1 : 0);
    Message msg = Message.Create(Handle, WM_SETREDRAW, wparam, IntPtr.Zero);
    NativeWindow.FromHandle(Handle).DefWndProc(ref msg);
}

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case 276: // WM_HSCROLL
        case 277: // WM_VSCROLL
            if ((ushort)m.WParam == 8) // SB_ENDSCROLL ignore scroll bar release
                break;
            sendRedrawMessage(false);
            base.WndProc(ref m);
            sendRedrawMessage(true);
            Refresh(); // Invalidate all
            return;
    }

    base.WndProc(ref m);
}
...