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);
}
Related
This question already has answers here:
RichTextBox syntax highlighting in real time--Disabling the repaint
(3 answers)
Closed 2 years ago.
I'm doing a text editor based on RichTextBox. It has to handle complex formatting (BIU, colored text, etc). The problem is that all formatting tools are selection based, e.g. i have to select piece of text, format it, select next, etc.
it takes time, and it is visible for user.
is there a way to turn-off RichTextBox redraw, then do formatting, then turn-on redraw?
Or maybe any other way to handel complex formatting quickly?
Decision found and it's working.
Wrote a wrap-class using this code
Class itself:
public class RichTextBoxRedrawHandler
{
RichTextBox rtb;
public RichTextBoxRedrawHandler (RichTextBox _rtb)
{
rtb = _rtb;
}
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int wMsg, int wParam, ref Point lParam);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int wMsg, int wParam, IntPtr lParam);
const int WM_USER = 1024;
const int WM_SETREDRAW = 11;
const int EM_GETEVENTMASK = WM_USER + 59;
const int EM_SETEVENTMASK = WM_USER + 69;
const int EM_GETSCROLLPOS = WM_USER + 221;
const int EM_SETSCROLLPOS = WM_USER + 222;
private Point _ScrollPoint;
private bool _Painting = true;
private IntPtr _EventMask;
private int _SuspendIndex = 0;
private int _SuspendLength = 0;
public void SuspendPainting()
{
if (_Painting)
{
_SuspendIndex = rtb.SelectionStart;
_SuspendLength = rtb.SelectionLength;
SendMessage(rtb.Handle, EM_GETSCROLLPOS, 0, ref _ScrollPoint);
SendMessage(rtb.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
_EventMask = SendMessage(rtb.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
_Painting = false;
}
}
public void ResumePainting()
{
if (!_Painting)
{
rtb.Select(_SuspendIndex, _SuspendLength);
SendMessage(rtb.Handle, EM_SETSCROLLPOS, 0, ref _ScrollPoint);
SendMessage(rtb.Handle, EM_SETEVENTMASK, 0, _EventMask);
SendMessage(rtb.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
_Painting = true;
rtb.Invalidate();
}
}
}
Usage:
RichTextBoxRedrawHandler rh = new RichTextBoxRedrawHandler(richTextBoxActually);
rh.SuspendPainting();
// do things with richTextBox
rh.ResumePainting();
I use C#, i have an app with a button1, button2 and a panel1, if i click to button1 it open an external program to panel1, then if i click to button2 it send a background click to the window, works fine.
But the problem is this click position is not relative to the panel and not go to right place. It send the click where are the cursor actually.
Code pieces what i use for that:
PerformRightClick(proc.MainWindowHandle, new Point(54, 42));
void PerformRightClick(IntPtr hwnd, Point point)
{
var pointPtr = MakeLParam(point.X, point.Y);
SendMessage(hwnd, WM_MOUSEMOVE, IntPtr.Zero, pointPtr);
SendMessage(hwnd, WM_RBUTTONDOWN, IntPtr.Zero, pointPtr);
SendMessage(hwnd, WM_RBUTTONUP, IntPtr.Zero, pointPtr);
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
IntPtr MakeLParam(int x, int y) => (IntPtr)((y << 16) | (x & 0xFFFF));
Based on my test, we need to use panel.Handle when we want to send click to panel.
Here is a code example you can refer to.
private const Int32 WM_MOUSEMOVE = 0x0200;
private const Int32 WM_RBUTTONDOWN = 0x0204;
private const Int32 WM_RBUTTONUP = 0x0205;
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
IntPtr MakeLParam(int x, int y) => (IntPtr)((y << 16) | (x & 0xFFFF));
private void button1_Click(object sender, EventArgs e)
{
PerformRightClick(panel1.Handle, new Point(20, 30));
}
void PerformRightClick(IntPtr hwnd, Point point)
{
var pointPtr = MakeLParam(point.X, point.Y);
SendMessage(hwnd, WM_MOUSEMOVE, IntPtr.Zero, pointPtr);
SendMessage(hwnd, WM_RBUTTONDOWN, IntPtr.Zero, pointPtr);
SendMessage(hwnd, WM_RBUTTONUP, IntPtr.Zero, pointPtr);
}
private void panel1_Click(object sender, EventArgs e)
{
MessageBox.Show("Panel is clicked");
}
private void button2_Click(object sender, EventArgs e)
{
PerformRightClick(this.Handle, new Point(20, 30));
}
private void Form1_Click(object sender, EventArgs e)
{
MessageBox.Show("Form is clicked");
}
Pic:
Like the above picture, when we click button1, the panel is clicked and the Form is clicked when we click button2.
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;
Goal: write a C# app that runs in the background, listens for the key combination Win-V, and when that occurs, pastes the clipboard contents into the current active window (some arbitrary app). Essentially I'm trying to mimic PureText, but I'm not bothering to convert the text to plain text first.
Problem: pasting into the currently active windows is not working.
Details: To listen in the background for key presses I'm using the globalKeyboardHook class from A Simple C# Global Low Level Keyboard Hook. I'm able to catch Win-V events, but I'm not able to send the paste command properly. I can send the paste by using the functions SendKeys.Send or keybd_event. However, they send another "V" press down the pipeline which gets caught by the gkh_KeyDown event and causes multiple paste events to fire.
I'm expecting that I need to use SendMessage or PostMessage, but all my attempts to do that have failed so far. Below is the full code with the last function, SendCtrlV, being the one of interest. The comments explain everything I've tried so far. Can you see what I'm missing?
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Utilities;
namespace KeyHookTest
{
public partial class Form1 : Form
{
private bool LWin_down;
private bool V_down;
globalKeyboardHook gkh = new globalKeyboardHook();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hwnd, int msg, int wParam, int lParam);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
gkh.HookedKeys.Add(Keys.V);
gkh.HookedKeys.Add(Keys.LWin);
gkh.KeyDown += new KeyEventHandler(gkh_KeyDown);
gkh.KeyUp += new KeyEventHandler(gkh_KeyUp);
}
void gkh_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.LWin)
LWin_down = false;
else
V_down = false;
}
void gkh_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.LWin)
LWin_down = true;
else
V_down = true;
if (LWin_down && V_down)
{
LogDebug("Enter Win+V");
try
{
SendCtrlV();
}
catch { }
}
}
private void SendCtrlV()
{
uint KEYEVENTF_KEYUP = 2;
int KEYDOWN = 0x0100;
int KEYUP = 0x0101;
byte KEY_LCONTROL1 = 0x11;
IntPtr KEY_LCONTROL2 = new IntPtr(0x11);
byte KEY_V1 = 0x56;
IntPtr KEY_V2 = new IntPtr(0x56);
int WM_PASTE1 = 0x302;
uint WM_PASTE2 = 0x302;
IntPtr hWnd = GetForegroundWindow();
// Works, but causes multiple gkh_KeyDown to fire so it's slow and buggy
/*keybd_event(KEY_LCONTROL1, 0, 0, 0);
keybd_event(KEY_V1, 0, 0, 0);
keybd_event(KEY_V1, 0, KEYEVENTF_KEYUP, 0);
keybd_event(KEY_LCONTROL1, 0, KEYEVENTF_KEYUP, 0);*/
// Works, but causes multiple gkh_KeyDown to fire so it's slow and buggy
//SendKeys.Send("^v");
// Doesn't work, causes UAC prompt
//SendKeys.Send("{^}v");
// Doesn't work, nothing gets pasted to the foregroundwindow
//SendMessage(hWnd, WM_PASTE1, 0, 0);
// Doesn't work, nothing gets pasted to the foregroundwindow
//PostMessage(hWnd, WM_PASTE2, IntPtr.Zero, IntPtr.Zero);
// Doesn't work, nothing gets pasted to the foregroundwindow
/*SendMessage(hWnd, KEYDOWN, KEY_LCONTROL1, 0);
SendMessage(hWnd, KEYDOWN, KEY_V1, 0);
SendMessage(hWnd, KEYUP, KEY_V1, 0);
SendMessage(hWnd, KEYUP, KEY_LCONTROL1, 0);*/
// Doesn't work, nothing gets pasted to the foregroundwindow
/*PostMessage(hWnd, 0x0100, KEY_LCONTROL2, IntPtr.Zero);
PostMessage(hWnd, 0x0100, KEY_V2, IntPtr.Zero);
PostMessage(hWnd, 0x0101, KEY_V2, IntPtr.Zero);
PostMessage(hWnd, 0x0101, KEY_LCONTROL2, IntPtr.Zero);*/
}
private void LogDebug(string msg)
{
string logpath = Environment.GetEnvironmentVariable("USERPROFILE") + #"\Desktop\KeyHookTest.txt";
File.AppendAllText(logpath, DateTime.Now.ToString("HH:mm:ss:fff") + ": " + msg + "\r\n");
}
}
}
These additional links helped lead me to the answer:
How to get active child window
How can I find the active child window?
Here's what's working for me:
private void SendCtrlV()
{
IntPtr hWnd = GetFocusedHandle();
PostMessage(hWnd, WM_PASTE, IntPtr.Zero, IntPtr.Zero);
}
static IntPtr GetFocusedHandle()
{
var info = new GuiThreadInfo();
info.cbSize = Marshal.SizeOf(info);
if (!GetGUIThreadInfo(0, ref info))
throw new Win32Exception();
return info.hwndFocus;
}
It works, but you must use the TextBox's native window handle if you want it to be effective