Как остановить UserControl (nee ScrollableControl) от вызова ScrollWindow?
.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 не изменит С#, чтобы позволить потомкам получить доступ к закрытым членам: я не могу этого сделать.
Обновить два
я понял, что копирование вставку ScrollableControl
, исправление одной проблемы означает, что мне также придется скопировать-вставить всю цепочку наследования до UserControl
...
ScrollableControl2 : Control, IArrangedElement, IComponent, IDisposable
ContainerControl2 : ScrollableControl2, IContainerControl
UserControl2 : ContainerControl2
Я бы предпочел работать с объектно-ориентированным дизайном, а не против него.
Ответы
Ответ 1
У меня была такая же проблема, спасибо за публикацию этого. Возможно, я нашел решение вашей проблемы. Мое решение состоит в том, чтобы перегрузить 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 события прокрутки может работать.
Надеюсь, что это поможет.
Tom
Ответ 2
Вы пытались связаться с программистом из Microsoft? Я уверен, что если вы обратитесь в Microsoft, вы можете отправить свой вопрос им, возможно, даже получить поддержку по телефону.
Вот ссылка на поддержку .NET framework: нажмите здесь. В нем упоминается, что вы можете связаться с профессионалами поддержки .NET по электронной почте, телефону или в Интернете.
Ответ 3
Решение Tom это потрясающе, но я думаю, что есть возможность для небольшой оптимизации.
Без Тома два метода, когда я вызываю прокрутку, например, щелкнув конечную точку прокрутки, мой onPaint видит один вызов.
Когда я добавляю Tom методы, мой 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);
}