Синхронизированная прокрутка компонентов Delphi - PullRequest
5 голосов
/ 21 июля 2010

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

Tried

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 ]

3 голосов
/ 05 ноября 2012

Я получил частичное, но теперь полностью рабочее решение (хотя бы для двух 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 ...

3 голосов
/ 21 июля 2010

Возможно, вы реализуете переопределение сообщения для обеих сеток. 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;
2 голосов
/ 08 ноября 2012

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

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

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

  • Поместите панель 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.

2 голосов
/ 06 ноября 2012

Как я уже говорил ...

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

Пожалуйста, прочитайте комментарии к коду, чтобы понять, что делает каждое предложение ... это довольно сложно ... но основная идея такая же, как была раньше ... установить другую горизонтальную полосу прокрутки 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, что это за другая памятка, которая должна быть синхронизирована.

В разделе вашей реализации для события 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 для синхронизации обеих полос прокрутки одновременно, может быть, для ленивых людей, может быть для людей, таких как copy & paste:

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 вернет ноль !!!

1 голос
/ 21 ноября 2012

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

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

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

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

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

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

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

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

...