How to Sync the Scrolling of Two Multiline Textboxes

How can I sync the scrolling of two multiline textboxes?

Yes, you'll have to create a custom text box so you can detect it scrolling. The trick is to pass the scroll message to the other text box so it will scroll in sync. This really only works well when that other text box is about the same size and has the same number of lines.

Add a new class to your project and paste the code shown below. Compile. Drop two of the new controls from the top of the toolbox onto your form. Set the Buddy property to the other control on both. Run, type some text in both of them and watch them scroll in sync as you drag the scrollbar.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class SyncTextBox : TextBox {
public SyncTextBox() {
this.Multiline = true;
this.ScrollBars = ScrollBars.Vertical;
}
public Control Buddy { get; set; }

private static bool scrolling; // In case buddy tries to scroll us
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
// Trap WM_VSCROLL message and pass to buddy
if (m.Msg == 0x115 && !scrolling && Buddy != null && Buddy.IsHandleCreated) {
scrolling = true;
SendMessage(Buddy.Handle, m.Msg, m.WParam, m.LParam);
scrolling = false;
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}

Synchronizing Multiline Textbox Positions in C#

I subclassed a RichTextBox and listened for the WM_VSCROLL message to do what you're trying to do. Perhaps you can do that instead of using a TextBox.

RESPONSE TO YOUR EDIT:

Note: I'm assuming you made a minor error in the copy and paste in your Application form and that textBoxSyncBusTraffic == textBoxSync1

The problem is in your declaration of your control's VerticalScroll event, in this line:

this.textBoxSyncBusTraffic.VerticalScroll += new System.EventHandler(this.textBoxSyncBusTraffic_VerticalScroll); 
  1. Your custom controls need to subscribe to your controls' TextBoxSynchronizedScroll.vScrollEventHandler events (not to System.EventHandler).
  2. The method referenced in your event handler doesn't exist (at least not in the code you posted).

So change this:

this.textBoxSyncBusTraffic.VerticalScroll += new System.EventHandler(this.textBoxSyncBusTraffic_VerticalScroll); 

To this:

this.textBoxSync1.VerticalScroll += new TextBoxSynchronizedScroll.vScrollEventHandler(textBoxSync1_VerticalScroll);

This uses the correct event handler and references the method you need and already have.

Also, make sure that the declaration for textBoxSync2's VerticalScroll event looks like this:

this.textBoxSync2.VerticalScroll += new TextBoxSynchronizedScroll.vScrollEventHandler(textBoxSync2_VerticalScroll);

Incidentally, there are a couple techniques you can use to make it easier to declare events:

The first is to use the form designer. If you open the Events window in the Properties window of an instance of your extended control in the forms designer, you'll see an event called VerticalScroll. Double click this item to have Visual Studio declare the event and create a method to call when the event fires.

There's also a shortcut you can use when you set up your event in code. You'll find that after you type the following code:

youtextBoxSync1.VerticalScroll +=

You'll be prompted to press Tab to finish the declaration. If you do this Visual Studio will create a method with the correct signature.

Synchronize Scroll Position of two RichTextBoxes?

I did this for a small project a while ago, and here's the simplist solution I found.

Create a new control by subclassing RichTextBox:

   public class SynchronizedScrollRichTextBox : System.Windows.Forms.RichTextBox
{
public event vScrollEventHandler vScroll;
public delegate void vScrollEventHandler(System.Windows.Forms.Message message);

public const int WM_VSCROLL = 0x115;

protected override void WndProc(ref System.Windows.Forms.Message msg) {
if (msg.Msg == WM_VSCROLL) {
if (vScroll != null) {
vScroll(msg);
}
}
base.WndProc(ref msg);
}

public void PubWndProc(ref System.Windows.Forms.Message msg) {
base.WndProc(ref msg);
}
}

Add the new control to your form and for each control explicitly notify the other instances of the control that its vScroll position has changed. Somthing like this:

private void scrollSyncTxtBox1_vScroll(Message msg) {
msg.HWnd = scrollSyncTxtBox2.Handle;
scrollSyncTxtBox2.PubWndProc(ref msg);
}

I think this code has problems if all the 'linked' controls don't have the same number of displayable lines.

Can I show the upper part of a line that doesn't fit whole in a multiline textbox?

I don't think you can do this out-of-the-box. But, there are 2 ways that I can think of by which you can achieve your goal

  1. Do you really need a TextBox control. Can a Label work for you. If yes, then Label does not have the problem you describe above. If not, then you can use a nifty trick to always display your contents in a Label and switch it to a TextBox when a user starts typing.
  2. Another way is to disable scrolling in the TextBox. Adjust the height of the TextBox to 3 clearly visible lines. Now drop this TextBox into a Panel. Make sure the Panel uses Panel.AutoScroll = true (you can use a separate VerticalScrollbar or HorizontalScrollbar or both if you need more control).
    Now adjust the Panel.Height so that only 2 full lines are visible and the 3rd line is partially visible.

You mentioned that your TextBox

can contain a variable number of lines

But you also mentioned

I want the textboxes to fit two lines plus tax in them so that only the upper part of the third line is displayed when a third line is present

So not sure what is the case. If you need to dynamically adjust the height of your TextBox, then look at this post to Autoresize textbox control vertically

Get two VBA textboxes to scroll together

After much digging and experimenting, I figured it out! By adding a ScrollBar, I was able to use the ScrollBar_Change() event to adjust the text boxes. On my form, I now have two TextBoxes and a ScrollBar object. Then I have a few necessary subs in my Userform code:

'This constant affects whether the ScrollBar appears or _
not, as well as some of the movement graphics of the _
ScrollBar.
'This MUST be reset if the TextBoxes are resized
'I made it a UserForm-level Const because I use it _
elsewhere, but it could also live in SetUpScrollBar
Private Const TEXTBOX_MAX_LINES_IN_VIEW as Long = 21

Private Sub SetUpScrollBar()
'I call this whenever I show my Userform (happens after a _
separate macro determines what to put in the TextBoxes). _
It determines whether the ScrollBar should be shown, and _
if so, sets the .Max property so it scrolls in accordance _
to the number of lines in the TextBoxes.

Dim linesInTextBox as Long

With Me.TextBox1
.SetFocus
linesInTextBox = .LineCount - 1
'Need to subtract 1 or you'll get an error _
when dragging the scroll bar all the way down.
End With

'If there are fewer lines than the max viewing area, hide the scroll bar.
Select Case linesInTextBox > TEXTBOX_MAX_LINES_IN_VIEW
Case is = True
ShowScrollBar True
With Me.ScrollBox
.Min = 0 'I believe this is the default, but I set it just in case
.Max = maxLinesInTextBox
.Value = 0
End With
Case is = False
ShowScrollBar False
End Select
End Sub

Private Sub ShowScrollBar(show As Boolean)
'A simple way of showing or hiding the scrollbar
With Me.ScrollBar1
.Enabled = show
.Visible = show
End With
End Sub

Private Sub ScrollBar1_Change()
'When the scrollbar position changes (either by dragging _
the slider or by clicking it), set the CurLine property _
of each TextBox to the current ScrollBar value.

With Me.TextBox1
'Need to set focus to the box to get or adjust the CurLine property
.SetFocus
.CurLine = Me.ScrollBar1.value
End With

With Me.TextBox2
'Need to set focus to the box to get or adjust the CurLine property
.SetFocus
.CurLine = Me.ScrollBar1.value
End With
End Sub

This seems to work quite well for my purposes. It allows me to keep the text-selecting/copying benefits of using TextBoxes while keeping my data synced together.

Some issues I've yet to solve:

  • Scrolling works fine, but if you try to click the arrows (particularly to go in the opposite direction that you just scrolled), you have to click until your cursor gets to the top of the TextBoxes. For me, this is 21 clicks. A bit annoying, but I'm sure there's a workaround.
  • Scrolling is not live like with a normal scrollbar. This means you can drag the scrollbar, but it won't update the TextBoxes until you let go.
  • If a user clicks into a TextBox and starts to navigate with their arrow keys, the two boxes will become out of sync. They'll resync the next time the user clicks the ScrollBar. This is very problematic if the user tries to select more lines than are visible in the window: one TextBox will scroll as they drag their selection but the other TextBox stays in place

Synchronizing Two Rich Text Box Scroll bars in WPF

You can use the ScrollViewer.ScrollChanged routed event to listen for scrolling changes. Example:

<UniformGrid Rows="1" Width="300" Height="150" >
<RichTextBox x:Name="_rich1"
VerticalScrollBarVisibility="Auto"
ScrollViewer.ScrollChanged="RichTextBox_ScrollChanged" />
<RichTextBox x:Name="_rich2"
VerticalScrollBarVisibility="Auto"
ScrollViewer.ScrollChanged="RichTextBox_ScrollChanged" />
</UniformGrid>

Then, in the event handler you do the actual synchronizing (code stolen from inspired by this other answer):

private void RichTextBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var textToSync = (sender == _rich1) ? _rich2 : _rich1;

textToSync.ScrollToVerticalOffset(e.VerticalOffset);
textToSync.ScrollToHorizontalOffset(e.HorizontalOffset);
}


Related Topics



Leave a reply



Submit