Синхронизированная прокрутка компонентов Delphi

Я пытаюсь синхронизировать прокрутку двух компонентов TDBGrid в приложении VCL Forms. У меня возникают трудности с перехватом WndProc каждого компонента сетки без каких-либо проблем со стеком. Я попытался отправить сообщения WM_VSCROLL в событиях прокрутки, но это все еще приводит к неправильной операции. Он должен работать для нажатия на полосу прокрутки, а также для выделения ячейки или кнопки мыши вверх или вниз. Вся идея состоит в том, чтобы две сетки располагались рядом друг с другом, отображая некое подобие соответствующего диалога.

Пытался

SendMessage( gridX.Handle, WM_VSCROLL, SB_LINEDOWN, 0 );

Также

procedure TForm1.GridXCustomWndProc( var Msg: TMessage );
begin
Msg.Result := CallWindowProc( POldWndProc, gridX.Handle, Msg.Msg, Msg.wParam, Msg.lParam );

   if ( Msg.Msg = WM_VSCROLL ) then 
   begin
      gridY.SetActiveRow( gridX.GetActiveRow );
      gridY.Perform( Msg.Msg, Msg.wParam, Msg.lParam );
      SetScrollPos( gridY.Handle, SB_VERT, HIWORD( Msg.wParam ), True );
   end;
end;

А также

procedure TForm1.GridxCustomWndProc( var Msg: TMessage );
begin
   if ( Msg.Msg = WM_VSCROLL ) then 
   begin
      gridY.SetActiveRow( gridX.GetActiveRow );
      gridY.Perform( Msg.Msg, Msg.wParam, Msg.lParam );
      SetScrollPos( gridY.Handle, SB_VERT, HIWORD( Msg.wParam ), True );
   end;
   inherited WndProc( Msg );
end;

Первое - только временное решение, второе приводит к неправильным чтениям памяти, а третье приводит к переполнению стека. Так что ни одно из этих решений, похоже, не работает для меня. Я хотел бы получить некоторую информацию о том, как выполнить эту задачу! Заранее спасибо.

ОБНОВЛЕНИЕ: Решение

  private
    [...]
    GridXWndProc, GridXSaveWndProc: Pointer;
    GridYWndProc, GridYSaveWndProc: Pointer;
    procedure GridXCustomWndProc( var Msg: TMessage );
    procedure GridYCustomWndProc( var Msg: TMessage );

procedure TForm1.FormCreate(Sender: TObject);
begin
  GridXWndProc := classes.MakeObjectInstance( GridXCustomWndProc );
  GridXSaveWndProc := Pointer( GetWindowLong( GridX.Handle, GWL_WNDPROC ) );
  SetWindowLong( GridX.Handle, GWL_WNDPROC, LongInt( GridXWndProc ) );

  GridYWndProc := classes.MakeObjectInstance( GridYCustomWndProc );
  GridYSaveWndProc := Pointer( GetWindowLong( GridY.Handle, GWL_WNDPROC ) );
  SetWindowLong( GridY.Handle, GWL_WNDPROC, LongInt( GridYWndProc ) );
end;

procedure TForm1.GridXCustomWndProc( var Msg: TMessage );
begin
   Msg.Result := CallWindowProc( GridXSaveWndProc, GridX.Handle, Msg.Msg, Msg.WParam, Msg.LParam );
   case Msg.Msg of
      WM_KEYDOWN:
      begin
         case TWMKey( Msg ).CharCode of VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
         end;
      end;
      WM_VSCROLL:
         GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_HSCROLL:
         GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_MOUSEWHEEL:
      begin
         ActiveControl := GridY;
         GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      end;
      WM_DESTROY:
      begin
         SetWindowLong( GridX.Handle, GWL_WNDPROC, Longint( GridXSaveWndProc ) );
         Classes.FreeObjectInstance( GridXWndProc );
      end;
  end;
end;

procedure TForm1.GridXMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer );
begin
   GridY.SetActiveRow( GridX.GetActiveRow );
end;

procedure TForm1.GridYCustomWndProc( var Msg: TMessage );
begin
   Msg.Result := CallWindowProc( GridYSaveWndProc, GridY.Handle, Msg.Msg, Msg.WParam, Msg.LParam );
   case Msg.Msg of
      WM_KEYDOWN:
      begin
         case TWMKey( Msg ).CharCode of VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
         end;
      end;
      WM_VSCROLL:
         GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_HSCROLL:
         GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_MOUSEWHEEL:
      begin
         ActiveControl := GridX;
         GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      end;
      WM_DESTROY:
      begin
         SetWindowLong( GridY.Handle, GWL_WNDPROC, Longint( GridYSaveWndProc ) );
         Classes.FreeObjectInstance( GridYWndProc );
      end;
   end;
end;

procedure TForm1.GridYMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer );
begin
   GridX.SetActiveRow( GridY.GetActiveRow );
end;

Спасибо - Sertac Akyuz за решение. При интеграции в приложение форм VCL с использованием сеток они будут имитировать друг друга при прокрутке и выделении выбранной записи.

5 ответов

Решение

Вы, вероятно, реализуете переопределение сообщения для обеих сеток. GridX прокручивает GridY, который, в свою очередь, прокручивает GridX, который, в свою очередь... ТАК. Вы можете защитить поверхностный код прокрутки, окружив блок флагами.

type
  TForm1 = class(TForm)
    [..] 
  private
    FNoScrollGridX, FNoScrollGridY: Boolean;
    [..]

procedure TForm1.GridXCustomWndProc( var Msg: TMessage );
begin
  Msg.Result := CallWindowProc(POldWndProc, gridX.Handle, Msg.Msg, Msg.wParam, Msg.lParam );

  if ( Msg.Msg = WM_VSCROLL ) then 
  begin
    if not FNoScrollGridX then
    begin
      FNoScrollGridX := True
      gridY.SetActiveRow( gridX.GetActiveRow );
      gridY.Perform( Msg.Msg, Msg.wParam, Msg.lParam );
//      SetScrollPos( gridY.Handle, SB_VERT, HIWORD( Msg.wParam ), True );
    end;
    FNoScrollGridX := False;
  end;
end;

Похожий код для GridY. Кстати, вам не нужно SetScrollPos.


редактировать:

TForm1 = class(TForm)
  [..]
private
  GridXWndProc, GridXSaveWndProc: Pointer;
  GridYWndProc, GridYSaveWndProc: Pointer;
  procedure GridXCustomWndProc(var Msg: TMessage);
  procedure GridYCustomWndProc(var Msg: TMessage);
  [..]

procedure TForm1.FormCreate(Sender: TObject);
begin
  [..]

  GridXWndProc := classes.MakeObjectInstance(GridXCustomWndProc);
  GridXSaveWndProc := Pointer(GetWindowLong(GridX.Handle, GWL_WNDPROC));
  SetWindowLong(GridX.Handle, GWL_WNDPROC, LongInt(GridXWndProc));

  GridYWndProc := classes.MakeObjectInstance(GridYCustomWndProc);
  GridYSaveWndProc := Pointer(GetWindowLong(GridY.Handle, GWL_WNDPROC));
  SetWindowLong(GridY.Handle, GWL_WNDPROC, LongInt(GridYWndProc));
end;

procedure TForm1.GridXCustomWndProc(var Msg: TMessage);
begin
  Msg.Result := CallWindowProc(GridXSaveWndProc, GridX.Handle,
      Msg.Msg, Msg.WParam, Msg.LParam);

  case Msg.Msg of
    WM_KEYDOWN:
      begin
        case TWMKey(Msg).CharCode of
          VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
        end;
      end;
    WM_VSCROLL: GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
    WM_MOUSEWHEEL:
      begin
        ActiveControl := GridY;
        GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
      end;
    WM_DESTROY:
      begin
        SetWindowLong(GridX.Handle, GWL_WNDPROC, Longint(GridXSaveWndProc));
        Classes.FreeObjectInstance(GridXWndProc);
      end;
  end;
end;

procedure TForm1.GridYCustomWndProc(var Msg: TMessage);
begin
  Msg.Result := CallWindowProc(GridYSaveWndProc, GridY.Handle,
      Msg.Msg, Msg.WParam, Msg.LParam);

  case Msg.Msg of
    WM_KEYDOWN:
      begin
        case TWMKey(Msg).CharCode of
          VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridX.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
        end;
      end;
    WM_VSCROLL: GridX.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
    WM_MOUSEWHEEL:
      begin
        ActiveControl := GridX;
        GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
      end;
    WM_DESTROY:
      begin
        SetWindowLong(GridY.Handle, GWL_WNDPROC, Longint(GridYSaveWndProc));
        Classes.FreeObjectInstance(GridYWndProc);
      end;
  end;
end;

Я получил частичное, но теперь полностью рабочее решение (по крайней мере, для двух TMemo)...

Я имею в виду частичное, потому что он слушает изменения только на одном TMemo, но не на другом...

Я имею в виду полноценную работу, потому что это не зависит от того, что сделано...

Это так же просто, как поместить одно и то же значение горизонтальной прокрутки в одну заметку, как и в другую...

Это не связано с сообщениями, но так как я пытался получить рабочее решение, перехватывая сообщения WM_HSCROLL и т. Д.... я оставил код, потому что он работает... я постараюсь улучшить его позже... например, только перехват WM_PAINT, или другими способами... но пока я ставлю его как есть, так как он работает... и я нигде не нашел ничего лучшего...

Вот код, который работает:

// On private section of TForm1
Memo_OldWndProc:TWndMethod; // Just to save and call original handler
procedure Memo_NewWndProc(var TheMessage:TMessage); // New handler

// On implementation section of TForm1    
procedure TForm1.FormCreate(Sender: TObject);
begin
     Memo_OldWndProc:=Memo1.WindowProc; // Save the handler
     Memo1.WindowProc:=Memo_NewWndProc; // Put the new handler, so we can do extra things
end;

procedure TForm1.Memo_NewWndProc(var TheMessage:TMessage);
begin
     Memo_OldWndProc(TheMessage); // Let the scrollbar to move to final position
     Memo2.Perform(WM_HSCROLL
                  ,SB_THUMBPOSITION+65536*GetScrollPos(Memo1.Handle,SB_HORZ)
                  ,0
                  ); // Put the horizontal scroll of Memo2 at same position as Memo1
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
     Memo1.WindowProc:=Memo_OldWndProc; // Restore the old handler
end;

Это работает для всех способов сделать прокрутку, чтобы изменить...

Заметки:

  • Я знаю, это ужасно, чтобы перехватить все сообщения, но по крайней мере работает...
  • это моя первая успешная попытка иметь два TMemos с синхронизированной горизонтальной полосой прокрутки...
  • Итак, если кто-то может немного улучшить его (не перехватывать все сообщения), пожалуйста, сделайте это и опубликуйте.
  • Это делает Memo1 только для горизонтальной синхронизации с панелью Memo2, но не Memo2 для синхронизации с Memo1
  • Нажимайте клавиши вверх, вниз, влево, вправо, колесико мыши и т. Д.... все, что вы хотите, но на Memo2, чтобы увидеть его в действии

Я постараюсь улучшить это: когда вы делаете что-то на Memo2, прокрутка Memo1 все еще будет синхронизирована...

Я думаю, что он может работать практически для любого элемента управления, который имеет ScrollBar, не только TMemo...

Как я уже сказал...

Здесь это лучшее решение (не окончательное) с точки зрения эффективности, чистого кода и двунаправленности... изменение одного влияет на другое...

Пожалуйста, прочитайте комментарии к коду, чтобы понять, что делает каждое предложение... это довольно сложно... но основная идея такая же, как была раньше... установить другую горизонтальную полосу прокрутки TMemo, как это есть на TMemo, где пользователь действует... независимо от того, что пользователь делает, перемещайте мышь и выбирайте текст, нажимайте клавиши влево, вправо, домой, конец, используйте горизонтальное колесо мыши (не у всех), перетаскивайте полосу прокрутки, нажимайте на любую часть горизонтали полоса прокрутки и т.д...

Основная идея заключается в том, что... объект необходимо перекрасить, поэтому поместите горизонтальную полосу прокрутки другого объекта, идентичную этой...

Эта первая часть просто для добавления вещей в класс TMemo, это просто создание нового производного класса, но с тем же именем класса, но только для модуля внутри объявленного.

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

type
    TMemo=class(StdCtrls.TMemo) // Just to add things to TMemo class only for this unit
    private
       BusyUpdating:Boolean; // To avoid circular stack overflow
       SyncMemo:TMemo; // To remember the TMemo to be sync
       Old_WindowProc:TWndMethod; // To remember old handler
       procedure New_WindowProc(var Mensaje:TMessage); // The new handler
    public
       constructor Create(AOwner:TComponent);override; // The new constructor
       destructor Destroy;override; // The new destructor
    end;

Следующая часть является реализацией предыдущих объявлений этого нового класса TMemo.

Добавьте это в раздел реализации везде, где вы предпочитаете:

constructor TMemo.Create(AOwner:TComponent); // The new constructor
begin
     inherited Create(AOwner); // Call real constructor
     BusyUpdating:=False; // Initialize as not being in use, to let enter
     Old_WindowProc:=WindowProc; // Remember old handler
     WindowProc:=New_WindowProc; // Replace handler with new one
end;

destructor TMemo.Destroy; // The new destructor
begin
     WindowProc:=Old_WindowProc; // Restore the original handler
     inherited Destroy; // Call the real destructor
end;

procedure TMemo.New_WindowProc(var Mensaje:TMessage);
begin
     Old_WindowProc(Mensaje); // Call the real handle before doing anything
     if  BusyUpdating // To avoid circular stack overflow
       or
         (not Assigned(SyncMemo)) // If not yet set (see TForm1.FormCreate bwlow)
       or
         (WM_PAINT<>Mensaje.Msg) // If not when need to be repainted to improve speed
     then Exit; // Do no more and exit the procedure
     BusyUpdating:=True; // Set that object is busy in our special action
     SyncMemo.Perform(WM_HSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_HORZ),0); // Send to the other TMemo a message to set its horizontal scroll as it is on this TMemo
     BusyUpdating:=False; // Set that the object is no more busy in our special action
end;

Теперь последняя часть, расскажите каждому TMemo, какие другие Memo должны быть синхронизированы.

В разделе реализации для события Form1 Create добавьте что-то вроде этого:

procedure TForm1.FormCreate(Sender: TObject);
begin
     Memo1.SyncMemo:=Memo2; // Tell Memo1 what TMemo must sync (Memo2)
     Memo2.SyncMemo:=Memo1; // Tell Memo2 what TMemo must sync (Memo1)
end;

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

Теперь немного настроек для обоих TMemo jsut, чтобы это работало идеально:

  • Пусть обе полосы прокрутки TMemo будут видны
  • Пусть WordWrap false на обоих Tmemo
  • Положите много текста (то же самое для обоих), длинные строки и много строк

Запустите его и посмотрите, как обе горизонтальные полосы прокрутки всегда синхронизированы...

  • Если вы переместите одну горизонтальную полосу прокрутки, другая горизонтальная полоса прокрутки переместится...
  • Если вы перейдете к тексту вправо или влево, к началу или концу строки и т. Д., Независимо от того, где находится SelStart, с другой... горизонтальная прокрутка текста синхронизирована.

Проблема, почему это не окончательная версия заключается в том, что:

  • Полосы прокрутки (горизонтальная в моем случае) не могут быть скрыты... поскольку, если одна из них скрыта, при вызове GetScrollPos она возвращает ноль, поэтому она не синхронизируется.

Если кто-то знает, как эмулировать скрытый или заставить GetScrollPos не возвращать ноль, пожалуйста, прокомментируйте, это единственное, что мне нужно исправить в финальной версии.

Заметки:

  • Очевидно, то же самое можно сделать с вертикальной полосой прокрутки... просто измените WM_HSCROLL на WM_VSCROLL и SB_HORZ на SB_VERT
  • Очевидно, что то же самое может быть сделано для обоих одновременно... просто скопируйте строку SyncMemo.Perform дважды и на одной пусть WM_HSCROLL и SB_HORZ, а на другой пусть WM_VSCROLL и SB_VERT

Вот пример процедуры New_WindowProc для синхронизации обеих полос прокрутки одновременно, может быть, для ленивых людей, может быть, для людей, подобных копированию и вставке:

procedure TMemo.New_WindowProc(var Mensaje:TMessage);
begin
     Old_WindowProc(Mensaje); // Call the real handle before doing anything
     if  BusyUpdating // To avoid circular stack overflow
       or
         (not Assigned(SyncMemo)) // If not yet set (see TForm1.FormCreate bwlow)
       or
         (WM_PAINT<>Mensaje.Msg) // If not when need to be repainted to improve speed
     then Exit; // Do no more and exit the procedure
     BusyUpdating:=True; // Set that object is busy in our special action
     SyncMemo.Perform(WM_HSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_HORZ),0); // Send to the other TMemo a message to set its horizontal scroll as it is on this TMemo
     SyncMemo.Perform(WM_VSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_VERT),0); // Send to the other TMemo a message to set its vertical scroll as it is on this TMemo
     BusyUpdating:=False; // Set that the object is no more busy in our special action
end;

Надеюсь, что кто-то может решить проблему скрытой полосы прокрутки, а GetScrollPos возвращает ноль!!!

Я нашел решение... я знаю, что это довольно сложно... но, по крайней мере, оно полностью функционально...

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

Сложная часть:

  • Поместите TPanel там, где находится TMemo, и поместите TMemo в TPanel
  • Скройте границы TPanel, установите BorderWith как 0, а все Bevel - в bvNone/bkNone.
  • Настройте TMemo Align для alTop, а не для alClient и т. Д.
  • Обработайте TPanel.OnResize, чтобы сделать TMemo.Height больше, чем TPanel.Height и высоту горизонтальной полосы прокрутки (на данный момент я использую постоянное значение в 20 пикселей, но я хотел бы знать, как получить реальное значение)

Вот и все... сделано!!! Горизонтальная полоса прокрутки находится вне видимой области... вы можете поместить туда, где вы хотите, панель TPanel, придать ей нужный размер... эта горизонтальная полоса прокрутки не будет видна пользователю и не будет скрыта, поэтому GetScrollPos будет работать правильно... хитрый, я знаю, но полностью функциональный.

Вот полный код для архивации:

В разделе интерфейса перед объявлением TForm ваша TForm увидит этот новый класс TMemo вместо обычного:

type
    TMemo=class(StdCtrls.TMemo) // Just to add things to TMemo class only for this unit
    private
       BusyUpdating:Boolean; // To avoid circular stack overflow
       SyncMemo:TMemo; // To remember the TMemo to be sync
       Old_WindowProc:TWndMethod; // To remember old handler
       procedure New_WindowProc(var Mensaje:TMessage); // The new handler
    public
       constructor Create(AOwner:TComponent);override; // The new constructor
       destructor Destroy;override; // The new destructor
    end;

В разделе реализации везде, где вы предпочитаете:

constructor TMemo.Create(AOwner:TComponent); // The new constructor
begin
     inherited Create(AOwner); // Call real constructor
     BusyUpdating:=False; // Initialize as not being in use, to let enter
     Old_WindowProc:=WindowProc; // Remember old handler
     WindowProc:=New_WindowProc; // Replace handler with new one
end;

destructor TMemo.Destroy; // The new destructor
begin
     WindowProc:=Old_WindowProc; // Restore the original handler
     inherited Destroy; // Call the real destructor
end;

procedure TMemo.New_WindowProc(var Mensaje:TMessage);
begin
     Old_WindowProc(Mensaje); // Call the real handle before doing anything
     if  (WM_PAINT<>Mensaje.Msg) // If not when need to be repainted to improve speed
       or
         BusyUpdating // To avoid circular stack overflow
       or
         (not Assigned(SyncMemo)) // If not yet set (see TForm1.FormCreate bwlow)
     then Exit; // Do no more and exit the procedure
     BusyUpdating:=True; // Set that object is busy in our special action
     SyncMemo.Perform(WM_HSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_HORZ),0); // Send to the other TMemo a message to set its horizontal scroll as it is on this TMemo
     BusyUpdating:=False; // Set that the object is no more busy in our special action
end;

Также в разделе реализации везде, где вы предпочитаете:

procedure TForm1.FormCreate(Sender: TObject);
begin
     Memo1.SyncMemo:=Memo2; // Tell Memo1 what TMemo must sync (Memo2)
     Memo2.SyncMemo:=Memo1; // Tell Memo2 what TMemo must sync (Memo1)
end;

procedure TForm1.pnlMemo2Resize(Sender: TObject);
begin
     Memo2.Height:=pnlMemo2.Height+20; // Make height enough big to cause horizontal scroll bar be out of TPanel visible area, so it will not be seen by the user
end;

Вот и все, ребята! Я знаю, что это довольно сложно, но полностью функционально.

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

Надеюсь, что когда-нибудь я узнаю, как заменить такие 20 на реальную (рассчитанную или прочитанную) высоту горизонтальной полосы прокрутки TMemo.

Спасибо GetSystemMetrics а также SM_CYHSCROLL, но это не просто достаточно... просто нужно еще 3 пикселя...

Так что я просто использую: GetSystemMetrics(SM_CYHSCROLL)+3

Примечание. Два таких пикселя могут быть из-за наличия родительской панели с BevelWidth со значением 1 но у меня есть BevelInner а также BevelOuter со значением bvNone так не может; но лишний пиксель я не знаю почему.

Большое спасибо.

Если вы предпочитаете, просто присоединяйтесь к ним в одном большом посте, но я думаю, что лучше не смешивать их.

В ответ на "Sertac Akyuz" (извините, что делаю это здесь, но я не знаю, как разместить их рядом с вашим вопросом):

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

Важно: я обнаружил, что идеальное решение не может быть достигнуто путем захвата сообщения, потому что есть случай, который вызывает прокрутку, но не сообщение WM_VSCROLL, WM_HSCROLL (только WM_PAINT)... это связано с выделением текста с помощью мыши... позвольте мне объяснить, как я вижу его в действии... Просто начните с конца последней визуальной строки и переместите мышь немного вниз, затем остановите движение мыши и дайте нажата кнопка мыши... без каких-либо действий (мышь не двигается, нет нажатия клавиш, нет нажатия клавиш, нет изменения кнопки мыши и т. д.). TMemo прокручивается вниз до конца текста... то же самое происходит для горизонтального прокручивает, когда мышь находится около правого конца визуальной линии и перемещается вправо... также в противоположных направлениях... такие прокрутки не проходят через сообщения WM_VSCROLLWM_HSCROLL, только WM_PAINT (по крайней мере, на моем компьютере)... то же самое происходит и на сетках.

Другие вопросы по тегам