views:

275

answers:

3

I have a C# application wherein there are two multiline textboxes side-by-side, each in one side of a split-container. I would like to synchronize their vertical scroll, so that when the user scrolls up or down one of the textboxes, the other textbox scrolls respectively in the same direction. Is there a way to do this? Thanks.

ADDITIONAL INFORMATION - 7/26/10

I found some interesting APIs on the MSDN website:

TextBox.GetFirstVisibleLineIndex Method
TextBox.GetLastVisibleLineIndex Method
TextBox.ScrollToLine Method

The documentation there looks promising, but my compiler (Microsoft Visual C# 2008 Express Edition) complains when I try to use it, even after adding the PresenationFramework as a Reference and inserting using System.Windows.Controls; at the top of the file:

Error 1 'System.Windows.Forms.TextBox' does not contain a definition for 'GetFirstVisibleLineIndex' and no extension method 'GetFirstVisibleLineIndex' accepting a first argument of type 'System.Windows.Forms.TextBox' could be found (are you missing a using directive or an assembly reference?)

ADDITIONAL INFORMATION - 7/27/10

I'm working on implementing Jay's suggestion of implementing a new control, but I'm having trouble tying the eventhandler into the control. Here's is what I have so far:

public partial class MyFormApplication : Form
{
  public MyFormApplication() // MyFormApplication constructor
  {
     this.InitializeComponent();

     this.textBox1.Dispose(); // Replacing with textBoxSync1
     this.textBox2.Dispose(); // Replacing with textBoxSync2

     // Draw textBoxSync1
     this.textBoxSync1.AcceptsReturn = true;
     this.textBoxSync1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
        | System.Windows.Forms.AnchorStyles.Left)
        | System.Windows.Forms.AnchorStyles.Right)));
     this.textBoxSync1.BackColor = System.Drawing.SystemColors.Control;
     this.textBoxSync1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
     this.textBoxSync1.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
     this.textBoxSync1.Location = new System.Drawing.Point(0, 19);
     this.textBoxSync1.Multiline = true;
     this.textBoxSync1.Name = "textBoxSync1";
     this.textBoxSync1.ReadOnly = true;
     this.textBoxSync1.ScrollBars = System.Windows.Forms.ScrollBars.Both;
     this.textBoxSync1.Size = new System.Drawing.Size(338, 231);
     this.textBoxSync1.TabIndex = 0;
     this.textBoxSync1.TabStop = false;
     this.textBoxSync1.WordWrap = false;
     this.splitContainer1.Panel1.Controls.Remove(this.textBox1);
     this.splitContainer1.Panel1.Controls.Add(this.textBoxSync1);

     // Draw textBoxSync2
     this.textBoxSync2.AcceptsReturn = true;
     this.textBoxSync2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
        | System.Windows.Forms.AnchorStyles.Left)
        | System.Windows.Forms.AnchorStyles.Right)));
     this.textBoxSync2.BackColor = System.Drawing.SystemColors.Control;
     this.textBoxSync2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
     this.textBoxSync2.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
     this.textBoxSync2.Location = new System.Drawing.Point(0, 19);
     this.textBoxSync2.Multiline = true;
     this.textBoxSync2.Name = "textBoxSync2";
     this.textBoxSync2.ReadOnly = true;
     this.textBoxSync2.ScrollBars = System.Windows.Forms.ScrollBars.Both;
     this.textBoxSync2.Size = new System.Drawing.Size(113, 231);
     this.textBoxSync2.TabIndex = 30;
     this.textBoxSync2.TabStop = false;
     this.textBoxSync2.WordWrap = false;
     this.splitContainer1.Panel2.Controls.Remove(this.textBox2);
     this.splitContainer1.Panel2.Controls.Add(this.textBoxSync2);

     /* Goes on to perform other initializations... */

  }

  private void textBoxSync1_VerticalScroll(Message msg)
  {
     msg.HWnd = this.textBoxSync2.Handle;
     this.textBoxSync2.PubWndProc(ref msg);
  }

  private void textBoxSync2_VerticalScroll(Message msg)
  {
     msg.HWnd = this.textBoxSync1.Handle;
     this.textBoxSync1.PubWndProc(ref msg);
  }
}

public class TextBoxSynchronizedScroll : System.Windows.Forms.TextBox
{
  public event vScrollEventHandler VerticalScroll;
  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 (VerticalScroll != null)
        {
           VerticalScroll(msg);
        }
     }

     base.WndProc(ref msg);
  }

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

I should think that something like...

this.textBoxSync1.VerticalScroll += new System.EventHandler(this.textBoxSync1_VerticalScroll);

...would be needed to hook the vertical scroll event into the control, but as you can probably see, this does not work. Any suggestions would be appreciated. Thanks.

A: 

Have you tried intercepting all the scroll-related Windows messages coming to one textbox and just re-sending them to the other textbox?

mquander
How would this be done?
Jim Fell
+2  A: 

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.

Jay Riggs
Okay, I think I've got everything setup, but I'm having trouble adding the new event to the control. How do I make the `Message` that gets passed to the new `VerticalScroll` event handler? ` this.textBoxSync1.VerticalScroll += new System.EventHandler(/* Message??? */);` Thanks.
Jim Fell
@Jim Fell - updated!
Jay Riggs
Thanks, Jay. Your assumption was correct; I fixed the typo in my post. I made the changes you suggested using the form designer. I think it would work, except now everywhere `TextBoxSynchronizedScroll` is referenced in Application.Designer.cs, it is referencing it as `Application.TextBoxSynchronizedScroll`, which generates an error. If I manually go through it and delete all the `Application.` prefixes, the errors go away, it compiles, and runs, but as soon as I do anything in the desinger, it reinserts the errors.
Jim Fell
@Jim Fell - It's possible you're getting your errors because your form's class name is Application, which may be conflicting with the Application class in System.Windows.Forms. You might want to rename your form's class. If you do, be sure to back up your Visual Studio solution first.
Jay Riggs
@Jay Riggs: I see. Actually, my form class name is different than the scrubbed version I posted. My apologies for the confusion. I've renamed the class in my posted example.
Jim Fell
@Jay Riggs: I deleted the new controls from the form designer, so that it built with zero errors. Then, I simply dragged a new `TextBoxSynchronizedScroll` onto the form and built the project. It generated a single error: `Error 1 The type name 'TextBoxSynchronizedScroll' does not exist in the type 'MyFormApplication.MyFormApplication' C:\Documents and Settings\user\My Documents\MyFormApplication\MyFormApplication\MyFormApplication.Designer.cs 71 64 MyFormApplication`. The line it references is `this.textBoxSynchronizedScroll1 = new Download_Tool.TextBoxSynchronizedScroll();`
Jim Fell
@Jay Riggs [continued]: This does not make a great deal of sense to me because 1) it is code generated by the VC# compiler located in the `InitializeComponent()` function of MyFormApplication.Designer.cs, and 2) the prototype (with no errors) is `private TextBoxSynchronizedScroll textBoxSynchronizedScroll1;`, located in the `partial class MyFormApplication` of MyFormApplication.Designer.cs.
Jim Fell
@Jim Fell - Without looking at your VS solution it's hard to say what's going on exactly. My guess is that there's an issue with namespace/class naming. I'm not sure where Download_Tool fits in - any chance your project compiles if you remove that reference? I'm guessing your project has a namespace and class both named MyFormApplication (or Download_Tool).
Jay Riggs
@Jay Riggs: Yes, by default, that is how VS initially setup the project; the namespace and application class have the same name. For the time-being, I've implemented the control outside the form designer as you described, and it works great! However, I would like to get it working using the form designer, if possible. Do I need to rename the namespace or application class? If so, are there any project setting that should be adjusted, or can I just go through the files and rename things accordingly? Thanks again for all your help!
Jim Fell
Jim Fell
@Jim Fell - Great!
Jay Riggs
+1  A: 

Based of the existing code I came up with the following. Seems to work for me.

class TextBoxSynchronizedScroll : TextBox
{
    public const int WM_VSCROLL = 0x115;

    List<TextBoxSynchronizedScroll> peers = new List<TextBoxSynchronizedScroll>();

    public void AddPeer(TextBoxSynchronizedScroll peer)
    {
        this.peers.Add(peer);
    }

    private void DirectWndProc(ref Message m)
    {
        base.WndProc(ref m);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_VSCROLL)
        {
            foreach (var peer in this.peers)
            {
                var peerMessage = Message.Create(peer.Handle, m.Msg, m.WParam, m.LParam);
                peer.DirectWndProc(ref peerMessage);
            }
        }

        base.WndProc(ref m);
    }
}

http://gist.github.com/593809

Joseph Kingry