I have a label which labels the line numbers based on the text on RichTextBox. I have hooked the event of Vscroll to handle the labeling.
private void rtbLogicCode_VScroll(object sender, EventArgs e)
{
Point pt = new Point(0, 1);
int firstIndex = rtbLogicCode.GetCharIndexFromPosition(pt);
int firstLine = rtbLogicCode.GetLineFromCharIndex(firstIndex);
pt.X = ClientRectangle.Width;
pt.Y = ClientRectangle.Height;
int lastIndex = rtbLogicCode.GetCharIndexFromPosition(pt);
int lastLine = rtbLogicCode.GetLineFromCharIndex(lastIndex);
// Small correction
if (rtbLogicCode.Text.EndsWith("\n"))
lastLine++;
labelLogicCode.ResetText();
LabelLineNum(firstLine+1,lastLine);
}
#endregion
private void LabelLineNum(int startNum, int lastNum)
{
labelLogicCode.Font = UIConstant.DDCLogicCodeFont;
for (int i = startNum; i < lastNum; i++)
{
labelLogicCode.Text += i + Environment.NewLine;
}
}
Everything seems to work properly except RichTextBox uses Smooth Scrolling feature, which screws up my line numbering in many cases where the user has not scrolled all the way to the next line. This causes the line numbers to be not synchronized with the actual text shown on the RichTextBox.
In the end, I need to disable smoothscrolling feature to accomplish this. I was told that you can override the postMessage API of RichTextBox to disable the mentioned feature but after searching through many documents, I couldn't find any good ones.
I would appreciate a solution that is as detailed as possible on how to disable smoothscrolling feature. Thanks.
Here's a VB example from Microsoft, suggesting you need to intercept WM_MOUSEWHEEL messages.
Here's a quick prototype in C#:
class MyRichTextBox : RichTextBox {
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(
IntPtr hWnd, // handle to destination window
uint Msg, // message
IntPtr wParam, // first message parameter
IntPtr lParam // second message parameter
);
const uint WM_MOUSEWHEEL = 0x20A;
const uint WM_VSCROLL = 0x115;
const uint SB_LINEUP = 0;
const uint SB_LINEDOWN = 1;
const uint SB_THUMBTRACK = 5;
private void Intercept(ref Message m) {
int delta = (int)m.WParam >> 16 & 0xFF;
if((delta >> 7) == 1) {
SendMessage(m.HWnd, WM_VSCROLL, (IntPtr)SB_LINEDOWN, (IntPtr)0);
} else {
SendMessage(m.HWnd, WM_VSCROLL, (IntPtr)SB_LINEUP, (IntPtr)0);
}
}
protected override void WndProc(ref Message m) {
switch((uint)m.Msg) {
case WM_MOUSEWHEEL:
Intercept(ref m);
break;
case WM_VSCROLL:
if(((uint)m.WParam & 0xFF) == SB_THUMBTRACK) {
Intercept(ref m);
} else {
base.WndProc(ref m);
}
break;
default:
base.WndProc(ref m);
break;
}
}
}
I know this is old, but if Dan Sporici's site ever goes down I thought I post his fantastic solution. It's simple and works easily with just a copy & paste.
class editedRichTextBox : RichTextBox
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
//this message is sent to the control when we scroll using the mouse
private const int WM_MOUSEWHEEL = 0x20A;
//and this one issues the control to perform scrolling
private const int WM_VSCROLL = 0x115;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MOUSEWHEEL)
{
int scrollLines = SystemInformation.MouseWheelScrollLines;
for (int i = 0; i < scrollLines; i++)
{
if ((int)m.WParam > 0) // when wParam is greater than 0
SendMessage(this.Handle, WM_VSCROLL, (IntPtr)0, IntPtr.Zero); // scroll up
else
SendMessage(this.Handle, WM_VSCROLL, (IntPtr)1, IntPtr.Zero); // else scroll down
}
return;
}
base.WndProc(ref m);
}
}
This will solve for VB.NET as well as C# (use code converter).
Just paste this code outside your own class (Eg: Public Class Form1).
Run the code once and new control will be added to the toolbox.
Now add the control to your FORM from the toolbox in IDE.
Code:
Public Class MyRichTextBox : Inherits RichTextBox
<System.Runtime.InteropServices.DllImport("user32.dll")> Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = &H20A Then : Dim ud As IntPtr
If m.WParam.ToInt32 > 0 Then ud = 0 Else ud = 1
For i = 1 To 3 : SendMessage(Me.Handle, &H115, ud, 0) : Next
Else : MyBase.WndProc(m) : End If
End Sub
End Class
Related
I am using Extended monitor screen. How to set the custom popup position of the predefined windows of Microsoft (for Example: OpenFileDialog) in windows forms using C#. This dialog should open to the center of the parent.
There is an other question answered by Dima, who wrote the following:
Open file dialog is a system dialog. It means that your application
has no control over it. It is controlled by user. If user wants to
change its size in one application, it will gain new size in all
applications, since this size is user's preference.
Purpose of common dialogs is providing standard UI parts to user.
Imagine how uncomfortable for users it would be to search for file to
open if every application would use its own OpenFileDialog.
However you can build your own dialog.
But if you want to open your custom form you can use this:
SomeForm form = new SomeForm();
form.StartPosition = FormStartPosition.CenterParent;
form.ShowDialog(this);
Sadly it will not work this way if you use this method with simple Show() maybe because than it will use another thread and cant access to the parent, or I don't really know.
I hope it helped.
EDIT:
If your problem is that the dialog isn't opening on the same screen your application runs maybe there is a solution here:
private void button1_Click(object sender, EventArgs e)
{
Point myNewLocation = Location;
myNewLocation.Offset(20, 20);
OpenFileDialog dlg = new OpenFileDialog();
dlg.Title = "Open";
MoveDialogWhenOpened(dlg.Title, myNewLocation);
dlg.ShowDialog(this);
}
private void MoveDialogWhenOpened(String windowCaption, Point location)
{
Object[] argument = new Object[] { windowCaption, location };
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler(MoveDialogThread);
backgroundWorker.RunWorkerAsync(argument);
}
private void MoveDialogThread(Object sender, DoWorkEventArgs e)
{
const String DialogWindowClass = "#32770";
String windowCaption = (String)(((Object[])e.Argument)[0]);
Point location = (Point)(((Object[])e.Argument)[1]);
// try for a maximum of 4 seconds (sleepTime * maxAttempts)
Int32 sleepTime = 10; // milliseconds
Int32 maxAttempts = 400;
for (Int32 i = 0; i < maxAttempts; ++i)
{
// find the handle to the dialog
IntPtr handle = Win32Api.FindWindow(DialogWindowClass, windowCaption);
// if the handle was found and the dialog is visible
if ((Int32)handle > 0 && Win32Api.IsWindowVisible(handle) > 0)
{
// move it
Win32Api.SetWindowPos(handle, (IntPtr)0, location.X, location.Y,
0, 0, Win32Api.SWP_NOSIZE | Win32Api.SWP_NOZORDER);
break;
}
// if not found wait a brief sec and try again
Thread.Sleep(sleepTime);
}
}
public class Win32Api
{
public const Int32 SWP_NOSIZE = 0x1;
public const Int32 SWP_NOZORDER = 0x4;
[DllImport("user32")]
public static extern IntPtr FindWindow(String lpClassName,
String lpWindowName);
[DllImport("user32")]
public static extern Int32 IsWindowVisible(IntPtr hwnd);
[DllImport("user32")]
public static extern Int32 SetWindowPos(IntPtr hwnd,
IntPtr hwndInsertAfter,
Int32 x,
Int32 y,
Int32 cx,
Int32 cy,
Int32 wFlags);
}
GetOpenFilename() allows you specify a hook procedure via the OFN_ENABLEHOOK flag,
and through this, you can create a hook procedure that receives a window handle to a child window of the open file dialog. By calling GetParent() from the hook procedure, you can subclass the window procedure of the GetOpenFilename() dialog and control where it locates itself when it is shown.
This method works well for me.
HWND hMainWnd = NULL; // Designated window handle assigned before GetOpenFilename() call.
TCHAR szFilepath[512] = _T(""); // Filename returned from GetOpenFile().
const UINT WM_OPENSAVE_DIALOG = RegisterWindowMessage(_T("OPENSAVE"));
bool bTriggerMove = false; // Move open file dialog only once, on WM_INITDIALOG.
WNDPROC pOldDlgProc = NULL; // Pointer to original window procedur for open file dialog
// Forward declarations.
BOOL CALLBACK NewOpenFileProc( HWND hDlg, UINT uMsg,
WPARAM wParam, LPARAM lParam );
UINT_PTR CALLBACK OpenFileHookProc( HWND hDlg, UINT uMsg,
WPARAM wParam, LPARAM lParam );
// Define a hook procedure for the GetOpenFilename() call
//---------------------------------------------------------------------------
UINT_PTR CALLBACK OpenFileHookProc( HWND hDlg, UINT uMsg,
WPARAM wParam, LPARAM lParam )
{
//==========================================================================
// This is the publicly exposed hook procedure for the open file dialog.
// From here, we subclass the parent window to get the *actual* OpenFile
// dialog box procedure, hook it, and set our trigger
if( uMsg == WM_INITDIALOG )
{
//The hook procedure handle is a child window of the actual dialog.
HWND hDlgParent = GetParent( hDlg );
pOldDlgProc = (WNDPROC)GetWindowLong( hDlgParent, GWL_WNDPROC );
bTriggerMove = true;
SetWindowLong( hDlgParent, GWL_WNDPROC, (LONG) NewOpenFileProc );
}
return 0;
}
// Define a window procedure subclass for the GetOpenFilename() call
//---------------------------------------------------------------------------
BOOL CALLBACK NewOpenFileProc( HWND hDlg, UINT uMsg,
WPARAM wParam, LPARAM lParam )
{
BOOL bResult = FALSE;
//========================================================================
// This is a window procedure subclass that centers a file open dialog
// over a designated window when GetOpenFilename() is called and refers
// to the OPENFILENAME structure defined in the function MyFileOpenDialog(),
// below. The dialog is centered over it's designated window regardless of
// what monitor the designated window is on.
if( pOldDlgProc != NULL )
{
if( uMsg == WM_NCDESTROY )
{
// Destroying open dialog window so remove window subclass.
SetWindowLong( hDlg, GWL_WNDPROC,(LONG)pOldDlgProc );
// Falls through and calls the old dialog procedure.
}
else
if( uMsg == WM_SHOWWINDOW )
{
// Showing or hide the open dialog window
if( wParam == TRUE && bTriggerMove == true )
{
// Showing the window for the first time.
bTriggerMove = false;
SendMessage( hDlg, WM_OPENSAVE_DIALOG, 0, 0 );
}
// Falls through and calls the old dialog procedure.
}
else
if( uMsg == WM_OPENSAVE_DIALOG )
{
// Center the dialog over hMainWnd (the designated window).
RECT rcDlg; int cxDlg, cyDlg;
RECT rcWnd; int cxWnd, cyWnd;
int iLeft, iTop;
// main window metrics
GetWindowRect( hMainWnd, &rcWnd );
cxWnd= rcWnd.right - rcWnd.left;
cyWnd= rcWnd.bottom - rcWnd.top;
// OpenFilename dialog metrics
GetWindowRect( hDlg, &rcDlg );
cxDlg= rcDlg.right - rcWnd.left;
cyDlg= rcDlg.bottom - rcDlg.top;
iLeft = rcWnd.left + ( ( cxWnd - cxDlg ) / 2);
iTop = rcWnd.top + ( ( cyWnd - cyDlg ) / 2);
SetWindowPos( hDlg, NULL, iLeft, iTop , 0, 0, SWP_NOSIZE );
//Don't call old window procedure for this custom message.
return FALSE;
}
bResult = CallWindowProc( pOldDlgProc, hDlg, uMsg, wParam, lParam );
}
return bResult;
}
// Define a function to incorporate necessary business logic, assign
// handle of window to center the dialog on, then call GetOpenFilename().
//---------------------------------------------------------------------------
void MyOpenFileDialog( void )
{
OPENFILENAME ofn;
// Initialize OPENFILENAME
memset( &ofn, 0, sizeof(ofn) );
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hMainWnd;
ofn.lpstrFile = szFilepath;
ofn.nMaxFile = RTL_NUMBER_OF(szFilepath);
ofn.lpstrFilter = _T("Excel Workbook\0*.xls\0");
ofn.nFilterIndex = 0;
ofn.lpstrTitle = _T("Import from Excel...");
ofn.lpfnHook = OFNHookProc;
ofn.Flags = OFN_PATHMUSTEXIST |
OFN_FILEMUSTEXIST |
OFN_ENABLEHOOK |
OFN_EXPLORER |
OFN_ENABLESIZING |
0;
// Display the Open dialog box.
if( GetOpenFileName( &ofn ) != TRUE )
{
return;
}
}
I am using Windows Forms and I am attempting to use SendMessage to get the ComboBox dropdown rectangle. However I can't seem to find the correct parameter combination that will allow the code to compile.
I have tried copying examples I found, but nothing seems to compile.
Here are some examples of lines that do not compile:
var z1 = SendMessage(hWnd, CB_GETDROPPEDCONTROLRECT, (IntPtr)1, (IntPtr)0); // The best overloaded match has some invalid arguments.
var z2 = SendMessage(hWnd, 0x0152, (IntPtr)1, (IntPtr)0);
var z3 = SendMessage(hWnd, CB_GETDROPPEDCONTROLRECT, 1, 0);
var z4 = SendMessage(hWnd, 0x0152, 1, 0);
Thanks in advance to anyone who has any ideas to make this work.
Here is my complete code:
public partial class Form1 : Form
{
[DllImport("user32.dll")]
public static extern int SendMessage(
int hWnd, // handle to destination window
uint Msg, // message
long wParam, // first message parameter
long lParam // second message parameter
);
public Form1()
{
InitializeComponent();
List<string> itms = new List<string>();
itms.Add("Choice 1");
itms.Add("Choice 2");
itms.Add("Choice 3");
itms.Add("Choice 4");
itms.Add("Choice 5");
this.comboBox1.Items.AddRange(itms.ToArray());
}
private void comboBox1_DropDown(object sender, EventArgs e)
{
const int CB_GETDROPPEDCONTROLRECT = 0x0152;
IntPtr hWnd = comboBox1.Handle;
var z = SendMessage(hWnd, CB_GETDROPPEDCONTROLRECT, (IntPtr)1, (IntPtr)0); // The best overloaded match has some invalid arguments.
var z1 = SendMessage(hWnd, 0x0152, (IntPtr)1, (IntPtr)0);
}
}
To get the dropdown rectangle of a combobox you can do this:
First, declare the RECT struct:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
Note: the Microsoft documentation states these fields should be long, but I tested it and for some strange reason SendMessage answers with int's here.
Second, the correct SendMessage declaration: For this special case you can now use a ref RECT parameter. Note that in your versions there are mistakes: hWnd needs to be an IntPtr while wParam is only int and not long:
[DllImport("user32.dll")]
public static extern int SendMessage(
IntPtr hWnd, // handle to destination window (combobox in this case)
int Msg, // message
int wParam, // first message parameter
ref RECT lParam // second message parameter
);
Third, the usage:
RECT rect = default;
int result = SendMessage(comboBox1.Handle, 0x0152, 1, ref rect);
Where comboBox1 is of course your ComboBox. If result is zero, the call failed, otherwise it succeeded and rect should contain the desired values.
I made a code that synchronize a scroll of two RichTextBox.
Hope this works without a matter of line numbers.
but when the line of RichTextBox gets large (around 2000+), System.OverflowException occurs at SendMessage method.
Covering SendMessage with try/catch does not make it work.
Is there any way to handle IntPtr with a number which is greater than Int.MaxValue?
This is my code.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
for (int a = 0; a < 4000; a++)
{
RTB1.Text += a + "\n";
RTB2.Text += a + "\n";
}
}
[DllImport("User32.dll")]
public extern static int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("User32.dll")]
public extern static int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
private void RTB1_VScroll(object sender, EventArgs e)
{
int nPos = GetScrollPos(RTB1.Handle, (int)ScrollBarType.SbVert);
nPos <<= 16;
uint wParam = (uint)ScrollBarCommands.SB_THUMBPOSITION | (uint)nPos;
SendMessage(RTB2.Handle, (int)Message.WM_VSCROLL, new IntPtr(wParam), new IntPtr(0)); //Error occurs here.
}
public enum ScrollBarType : uint
{
SbHorz = 0,
SbVert = 1,
SbCtl = 2,
SbBoth = 3
}
public enum Message : uint
{
WM_VSCROLL = 0x0115
}
public enum ScrollBarCommands : uint
{
SB_THUMBPOSITION = 4
}
}
Looks like your application is running as 32 bit and you're getting an Overflow because UInt can have a value which can't be fit in 32 bit signed int.
For instance, running your application as 64 bit should just work fine.
That said, you don't need that. You can simply avoid using uint and just use int which will work just fine.
int wParam = (int)ScrollBarCommands.SB_THUMBPOSITION | (int)nPos;
I guess this is generic issue and not limited to ComboBox, however I have specifically problem with ComboBox. I extended ComboBox object with MyCB MyCB : ComboBox)
What happens is every time I hover over the control, leave the control, expand selection box or select a value, the control flickers. For a short while I can see default (non-replaced) control which is being instantly replaced with mine.
I believe what's happening is that Windows first draws the "original" control (by calling base.WndProc()) and then repaints it with mine.
The question is, can I somehow stop windows from painting it's own control and instantly paint mine?
Below is code overriding WndProc
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
Graphics gg = this.CreateGraphics();
gg.FillRectangle(BorderBrush, this.ClientRectangle);
// ... //
//Draw the arrow
gg.FillPath(ArrowBrush, pth);
// ... //
if(this.Text == "")
gg.DrawString("-- SELECT --", this.Font, new SolidBrush(Color.Black), rf, sf);
else
gg.DrawString(this.Text, this.Font, new SolidBrush(Color.Black), rf, sf);
gg.Dispose();
}
}
What have I tried so far:
I know that I can't do this:
if (m.Msg == WM_PAINT)
{
...
}
else
{
base.WndProc(ref m);
}
as that will cause control to repaint itself infinitely (not sure why)
I was able to remove flickering which happened when mouse leaves/enters the control by adding this code
if (m.Msg == WM_MOUSEFIRST || m.Msg == WM_MOUSELEAVE) // 0x0200 0x02A3
{
m.Result = (IntPtr)1;
}
else
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
...
}
}
however this doesn't solve the problem completely
I looked into ILSpy to check on ComboBox's WndProc but there were so many windows messages that I didn't know which of those I could possibly immitate to achive my goal
public CustomComboBox()
{
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == Win32.WM_MOUSEFIRST || m.Msg == Win32.WM_MOUSELEAVE || m.Msg == Win32.WM_MOUSEHOVER) //0x0200, 0x02A3, 0x02A1
{
m.Result = (IntPtr)1;
return;
}
base.WndProc(ref m);
if (m.Msg == Win32.WM_PAINT)
{
IntPtr hDC = Win32.GetWindowDC(m.HWnd);
Graphics g = Graphics.FromHdc(hDC);
g.SmoothingMode = SmoothingMode.HighSpeed;
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
if (Text.Length == 0)
{
StringFormat stringFormat = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Near
};
g.DrawString("Water Marks", Font, new SolidBrush(Color.FromArgb(51, 51, 51)), (Bounds.Width - 2), FontHeight + 2, stringFormat);
}
Pen border = new Pen(ui_BorderLineColor, 1);
g.DrawRectangle(border, 0, 0, this.Width - 1, this.Height - 1);
g.Dispose();
Win32.ReleaseDC(m.HWnd, hDC);
}
}
protected override void InitLayout()
{
base.InitLayout();
DropDownStyle = ComboBoxStyle.DropDownList; //Works only in DropDownList
}
// Edit completing Moses comment
public class Win32
{
public const int WM_MOUSEFIRST = 0x0200;
public const int WM_MOUSELEAVE = 0x02A3;
public const int WM_MOUSEHOVER = 0x02A1;
public const int WM_PAINT = 0x000F;
[DllImport("user32")]
public static extern IntPtr GetWindowDC(IntPtr hwnd);
[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
[DllImport("user32.dll")]
public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprc, IntPtr hrgn, uint flags);
}
In my application's form, I have two RichTextBox objects. They will both always have the same number of lines of text. I would like to "synchronize" the vertical scrolling between these two, so that when the user changes the vertical scroll position on one, the other scrolls the same amount. How might I go about doing this?
Thanks Jay for your answer; after some more searching I also found the method described here. I'll outline it below for anyone else interested.
First, declare the following enums:
public enum ScrollBarType : uint {
SbHorz = 0,
SbVert = 1,
SbCtl = 2,
SbBoth = 3
}
public enum Message : uint {
WM_VSCROLL = 0x0115
}
public enum ScrollBarCommands : uint {
SB_THUMBPOSITION = 4
}
Next, add external references to GetScrollPos and SendMessage.
[DllImport( "User32.dll" )]
public extern static int GetScrollPos( IntPtr hWnd, int nBar );
[DllImport( "User32.dll" )]
public extern static int SendMessage( IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam );
Finally, add an event handler for the VScroll event of the appropriate RichTextBox:
private void myRichTextBox1_VScroll( object sender, EventArgs e )
{
int nPos = GetScrollPos( richTextBox1.Handle, (int)ScrollBarType.SbVert );
nPos <<= 16;
uint wParam = (uint)ScrollBarCommands.SB_THUMBPOSITION | (uint)nPos;
SendMessage( richTextBox2.Handle, (int)Message.WM_VSCROLL, new IntPtr( wParam ), new IntPtr( 0 ) );
}
In this case, richTextBox2's vertical scroll position will be synchronized with richTextBox1.
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.
[Visual Studio C# 2010 Express, v10.0.30319 on a Windows 7 64bit installation]
I've used Donut's solution posted above, but found a problem when scrolling to the end of RichTextBoxes that contain many lines.
If the result of GetScrollPos() is >0x7FFF then when nPos is shifted, the top bit is set. The creation of the IntPtr with the resulting wParam variable will then fail with an OverflowException. You can easily test this with the following (the second line will fail):
IntPtr ip = new IntPtr(0x7FFF0000);
IntPtr ip2 = new IntPtr(0x80000000);
A version of SendMessage() that uses UIntPtr would appear to be a solution, but I couldn't get that to work. So, I've use the following:
[DllImport("User32.dll")]
public extern static int SendMessage(IntPtr hWnd, uint msg, UInt32 wParam, UInt32 lParam);
This should be good up to 0xffff, but would fail after that. I've not yet experienced a >0xffff result from GetScrollPos(), and assume that User32.dll is unlikely to have a 64bit version of SendCommand(), but any solutions to that problem would be greatly appreciated.
const int WM_USER = 0x400;
const int EM_GETSCROLLPOS = WM_USER + 221;
const int EM_SETSCROLLPOS = WM_USER + 222;
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref Point lParam);
private void RichTextBox1_VScroll(object sender, EventArgs e)
{
Point pt;
SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, ref pt);
SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, ref pt);
}
private void RichTextBox2_VScroll(object sender, EventArgs e)
{
Point pt;
SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, ref pt);
SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, ref pt);
}
A variation of Jay's subclass approach can be found in Joseph Kingry's answer here: Synchronizing Multiline Textbox Positions in C#.
Joseph's approach also subclasses but doesn't require a _VScroll event handler. I used that approach to do a 3-way bind between 3 boxes and added WM_HSCROLL.
#Sudhakar MuthuKrishnan's answer needs some fixes, but works. Thanks!
First GetScrollPos which rised event and then set scroll position for others.
private void RichTextBox1_VScroll(object sender, EventArgs e)
{
Point pt = new Point();
SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, ref pt);
SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, ref pt);
}
private void RichTextBox2_VScroll(object sender, EventArgs e)
{
Point pt = new Point();
SendMessage(RichTextBox2.Handle, EM_GETSCROLLPOS, 0, ref pt);
SendMessage(RichTextBox1.Handle, EM_SETSCROLLPOS, 0, ref pt);
}