I have a WinForms ComboBox that remembers what items have previously been entered into it. I want a way to remove previous entries. I override the DrawItem event of the ComboBox to render the text along with an X icon. The X icon is just a square image that I scale to the height of the item. The code is pretty straight forward.
// Enable the owner draw on the ComboBox.
ServerComboBox.DrawMode = DrawMode.OwnerDrawFixed;
// Handle the DrawItem event to draw the items.
ServerComboBox.DrawItem += delegate(object cmb, DrawItemEventArgs args)
{
// Draw the default background
args.DrawBackground();
String url = (String)ServerComboBox.Items[args.Index];
// Get the bounds for the first column
Rectangle r1 = args.Bounds;
r1.Width -= r1.Height;
// Draw the text
using (SolidBrush sb = new SolidBrush(args.ForeColor))
{
args.Graphics.DrawString(url, args.Font, sb, r1);
}
// Draw the X icon
Rectangle r2 = new Rectangle(r1.Width+1, r1.Y + 1, r1.Height - 2, r1.Height - 2);
args.Graphics.DrawImage(Project.Test.Properties.Resources.CloseIcon, r2);
};
Now my issue is how to capture if the X was clicked. My first thought was to capture the MouseDown event for the ComboBox and check if the DroppedDown property was true, but that event only gets fired when you click the unexpanded ComboBox. How can I capture events from the DropDown part of the ComboBox. Once I get that, I don't think it'll be much of an issue figuring out if the X was clicked or now.
In fact your problem is just a trivial problem which Win32 can solve:
public class Form1 : Form {
[DllImport("user32")]
private static extern int GetComboBoxInfo(IntPtr hwnd, out COMBOBOXINFO comboInfo);
struct RECT {
public int left, top, right, bottom;
}
struct COMBOBOXINFO {
public int cbSize;
public RECT rcItem;
public RECT rcButton;
public int stateButton;
public IntPtr hwndCombo;
public IntPtr hwndItem;
public IntPtr hwndList;
}
public Form1(){
InitializeComponent();
comboBox1.HandleCreated += (s, e) => {
COMBOBOXINFO combo = new COMBOBOXINFO();
combo.cbSize = Marshal.SizeOf(combo);
GetComboBoxInfo(comboBox1.Handle, out combo);
hwnd = combo.hwndList;
init = false;
};
}
bool init;
IntPtr hwnd;
NativeCombo nativeCombo = new NativeCombo();
//This is to store the Rectangle info of your Icons
//Key: the Item index
//Value: the Rectangle of the Icon of the item (not the Rectangle of the item)
Dictionary<int, Rectangle> dict = new Dictionary<int, Rectangle>();
public class NativeCombo : NativeWindow {
//this is custom MouseDown event to hook into later
public event MouseEventHandler MouseDown;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x201)//WM_LBUTTONDOWN = 0x201
{
int x = m.LParam.ToInt32() & 0x00ff;
int y = m.LParam.ToInt32() >> 16;
if (MouseDown != null) MouseDown(null, new MouseEventArgs(MouseButtons.Left, 1, x, y, 0));
}
base.WndProc(ref m);
}
}
//DropDown event handler of your comboBox1
private void comboBox1_DropDown(object sender, EventArgs e) {
if (!init) {
//Register the MouseDown event handler <--- THIS is WHAT you want.
nativeCombo.MouseDown += comboListMouseDown;
nativeCombo.AssignHandle(hwnd);
init = true;
}
}
//This is the MouseDown event handler to handle the clicked icon
private void comboListMouseDown(object sender, MouseEventArgs e){
foreach (var kv in dict) {
if (kv.Value.Contains(e.Location)) {
//Show the item index whose the corresponding icon was held down
MessageBox.Show(kv.Key.ToString());
return;
}
}
}
//DrawItem event handler
private void comboBox1_DrawItem(object sender, DrawItemEventArgs e) {
//We have to save the Rectangle info of the item icons
Rectangle rect = new Rectangle(0, e.Bounds.Top, e.Bounds.Height, e.Bounds.Height);
dict[e.Index] = rect;
//Draw the icon
//e.Graphics.DrawImage(yourImage, rect);
}
}
A little about what happens
A ComboBox has an attached Drop down list, this can be accessed via its Handle. We use GetComboBoxInfo win32 api function to retrieve some info of a ComboBox, this info is saved in a structure called COMBOBOXINFO. We can get the Handle of the Drop down list via the member hwndList in this structure.
After having access to the hwndList. We can hook into its message loop using a custom NativeWindow class (NativeCombo in the example). This allows us to interfere the message loop of the drop down list easily. Then we can catch the WM_LBUTTONDOWN message to handle the MouseDown event. Of course, it's not a full MouseDown event, but it's just a demo and in this case it's enough to solve the problem. The WM_LBUTTONDOWN is sent with some info on the clicked point saved in LParam. The clicked point is calculated in the coordinates of the Client area of the drop down list (not in screen coordinates). We should notice that the DrawItem event handler also has e.Bounds argument which is also calculated in the Client area coordinates (not screen coordinates). Hence they are in the same coordinates system. We use the Rectangle.Contains method to know if the clicked point is contained in the Bounds of some icon. We stores all the icon Bounds in a Dictionary. So the corresponding Key (stores the Item index) of the Rectangle which contains the clicked point let us know the corresponding Item index, then we can process further.
When the user selects or uses mouse wheel SelectionIndexChanged event will fire. If you don't want to react for mouse wheel and if you need to respond only for clicking and selecting then you may consider using SelectionChangeCommited event.
Then you can read SelectedIndex or SelectedItem property to get the selected item.
Edit: Am sorry it seems I complelely misunderstand the question. I think you need to capture mouse down of ListBox inside the Combo and manually check it using Rectange.Contains. Will get back with more details.
Once the user has clicked on an item in the combo box, it becomes the selected item. You can retrieve this property from the combo box object.
COMBOBOXINFO will give you that information that you can use to subclass the dropdown list window. See this for an example:
https://github.com/ehosca/MRUComboBox
Related
I have Listbox with initial number of items and it has vertical scrollbar.
I have implemented following behavior: if I move (holding left mouse button) scroll to the end of scrollbar then some number of a new items are added to the Listbox control. The problem is that scrollbar of Listbox is updated (with respect to the new listbox items count) only if I do left mouse button up. But I need it to be updated immediately after new items are added to the listbox and without need to stop dragging mouse down.
Example of right behavior that I want: https://www.google.com/fonts.
As you can see there, if you dragging scroll down it is immediatly updated and jumps on a number of positions up each time new items are loaded.
So, my question is: how to correctly implement such behavior without using 3rd party listbox controls?
EDIT:
In my Form1 form control I do load new items by testing scroll position:
public Form1()
{
InitializeComponent();
...
var timer = new System.Windows.Forms.Timer();
timer.Interval = 500;
timer.Tick += TimerCheckForListItemsScrollDown;
timer.Start();
}
private void TimerCheckForListItemsScrollDown(object sender, EventArgs e)
{
if (listBox1.Items.Count < 1)
{
return;
}
int visibleCount = listBox1.ClientSize.Height /
((MyListItem)(listBox1.Items[0])).ItemHeight + 1;
if (listBox1.TopIndex == 0)
return;
if (listBox1.TopIndex + visibleCount > listBox1.Items.Count - 5)
{
InsertNextBucketOfListItems();
//I try to invalidate and refresh control but with no positive results, while I hold left mouse button down nothing happens.
listBox1.Invalidate();
listBox1.Refresh();
}
}
EDIT 2:
I added my solution below.
It seems that I discovered a solution for my problem. The main idea is to generate WM_LBUTTONUP event on listbox control.
That is how I do that:
[DllImport("user32.dll")]
static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo);
private void TimerCheckForListItemsScrollDown(object sender, EventArgs e)
{
if (listBox1.Items.Count < 1)
{
return;
}
int visibleCount = listBox1.ClientSize.Height /
((MyListItem)(listBox1.Items[0])).ItemHeight + 1;
if (listBox1.TopIndex == 0)
return;
if (listBox1.TopIndex + visibleCount > listBox1.Items.Count - 5)
{
InsertNextBucketOfListItems();
//Now is the trick
var p = Cursor.Position;
//0004- mouse Up
mouse_event((uint)0x0004, (uint)p.X, (uint)p.Y, 0, UIntPtr.Zero);
}
}
Since that scroll is updated each time when new items are loaded. But I didn't realized how to continue scrolling without need to release mouse button after scroll is updated.
I have created a text box, inside a PictureBox, on mouse click at run time. Now I want to resize it using mouse drag. Is there some simple way to do this ?
Here is the code I have so far:
public partial class Form1 : Form
{
public static TextBox PTB; //declaring text box to be created
public static bool textOption; //stores the state of button , i.e whether or not text box button is clicked before or not
public Form1()
{
InitializeComponent();
this.pictureBox1.Click += new System.EventHandler(this.pictureBox1_Click);
}
private void pictureBox1_Click(object sender, EventArgs e)
{
if (textOption == true)//if user selected option to draw text box
{
MouseEventArgs eM = (MouseEventArgs)e; //create an instance of mouse event
PTB = new TextBox();//dynamically creating text box
PTB.Location = new System.Drawing.Point(eM.X, eM.Y);//settign position of textbox where mouse was clicked
PTB.Name = "textBox1";
PTB.Size = new System.Drawing.Size(100, 20);//size of text box
this.pictureBox1.Controls.Add(PTB);//adding the textbox to the picture box
}
}
Update
I'm sorry I have totally misread your question.
The reason is probably that you seem to be under the impression that Paint programs have TextBoxes sitting on their canvas. They don't. They draw text the same way as they draw lines or circles etc..
Also: Resizing the TextBox will not change the Font size, in case that's is what you want.
Finally: A TextBox will never be transparent, it will always sit on the PictureBox and look, well, like a TextBox. Not like anything in a Paint programm..
But: If you actually do want to resize the TextBox here are a few hints:
You need some way to show the user they are on the right spot by changing the cursor to the right icon
You need to store the mouse down point (attention: it will be inside the TextBox!) and keep track of the increments in the MouseMove. As long as the Button is down all rported e.Location will still be in the TextBox coordinates.
Use the increments (not the absolute values) to increase the size of the TextBox.
It is really hard to get the resizing right on the top and left sides. (Because it will involve moving at the same time), so better don't try!
Do include moving, which is easy and will suffice for all you need.
No, this is a good deal harder than increasing font size. 200-300 lines of code, the last time I did it..
But you may find another somewhat simpler answer; look for "Resize Control with Mouse winforms c#" on Google..
I leave the old answer in place, even if it not what you were looking for..
Old answer about changing font size while placing text:
It is not very hard but you need to get it right; it is basically the same as drawing a Rectangle
with a live preview. You need these things: four events, a Point or two, a Size and a font variable..
The events are:
MouseDown
MouseMove
MouseUp
Paint
You need to store a point for the placement (in the MouseDown event) and a size you update in the MouseMove.
From that size you can calculate the maximum Font size you can fit in the Rectangle.
On MouseUp you finalize things.
In the Paint event you draw string at the down Point with the current Font size.
In the MouseMove you call Invalidate on the PictureBox to trigger the Paint event.
in the MouseMouve you should check the Button to be the left one.
For extra good UI you can also check the keyboard for space and use it to move the DownPoint..
The Click event is rather useless, btw..
Here is a minimal code example to get you started:
Point mDown = Point.Empty;
float fSize = 12f;
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
using (Font font = new Font("Consolas", fSize))
e.Graphics.DrawString("Hello World", font, Brushes.Black, mDown);
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
mDown = e.Location;
pictureBox1.Invalidate();
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
fSize = Math.Abs(e.Y - mDown.Y) / 2f + 1;
pictureBox1.Invalidate();
}
I left out the MouseUp. Here you would store the final state (font, location..) of the drawn string somewhere or persist it by drawing into a Bitmap etc..
I also didn't do a full Rectangle calculation but determined the font size by simply scaling the y-movement down a little.. You could improve with a little pythagoras ;-)
The ability to resize a window is innate behavior, provided by the default window procedure built into Windows. All you have to do is give the control a resizable border. Everything else is for free, you'll get the correct cursor and dragging a corner or edge resizes the control.
using System;
using System.Windows.Forms;
class ResizeableTextBox : TextBox {
protected override CreateParams CreateParams {
get {
var cp = base.CreateParams;
cp.Style |= 0x840000; // Turn on WS_BORDER + WS_THICKFRAME
return cp;
}
}
}
This sample will create the TextBox on the MouseDown event and start resizing, the final size of the TextBox will be where the mouse button is released on the MouseUp event. It will let you create multiple textboxes as well.
I realize this might not be exactly what you want but it might be a start.
private int _textBoxCounter;
private TextBox _textBoxCurrentResizing;
public Form1()
{
InitializeComponent();
pictureBox1.MouseDown += PictureBox1OnMouseDown;
pictureBox1.MouseUp += PictureBox1OnMouseUp;
pictureBox1.MouseMove += PictureBox1OnMouseMove;
}
public Point RelativeMousePosition { get { return PointToClient(MousePosition); } }
private void PictureBox1OnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
ResizeTextBox();
}
private void PictureBox1OnMouseUp(object sender, MouseEventArgs mouseEventArgs)
{
EndResizeTextBox();
}
private void PictureBox1OnMouseDown(object sender, MouseEventArgs mouseEventArgs)
{
var tb = CreateTextBox();
StartResizeTextBox(tb);
}
private TextBox CreateTextBox()
{
var tb = new TextBox
{
Location = RelativeMousePosition,
Size = new Size(100, 20),
Multiline = true,
Name = "textBox" + _textBoxCounter++,
};
pictureBox1.Controls.Add(tb);
return tb;
}
private void StartResizeTextBox(TextBox tb)
{
_textBoxCurrentResizing = tb;
}
private void ResizeTextBox()
{
if (_textBoxCurrentResizing == null) return;
var width = Math.Abs(_textBoxCurrentResizing.Left - RelativeMousePosition.X);
var height = Math.Abs(_textBoxCurrentResizing.Top - RelativeMousePosition.Y);
_textBoxCurrentResizing.Size = new Size(width, height);
}
private void EndResizeTextBox()
{
_textBoxCurrentResizing = null;
}
When I add buttons to a datagridview and then scroll up or down, buttons remain static and do not scroll together with the datagridview.
This is the code I have for the button click event:
private void button4_Click(object sender, EventArgs e)
{
button5.Location = new Point(dgvTurne.HitTest(posicionBoton.X, posicionBoton.Y).ColumnX, dgvTurne.HitTest(posicionBoton.X, posicionBoton.Y).RowY);
Size size = new Size(dgvTurne.SelectedCells[0].Size.Width, 0);
foreach (DataGridViewCell celda in dgvTurne.SelectedCells)
{
size.Height += celda.Size.Height;
}
button5.Size = size;
dgvTurne.Controls.Add(this.Controls["button5"]);
}
In the event above, when a button is clicked, I get the position of the selection in the datagridview by creating an instance of the Point class. Coordenates are taken using HitTest method from datagridview. Then the button is added to datagridview.
The problem is that after button is added, when I scroll up or down, button remains static and do not scroll together with datagridview.
I am trying this: Each button will contain information about appointments. For example: "Appointment with dentist"
I warn you now- getting this working perfectly will most likely ruin your brain. I know it did mine.
Creating a custom DataGridView with all the various events and missing events and everything else caught and handled is FAR too complicated to cover in this answer, but the starting point is here:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetScrollPos(IntPtr hWnd, int nBar);
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0115) // WM_VSCROLL
{
int scrollPos = MyDataGridView.GetScrollPos(this.Handle, 1); // SB_VERT
// get the vertical scroll position
this.button1.Y = desiredY + scrollPos;
// you'll need to calculate where all the buttons should be, then offset them by the scroll pos
}
base.WndProc(ref m);
}
That should cover scrolling with the wheel, grabbing the bar, pressing the arrow buttons next to the bar, using up, down, home and end on the keyboard and any other ways I might have forgotten.
Once that's done though you'll likely find another situation that's not properly covered, or it's not redrawing properly, or any number of other issues and it's back to the Win32 messages again..
Disclaimer:
Edits to the OP invalidated this answer. This will add a button for each row in a single column, but by the new image posted, this may not completely meet the needs of the original question. It won't extend a button over multiple rows like for the time slots imaged above.
See Octopoid's answer.
It sounds like you want a button associated with each row. Manually adding buttons and moving their location is far too much work. What you want to do is add a column of buttons to the dgv. After you've bound your DataSource, add a button column like so:
DataGridViewButtonColumn btnCol = new DataGridViewButtonColumn();
btnCol.HeaderText = "Foo";
btnCol.Name = "Bar";
btnCol.Text = "Click Me";
btnCol.UseColumnTextForButtonValue = true;
this.dataGridView1.Columns.Add(btnCol);
this.dataGridView1.CellClick += new DataGridViewCellEventHandler(col_Click);
Then, to handle the click event for each button, you handle the CellClick event.
public void col_Click(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == this.dataGridView1.Columns["Bar"].Index)
{
// Whatever you want the button to do
}
}
I am creating a Word Add-In and in order to allow dragging something from a custom task pane to the document, I have followed the following guide:
http://msdn.microsoft.com/en-us/library/office/hh780901(v=office.14).aspx
There are some real drawbacks using this approach.
First, the transparent Windows Form (or WPF in my case) that catches the drop event is the size of the Window, not the document, and RangeFromPoint always returns a value, even if we aren't over the document (for instance, if we are over the Ribbon). So once you drag something and this form is created, no matter where you drop, it will be placed in the document. There is no graceful way to cancel once you've started.
My question is:
Has anyone done any work with Drag and Drop in a Word Add In, and found a better way to handle it than the supplied example by Microsoft?
It would be nice to either use the current solution, but know when the user is not dragging over the document or have that transparent window only show over the document area.
Hope you already had your answer.
I got a solution for my own.
So, my requirement:
I have a custom pane, which contains a listbox, each item is a normal string. When I drag an item from the listbox into the document, at a specific location, I want to insert a merge field at that location. The name of the merge field is the text of the item.
It was simple at first, then I got a problem just like you describe in your question.
About the code
So, there is a listbox, you need to handle mouseDown and mouseMove, don't worry about mouseUp.
In mouseDown handler, I record the boundary, if the mouse moves out of that boundary, the drag will start.
Then, in listBox_MouseMoveHandler, I check position of the mouse to start the dragdrop. And I have to use DragDropEffects.Copy for the DoDragDrop method.
DoDragDrop((sender as ListControl).SelectedValue, DragDropEffects.Copy);
With that option, SelectedValue will be inserted at the drop position, and after it is inserted, it will also be selected.
Then, I just check if selection is not empty, and replace the selected text with the merge field. Of course, I collapsed the selection before DoDragDrop. And that is the whole trick.
private int _selectedItemIndex;
private Rectangle dragBoxFromMouseDown;
private void CustomizationForListBox(ListBox listBox)
{
listBox.ItemHeight = 25;
listBox.DrawMode = DrawMode.OwnerDrawFixed;
listBox.DrawItem += ListBox_DrawItem;
listBox.MouseDoubleClick += listBox_MouseDoubleClick;
listBox.MouseMove += listBox_MouseMoveHandler;
listBox.MouseUp += listBox_MouseUp;
listBox.MouseDown += (sender, e) =>
{
// Handle drag/drop
if (e.Button == MouseButtons.Left)
{
_selectedItemIndex = listBox.IndexFromPoint(e.Location);
// Remember the point where the mouse down occurred. The DragSize indicates
// the size that the mouse can move before a drag event should be started.
Size dragSize = SystemInformation.DragSize;
// Create a rectangle using the DragSize, with the mouse position being
// at the center of the rectangle.
dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2),
e.Y - (dragSize.Height / 2)), dragSize);
}
};
}
private void listBox_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
// Reset the drag rectangle when the mouse button is raised.
dragBoxFromMouseDown = Rectangle.Empty;
}
}
private void listBox_MouseMoveHandler(object sender, MouseEventArgs e)
{
// Handle drag and drop
// To check if the Mouse left button is clicked
if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
{
// If the mouse moves outside the rectangle, start the drag.
if (dragBoxFromMouseDown != Rectangle.Empty &&
!dragBoxFromMouseDown.Contains(e.X, e.Y))
{
// Collapse current selection, now we know nothing is selected
Globals.ThisAddIn.Application.Selection.Collapse(WdCollapseDirection.wdCollapseEnd);
//Start Drag Drop
DoDragDrop((sender as ListControl).SelectedValue, DragDropEffects.Copy);
if (_selectedItemIndex != -1)
{
// If the drag/drop was successful, there dropped text must be selected
if (!String.IsNullOrWhiteSpace(Globals.ThisAddIn.Application.Selection.Text))
{
// Replace the selected text with a merge field MergeFieldHelper.InsertSingleMergeField(mergeFieldInfos[_selectedItemIndex].Name);
}
}
}
}
}
I have a RichTextBox inside a panel in a WinForm. I want to hide the vertical scroll bar of RichTextBox and synchronize its scrolling with the vertical scroll bar of the container panel; whenever text overflows in the textbox the scroll bar of the panel should show up and whenever I scroll the scroll bar of the panel the textbox should scroll.
How to achieve this?
As I said in my comment, we have to deal with win32 message and use some hack. I've used all my knowledge about win32 message and control hack/customization in winforms to make this demo for you. It's not complete and of course won't be as perfect as the standard scrollbars of the RichTextBox. The deficiency is if you keep holding down the arrow keys, the scrollbar thumb won't be moved right, however if you press the arrow keys normally the scrollbar thumb will move the caret into view as the standard scrollbars do. You can try the code yourself to see it in action:
public class Form1 : Form {
[DllImport("user32")]
private static extern int SendMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
public Form1(){
InitializeComponent();
//initialize some properties for your richTextBox1 (this should be added as a child of your panel1)
richTextBox1.ScrollBars = RichTextBoxScrollBars.Horizontal;
richTextBox1.BorderStyle = BorderStyle.None;
richTextBox1.Dock = DockStyle.Top;
richTextBox1.MinimumSize = new Size(panel1.Width, panel1.Height - 2);
//initialize some properties for your panel1
panel1.AutoScroll = true;
panel1.BorderStyle = BorderStyle.FixedSingle;
//If the size of panel1 is changed, we have to update the MinimumSize of richTextBox1.
panel1.SizeChanged += (s,e) => {
richTextBox1.MinimumSize = new Size(panel1.Width, panel1.Height - 2);
};
new NativeRichTextBox() { Parent = panel1 }.AssignHandle(richTextBox1.Handle);
hidden.Parent = panel1;
}
//hidden control of panel1 is used to scroll the thumb when the KeyUp of richTextBox1 is raised.
Control hidden = new Control();
//this is used to hook into the message loop of the richTextBox1
public class NativeRichTextBox : NativeWindow
{
public Panel Parent;
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x20a)//WM_MOUSEWHEEL = 0x20a
{
if (Parent != null)
{
SendMessage(Parent.Handle, m.Msg, m.WParam, m.LParam);
return;
}
}
base.WndProc(ref m);
}
}
//ContentsResized event handler of your richTextBox1
private void richTextBox1_ContentsResized(object sender, ContentsResizedEventArgs e)
{
richTextBox1.Height = e.NewRectangle.Height + 5;
}
//KeyUp event handler of your richTextBox1
private void richTextBox1_KeyUp(object sender, KeyEventArgs e)
{
Point p = richTextBox1.GetPositionFromCharIndex(richTextBox1.SelectionStart);
hidden.Top = panel1.PointToClient(richTextBox1.PointToScreen(p)).Y;
hidden.Height = (int) richTextBox1.SelectionFont.Height;
panel1.ScrollControlIntoView(hidden);
}
}
Note: you have to register the event handlers ContentsResized and KeyUp for your richTextBox1 either using code or by designer.
in order to hide the scroll bars you can do
richTextBox1.ScrollBars = RichTextBoxScrollBars.None;
but the problem with it is that it makes the text warp. so you also need
richTextBox1.WordWrap = false;
once you've done that the rest isn't that easy too.
register on the event of the panel scrolling and change the scroll on the rich text box. the problem is is that you can't just change is scrolling offset of the richTextBox, so you can use the Select method to jump to the place you need. you can use the length of the line to know what's the size of the scrollBar need to be.
at the end, it's gonna be a hard time work. good luck