Size RichTextBox according to contents - c#

This code automatically sizes a RichTextBox according to it's contents. I'm having issues, especially with tables. \t may be ignored. I tried a managed solution, now I'm trying platform invoke. Current Output:
[DllImport("gdi32.dll")]
static extern bool GetTextExtentPoint32(IntPtr hdc, string lpString, int cbString, out SIZE lpSize);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetDC(IntPtr hWnd);
[StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SIZE
{
public int cx;
public int cy;
public SIZE(int cx, int cy)
{
this.cx = cx;
this.cy = cy;
}
}
public static void Main()
{
Form form = new Form();
RichTextBox rtfBox = new RichTextBox();
rtfBox.Rtf = #"{\rtf1\ansi\deff0{\fonttbl{\f0\fnil Arial;}}\viewkind4\uc1\trowd\trgaph100\cellx1000\cellx2000\pard\intbl\lang1033\f0\fs20 hi\cell bye\cell\row\intbl one\cell two\cell\row\pard\par}";
rtfBox.ScrollBars = RichTextBoxScrollBars.None;
string sInput = "hi\t bye\t\n";// one\t two\t\n";
SIZE CharSize;
form.Controls.Add(rtfBox);
IntPtr hdc = GetDC(IntPtr.Zero);//Context for entire screen
GetTextExtentPoint32(hdc, sInput, sInput.Length, out CharSize);
rtfBox.Width = CharSize.cx;
rtfBox.Height = CharSize.cy;
form.Visible = false;
form.ShowDialog();
}
(Note, for simplicity this is a console application with a reference to System.Windows.Forms.dll)

Have you looked at the ContentsResized event? Add the following method to be called when the event fires:
private void richTextBox_ContentsResized(object sender, ContentsResizedEventArgs e)
{
var richTextBox = (RichTextBox) sender;
richTextBox.Width = e.NewRectangle.Width;
richTextBox.Height = e.NewRectangle.Height;
}
When the RTF content is changed (using Rtf), the RichTextBox should be resized to match its contents. Make sure you also set the WordWrap property to false.
I've tried it with your table example and it does appear to work (albeit with a little offset, which you could possibly solve by adding a few pixels of width to the adjusted size - not sure why that happens):
P.Brian.Mackey EDIT
This answer worked for me. To clarify, here's the final code including border fix:
public static void Main()
{
string sInput = "hi\t bye\t\n";// one\t two\t\n";
SIZE CharSize;
Form form = new Form();
RichTextBox rtfBox = new RichTextBox();
rtfBox.ContentsResized += (object sender, ContentsResizedEventArgs e) =>
{
var richTextBox = (RichTextBox)sender;
richTextBox.Width = e.NewRectangle.Width;
richTextBox.Height = e.NewRectangle.Height;
rtfBox.Width += rtfBox.Margin.Horizontal + SystemInformation.HorizontalResizeBorderThickness;
};
rtfBox.WordWrap = false;
rtfBox.ScrollBars = RichTextBoxScrollBars.None;
rtfBox.Rtf = #"{\rtf1\ansi\deff0{\fonttbl{\f0\fnil Arial;}}\viewkind4\uc1\trowd\trgaph100\cellx1000\cellx2000\pard\intbl\lang1033\f0\fs20 hi\cell bye\cell\row\intbl one\cell two\cell\row\pard\par}";
form.Controls.Add(rtfBox);
form.ShowDialog();
}

It's much easier to use GetPreferredSize, as described in this answer. Then you don't need to wait for a ContentsResized event,

What about the Height ?
I added
richTextBox.Height += richTextBox.Margin.Vertical +
SystemInformation.VerticalResizeBorderThickness;
at the end.
It also looks like a good candidate for an extension method :
static public class RichTextBoxResizer {
static public void ResizeToContents(this RichTextBox richTextBox, ContentsResizedEventArgs e) {
richTextBox.Width = e.NewRectangle.Width;
richTextBox.Height = e.NewRectangle.Height;
richTextBox.Width += richTextBox.Margin.Horizontal +
SystemInformation.HorizontalResizeBorderThickness +
SystemInformation.HorizontalScrollBarThumbWidth;
richTextBox.Height += richTextBox.Margin.Vertical +
SystemInformation.VerticalResizeBorderThickness;
}
static public void ResizeToContentsHorizontally(this RichTextBox richTextBox, ContentsResizedEventArgs e) {
richTextBox.Width = e.NewRectangle.Width;
richTextBox.Width += richTextBox.Margin.Horizontal +
SystemInformation.HorizontalResizeBorderThickness +
SystemInformation.HorizontalScrollBarThumbWidth;
}
static public void ResizeToContentsVertically(this RichTextBox richTextBox, ContentsResizedEventArgs e) {
richTextBox.Height = e.NewRectangle.Height;
richTextBox.Height += richTextBox.Margin.Vertical +
SystemInformation.VerticalResizeBorderThickness;
}
}
So the event sink looks like :
private void rtfBox_ContentsResized(object sender, ContentsResizedEventArgs e) {
RichTextBox rtb = (RichTextBox)sender;
rtb.ResizeToContents(e);
}

Related

How to draw my own tooltip without shadow?

I am trying to draw my own tooltip. But I cant get rid of the standard shadow.
Its a standard WinForm application, with lots of forms. So therefore is
Application.EnableVisualStyles();
called, and needed, when the application starts. If I comment out this line, it works. I made a minimal WinForm app below. If EnableVisualStyles is commented out, it draws a red rectangle only. When I uncomment it, it draws a red rectangle with a shadow.
Does anyone know ho to solve this? How to have Application.EnableVisualStyles(), and have a tooltip 100% OwnerDrawn, without any standard shadows?
Minimal WinForm app is here:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ToolTipExample
{
public class MainForm : Form
{
[STAThread]
static void Main()
{
// Comment out below line and it works.
Application.EnableVisualStyles();
Application.Run(new MainForm());
}
private ToolTip toolTip;
private Button button;
public MainForm()
{
toolTip = new ToolTip();
toolTip.OwnerDraw = true;
toolTip.Draw += new DrawToolTipEventHandler(toolTip1_Draw);
toolTip.Popup += new PopupEventHandler(toolTip1_Popup);
button = new Button();
button.Location = new Point(25, 25);
button.Text = "Button";
toolTip.SetToolTip(button, "Button tip text");
Controls.AddRange(new Control[] { button });
}
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
e.ToolTipSize = new Size(100, 100);
}
private void toolTip1_Draw(System.Object sender, DrawToolTipEventArgs e)
{
e.Graphics.FillRectangle(new SolidBrush(Color.Red), e.Bounds);
}
}
}
You can get the class style of the ToolTip using GetClassLong and then remove CS_DROPSHADOW style from it and set the class style for the ToolTip again:
//using System.Runtime.InteropServices;
public const int GCL_STYLE = -26;
public const int CS_DROPSHADOW = 0x20000;
[DllImport("user32.dll", EntryPoint = "GetClassLong")]
public static extern int GetClassLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetClassLong")]
public static extern int SetClassLong(IntPtr hWnd, int nIndex, int dwNewLong);
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
e.ToolTipSize = new Size(100, 100);
var hwnd = (IntPtr)typeof(ToolTip).GetProperty("Handle",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance).GetValue(toolTip);
var cs = GetClassLong(hwnd, GCL_STYLE);
if ((cs & CS_DROPSHADOW) == CS_DROPSHADOW)
{
cs = cs & ~CS_DROPSHADOW;
SetClassLong(hwnd, GCL_STYLE, cs);
}
}

Making a text editor with multiple pages using richtextboxes

i am trying to give my text editor multiple pages mode the problem is when the richtextbox reaches the last line it resizes and add a scroll bar which is not what i want, i made a code to transfer the last line of the richtextbox to the one that follows but it's moving the whole text instead and it's kind of sluggish, any help would be appreciated
public partial class Form1 : Form
{
protected static bool GetVisibleScrollbars(Control ctl)
{
int wndStyle = Win32.GetWindowLong(ctl.Handle, Win32.GWL_STYLE);
bool vsVisible = (wndStyle & Win32.WS_VSCROLL) != 0;
return vsVisible;
}
public Form1()
{
InitializeComponent();
}
List<RichTextBox> pages=new List<RichTextBox>();
int currentdocindex = 0;
public void AddPage()
{
RichTextBox B = new RichTextBox();
B.Size = richTextBox1.Size;
panel1.Controls.Add(B);
B.Location = new Point(pages[pages.Count - 1].Location.X, pages[pages.Count - 1].Location.Y + richTextBox1.Height + 20);
pages.Add(B);
B.SelectionIndent = 20;
B.SelectionRightIndent = 20;
B.Enter += new EventHandler(richTextBox_Enter);
}
private void richTextBox_Enter(object sender, EventArgs e)
{
int i = 0;
foreach (RichTextBox box in pages)
{
if (box == (RichTextBox)sender)
{
currentdocindex=i;
break;
}
i++;
}
label1.Text = (currentdocindex + 1).ToString();
}
private void Form1_Load(object sender, EventArgs e)
{
pages.Add(richTextBox1);
richTextBox1.SelectionIndent = 20;
richTextBox1.SelectionRightIndent = 20;
}
private void richTextBox1_Enter(object sender, EventArgs e)
{
int i = 0;
foreach (RichTextBox box in pages)
{
if(box==(RichTextBox)sender)
{
currentdocindex=i;
break;
}
i++;
}
}
bool added = false;
private void timer1_Tick(object sender, EventArgs e)
{
int correntPageIndex = currentdocindex;
if (GetVisibleScrollbars(pages[currentdocindex]))
{
if (!added)
{
AddPage();
added = true;
}
}
else
{
added = false;
}
}
if(GetVisibleScrollbars(pages[correntPageIndex]))
{
string LastLineText = pages[correntPageIndex].Lines[pages[correntPageIndex].Lines.Count() - 1];
int LastLineStartIndex = pages[correntPageIndex].Text.LastIndexOf(LastLineText);
pages[correntPageIndex].SelectionStart = LastLineStartIndex;
pages[correntPageIndex].SelectionLength = pages[correntPageIndex].Text.Length - 1;
LastLineText = pages[correntPageIndex].SelectedRtf;
pages[correntPageIndex].Text = pages[correntPageIndex].Text.Remove(LastLineStartIndex);
pages[correntPageIndex + 1].SelectionStart = 0;
pages[correntPageIndex+1].SelectedRtf = LastLineText;
}
}
}
public class Win32
{
// offset of window style value
public const int GWL_STYLE = -16;
// window style constants for scrollbars
public const int WS_VSCROLL = 0x00200000;
public const int WS_HSCROLL = 0x00100000;
[DllImport("user32.dll", SetLastError = true)]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
}
RichTextBox is a pain for this sort of thing, because to mutate a small portion of text you have to actually select the text first (which it appears you're attempting to do) and ensure the change only affects that text. It's a little nasty on the memory usage, but you might be better served by determining how many characters you want per page and subscribing to the KeyDown Event to determine when you move to a new page. Try to adapt something like this and see if it works better.
public void MyKeyDownHandler(object sender, System.Windows.Forms.KeyEventArgs e)
{
if(this.CurrentPageControl.RTB.Text.Length >= MY_LIMITING_CONSTANT_I_SET)
{
MyPageUserControl mpuc = new MyPageUserControl();
mpuc.RTB.Text = this.CurrentPageControl.RTB.Text.Split(' ').Last();
thePageCollectionIPresumeYouHave.Add(mpuc);
this.CurrentPageControl = thePageCollectionIPresumeYouHave.Last();
mpuc.RTB.Focus();
}
}
Caveat: I did that entirely from memory and without a chance to read all of your code ( I had to skim) because I'm at work.
Another Caveat: I assumed you put your RichTextBoxes in a custom "page" control. If you didn't, I hope my code shows you why you might want to.

RichTextBox doesn't start selection on mouse down when the form has not focus

I'm using WinForms and on my Form I have a RichTextBox. When my form is out of focus but visible and I try to highlight/select text, it does not allow me to until the form or textbox itself has focus.
I've tried:
txtInput.MouseDown += (s, e) => { txtInput.Focus(); }
but to no avail and I can't seem to find anything online about this issue.
When testing with another program like Notepad, it does possess the desired behavior.
MouseDown is too late.
This is a workaround for sure, but may be all you need:
private void txtInput_MouseMove(object sender, MouseEventArgs e)
{
txtInput.Focus();
}
or of course:
txtInput.MouseMove += (s, e) => { txtInput.Focus(); }
As it is it may steal focus from other controls on your form when you move over the textbox. If this is a problem you could prevent it by checking if your program is active using one the of answers here..
You can make the selection manually using MouseDown and MouseMove events. The answer is based on Taw's first idea:
int start = 0;
private void richTextBox1_MouseDown(object sender, MouseEventArgs e)
{
start = richTextBox1.GetTrueIndexPositionFromPoint(e.Location);
richTextBox1.SelectionStart = start;
}
private void richTextBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left))
{
var current = richTextBox1.GetTrueIndexPositionFromPoint(e.Location);
richTextBox1.SelectionStart = Math.Min(current, start);
richTextBox1.SelectionLength = Math.Abs(current - start);
}
}
And here is the codes for GetTrueIndexPositionFromPoint method which has taken from Justin:
public static class RichTextBoxExtensions
{
private const int EM_CHARFROMPOS = 0x00D7;
public static int GetTrueIndexPositionFromPoint(this RichTextBox rtb, Point pt)
{
POINT wpt = new POINT(pt.X, pt.Y);
int index = (int)SendMessage(new HandleRef(rtb, rtb.Handle), EM_CHARFROMPOS, 0, wpt);
return index;
}
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, POINT lParam);
}
This worked for me;
Extend RichTextBox and override WindowProc with this
protected override void WndProc(ref Message m) {
const int WM_MOUSEACTIVATE = 0x21;
if (m.Msg == WM_MOUSEACTIVATE) {
// Take focus to enable click-through behavior for setting selection
this.Focus();
}
// Let the base handle the event.
base.WndProc(ref m);
}
This soulution didn't work for me since my child window had a TextBox that would lose focus when I would hover over the RichTextBox. After some trial and error, I've managed to find another solution:
private const int WM_PARENTNOTIFY = 0x0210;
private Form Form = new Form(); // Your Form here!
private RichTextBox RTB = new RichTextBox(); // Your RichTextBox here!
protected override void WndProc(ref Message m)
{
if ((m.Msg == WM_PARENTNOTIFY) && (Form != null) && (Form.Visible) && (GetChildAtPoint(PointToClient(Cursor.Position)) == RTB))
{
RTB.Focus();
}
base.WndProc(ref m);
}
The WM_PARENTNOTIFY message can be sent multiple times (including when the main Form is being initialized) so it is important to check that that your Form isn't null otherwise you'll receive an exception.

Richtextbox convert a line number to scroll bar position

I'm able now to synchronize my two RichTextBox using this potion of code:
private const int SB_HORZ = 0x0;
private const int SB_VERT = 0x1;
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int SB_THUMBPOSITION = 4;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(int hWnd, int nBar);
[DllImport("user32.dll")]
private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
internal int HScrollPos
{
private get { return GetScrollPos((int)this.Handle, SB_HORZ); }
set
{
SetScrollPos((IntPtr)this.Handle, SB_HORZ, value, true);
PostMessageA((IntPtr)this.Handle, WM_HSCROLL, SB_THUMBPOSITION + 0x10000 * value, 0);
}
}
internal int VScrollPos
{
get { return GetScrollPos((int)this.Handle, SB_VERT); }
set
{
SetScrollPos((IntPtr)this.Handle, SB_VERT, value, true);
PostMessageA((IntPtr)this.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * value, 0);
}
}
I can synchronize the RichTextBoxes while key down ,up and Vscroll event.
Indeed this is not my goal, I want to synchronize my RichTextBoxes basing on the content,
What do I need:
Get the current line form non-selected RichTextBox.
Set Scroll bar position using line number in the other RichTextBox (without losing the focus from the current one).
Get line number using scroll bar position.
Note: you are welcome to ask if you need any more details.
Thanks in advance.
From what I understand, you need to synchronize scrolling on 2 RichTextBoxes based on the line number. Leave me a comment if I misunderstood it.
RichTextBox extended :
public class RichTextBoxEx : RichTextBox
{
// combination of multiple events that may cause focus(caret) to change
public event EventHandler FocusChanged;
public RichTextBoxEx()
{
this.KeyPress += (s, e) => RaiseFocusChanged();
this.KeyDown += (s, e) => RaiseFocusChanged();
this.KeyUp += (s, e) => RaiseFocusChanged();
this.MouseClick += (s, e) => RaiseFocusChanged();
}
private void RaiseFocusChanged()
{
var focusChanged = FocusChanged;
if (focusChanged != null)
{
focusChanged(this, null);
}
}
public int GetFirstSelectedLine()
{
var index = GetFirstCharIndexOfCurrentLine();
return GetLineFromCharIndex(index);
}
public int GetFirstVisibleLine()
{
var index = GetCharIndexFromPosition(new Point(1, 1));
return GetLineFromCharIndex(index);
}
public void ScrollToLine(int line)
{
if (line < 0)
throw new ArgumentOutOfRangeException("line cannot be less than 0");
// save the current selection to be restored later
var selection = new { SelectionStart, SelectionLength };
// select that line and scroll it to
Select(GetFirstCharIndexFromLine(line) + 1, 0);
ScrollToCaret();
// restore selection
Select(selection.SelectionStart, selection.SelectionLength);
}
}
Usage :
void Main()
{
var mainScreenArea = Screen.PrimaryScreen.WorkingArea;
var rich1 = new RichTextBoxEx() { Width = mainScreenArea.Width / 2 - 10, Dock = DockStyle.Left };
var rich2 = new RichTextBoxEx() { Width = mainScreenArea.Width / 2 - 10, Dock = DockStyle.Right };
rich1.LoadFile(__RTF_FILE_0__);
rich2.LoadFile(__RTF_FILE_1__);
// pick one :
// synchronize by focus
rich1.FocusChanged += (s, e) => rich2.ScrollToLine(rich1.GetFirstSelectedLine());
// synchronize by viewbox
// rich1.VScroll += (s, e) => rich2.ScrollToLine(rich1.GetFirstVisibleLine());
var form = new Form();
form.Controls.Add(rich1);
form.Controls.Add(rich2);
form.WindowState = FormWindowState.Maximized;
form.ShowDialog()
}

Custom paint Splitter control in C# WinForms

I am trying to paint the split line that appears when you drag a splitter control:
As you can see from this image, the default splitter is a checkerboard.
...this doesn't work:
public partial class MockForm : Form
{
public MockForm()
{
InitializeComponent();
this.splitter1.Paint += splitter1_Paint;
}
private void splitter1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.Red);
}
}
this just paints the background of the control but not the splitter when it's dragged.
Any ideas?
The answer posted by LarsTech is really good, But the handler flickers are somehow annoying. Instead of showing the control in Form, if you show a Form as splitter handler and show it above the Container of splitter, the flickers will be gone.
HighLight f = new HighLight() { BackColor = Color.Red };
private void splitter1_SplitterMoving(object sender, SplitterEventArgs e)
{
this.splitter1.Parent.Refresh();
f.Location = this.splitter1.Parent.PointToScreen(new Point(e.SplitX, e.SplitY));
f.Size = this.splitter1.Size;
if (!f.Visible)
f.ShowInactiveTopmost();
}
private void splitter1_SplitterMoved(object sender, SplitterEventArgs e)
{
f.Hide();
}
Here is the form which I used as highlight:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class HighLight : Form
{
public HighLight()
{
Opacity = 0;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
StartPosition = FormStartPosition.Manual;
}
protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);
this.Hide();
}
private const int SW_SHOWNOACTIVATE = 4;
private const int HWND_TOPMOST = -1;
private const uint SWP_NOACTIVATE = 0x0010;
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
static extern bool SetWindowPos(int hWnd, int hWndInsertAfter,
int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
public void ShowInactiveTopmost()
{
ShowWindow(this.Handle, SW_SHOWNOACTIVATE);
SetWindowPos(this.Handle.ToInt32(), HWND_TOPMOST,
this.Left, this.Top, this.Width, this.Height,
SWP_NOACTIVATE);
this.Opacity = 1;
}
}
To see a custom splitter which supports transparent handler take a look at this related post. In the other post I created a new splitter control using source codes of original splitter, but changed rendering the highlight:
Change Splitter Highlighting/Resize Line
The old Splitter control uses a private painting method to produce that checkerboard effect, so there isn't any thing you can override to replace that.
You can fake it by dragging your own control in the space of the checkerboard control you see on the screen. This may produce some flicker:
Control draggingControl = new Control { BackColor = Color.Green, Visible = false };
public MockForm() {
InitializeComponent();
this.Controls.Add(draggingControl);
splitter1.SplitterMoving += splitter1_SplitterMoving;
splitter1.SplitterMoved += splitter1_SplitterMoved;
}
void splitter1_SplitterMoving(object sender, SplitterEventArgs e) {
draggingControl.Bounds = new Rectangle(new Point(e.X - (e.X - e.SplitX), 0),
splitter1.Size);
if (!draggingControl.Visible) {
draggingControl.Visible = true;
draggingControl.BringToFront();
}
this.Refresh();
}
void splitter1_SplitterMoved(object sender, SplitterEventArgs e) {
draggingControl.Visible = false;
this.Refresh();
}
The Splitter control was deprecated in favor of the SplitContainer control.

Categories

Resources