DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report

Tie the scrolling between two controls

Denzel D. user avatar by
Denzel D.
·
Jan. 21, 11 · Interview
Like (0)
Save
Tweet
Share
4.84K Views

Join the DZone community and get the full member experience.

Join For Free

Today I encountered an interesting question on Dream.In.Code. The original poster was curious whether it is possible to tie two TextBox controls in a way so that the scrolling of one TextBox control would trigger the scrolling of another. Initially, I tried to look at .NET events but there was nothing available for me, so I had to look at Windows API to directly hook to the scrolling event. For now, I only had to look at vertical scrolling so I had to implement the most basic hooks.

What I did first is re-implement the TextBox control as a custom class that inherits the base properties and capabilities of the TextBox class.

public class ExtendedTextBox : TextBox
{
private int oldValue = 0;
private int newValue = 0;

private ExtendedTextBox textBox = null;

public event ScrollEventHandler VerticalScrolled = null;

private System.ComponentModel.Container components = null;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x115)
{
if (VerticalScrolled != null)
{
SCROLLINFO info = new SCROLLINFO();
info.cbSize = Marshal.SizeOf(info);
info.fMask = 1| 2| 3 | 4 | 10;
WinApi.GetScrollInfo(m.HWnd, 1, out info);

if (m.WParam.ToInt32() == 8)
{
ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, info.nPos);
VerticalScrolled(this, args);
oldValue = newValue;
newValue = args.NewValue;

if (text != null)
{
WinApi.SendMessage(text.Handle, 182, IntPtr.Zero, (IntPtr)(newValue - oldValue));
}
}
}
}
base.WndProc(ref m);
}

public ExtendedTextBox()
{

}

public ExtendedTextBox(ExtendedTextBox textBox)
{
this.textBox = textBox;
}

private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
}

The overall class structure might seem confusing, so I will explain each part. The standard Dispose and InitializeComponent methods are standard for Windows controls - these allow the developer to control memory allocations for a specific control when that one is in use or not.

There are two constructors defined - one for the un-linked ExtendedTextBox that can be used to track scrolling when you don't need another ExtendedTextBox scrolling simultaneously, and another one for a linked ExtendedTextBox control:

public ExtendedTextBox()
{

}

public ExtendedTextBox(ExtendedTextBox textBox)
{
this.textBox = textBox;
}

If the second constructor is used, I am using an internal instance of ExtendedTextBox to later use its handle for scrolling purposes.

Also, I now have an event handler that can be used for tracking vertical scrolling:

public event ScrollEventHandler VerticalScrolled = null;

This event is not specific to a TextBox control, so that's why I have to manually include it in the extended class.

The next step is overriding the WndProc method that executes a call to the WindowProc function. This is the gateway to event tracking since WindowProc handles the messages sent to a window/control with a handle identifier (hWnd). The vertical scroll message is identified by an integer value equal to 115 - same as the WM_VSCROLL constant.

protected override void WndProc(ref Message m)
{
if (m.Msg == 0x115)
{

}
base.WndProc(ref m);
}

This checks the current message processed by the control and in case this is the vertical scrolling message, it needs to be handled.

If the event handler is null (therefore it is not possible to handle the scrolling), there is no need to actually try to process the scrolling even if it happens. So let's say that there is an event handler. If that's the case, I need to instantiate a SCROLLINFO struct that will keep some information related to the current scrolling progress.

The struct itself has the following structure:

[StructLayout(LayoutKind.Sequential)
public struct SCROLLINFO
{
public int cbSize;
public int fMask;
public int nMin;
public int nMax;
public int nPage;
public int nPos;
public int nTrackPos;
}

I am required to specify the size of the struct and also set the mask that will define the returned values:

SCROLLINFO info = new SCROLLINFO();
info.cbSize = Marshal.SizeOf(info);
info.fMask = 1| 2| 3 | 4 | 10;

The numbers used in the mask are also associated with struct-specific constants:

  • SIF_RANGE = 0x1
  • SIF_PAGE = 0x2
  • SIF_POS = 0x4
  • SIF_TRACKPOS = 0x10
  • SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS

In this case, I need SIF_ALL that will return all possible data related to the scroll bar in the control.

NOTE: This will only apply to the scroll bar and not to the control itself. Therefore, the returned struct can only be used to refer to the scroll bar instance.

It is now time to get the actual data via GetScrollInfo:

WinApi.GetScrollInfo(m.HWnd, 1, out info);

This is a native WinApi function referenced via DllImport:

public static class WinApi
{
[DllImport("user32.dll")]
public extern static bool GetScrollInfo(IntPtr hWnd, int nBar, out SCROLLINFO lpsi);
}

I created a separate static class to hold WinApi function references. It is possible to have those separated inside the actual working class, but the way I have it makes it more organized and easier to access and extend later on.

The second parameter in the GetScrollInfo call is the constant that represents the current scroll bar that should be tracked. An integer value of 1 represents SB_VERT, also known as the vertical scroll bar.

The wParam I am checking next should represent SB_ENDSCROLL - the marker that shows that the scrolling is over, and when this happens, I am defining the ScrollEventArgs instance and trigger the scrolling event handler:

if (m.WParam.ToInt32() == 8)
{
ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, info.nPos);
VerticalScrolled(this, args);
}

In the same if statement I am storing the current line and the old line, so that I can later on calculate the difference and scroll the linked text box according to the number of lines used in the first one:

oldValue = newValue;
newValue = args.NewValue;

If the textBox field is not empty, which means that the second constructor is used, the SendMessage function can be used to scroll the linked text box to a number of lines represented by the difference I mentioned above:

if (textBox != null)
{
WinApi.SendMessage(textBox.Handle, 182, IntPtr.Zero, (IntPtr)(newValue - oldValue));
}

The message I am sending is representing the EM_LINESCROLL constant, equal to 182 (hex: &HB6). The lParam here is the number of lines.

The SendMessage is also referenced in the static WinApi class:

[DllImport("user32.dll")]
public extern static bool SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

Trying it out

Now, in the working form, make sure you add the needed ExtendedTextBox instances:

// First extended text box - will be tied to the second one.
ExtendedTextBox textBox = new ExtendedTextBox();
textBox.ScrollBars = ScrollBars.Vertical;

for (int i = 0; i < 100; i++)
{
textBox.Text += i.ToString() + Environment.NewLine;
}

textBox.Width = 100;
textBox.Height = 100;
textBox.Multiline = true;
this.Controls.Add(textBox);

// The "master" extended text box.
ExtendedTextBox textBox2 = new ExtendedTextBox(textBox);
textBox2.ScrollBars = ScrollBars.Vertical;

for (int i = 0; i < 100; i++)
{
textBox2.Text += i.ToString() + Environment.NewLine;
}

textBox2.Width = 100;
textBox2.Height = 100;
textBox2.Multiline = true;
textBox2.Location = new Point(200, 0);
textBox2.VerticalScrolled += new ScrollEventHandler(textBox2_VerticalScrolled);
this.Controls.Add(textBox2);

Here I am also adding some sample data to show the scrolling procedures - that's what the for loops are there for. The second text box constructor receives the first one as a reference and links it to itself so that the scrolling will be symmetrical.

Event

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 19 Most Common OpenSSL Commands for 2023
  • NoSQL vs SQL: What, Where, and How
  • What Are the Different Types of API Testing?
  • Java REST API Frameworks

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: