Wordwrap in RTF

Hi,

I have a question concering wordwrapping. I want to implement a rich text box that has wordwrapping set to false.
I looked at the example given in RichText in your samples. I tried setting the wordwrap property to false because I don't want to the text to automatically go to next line when I go over bounds.
When I am in edit-mode, I type so much text and I see both vertical and horizontal scroll bars being displayed. This is desired behavior.
Problem is, when I leave edit-mode and click on another object, the text gets automatically word-wrapped. I tried putting .Wordwrap = false everywhere in event handlers and editor/control code. None of it worked. it still automatically wordwraps the text when leaving the object.
I think problem lies in Paint() or GetImage() methods. Inside they use SendMessage calls.
I couldn't find any solution to my problem.
Please help.
Thanks,
Sido

As a sample class, RichText doesn’t have all the features of the GoText class. When AutoResizes is true (the default), it really means “fixed width, however long it needs to be with auto-wrapping”.



The SendMessage calls are used because (at least at the time this sample was written) there wasn’t support in the .NET RichTextBox to format and render the Richtext to an image in the way we use it.



Let me research this a bit…

OK… this method



private int CalculateWidth(RichTextBox box) {

Point p = box.GetPositionFromCharIndex(box.TextLength);

for (int i = 0; i < box.Lines.Length; i++) {

Point lp = box.GetPositionFromCharIndex(box.Lines.Length);

if (lp.X > p.X) {

p = lp;

}

}

return p.X + 1;

}



(borrowed from: http://social.msdn.microsoft.com/forums/en-US/winforms/thread/70999711-c0f8-43c7-a5d5-574c34b89860/)



and added to Measure in GoDiagram’s RichText:



private SizeF Measure() {

RectangleF drect = this.Bounds;

int width = 999999999; // new - was: Math.Max((int)Math.Ceiling(drect.Width), 10);

int height = 10; // doesn’t matter?

RichTextBox box = GetRichTextBox();

box.Size = new Size(width, height);

box.Rtf = this.Rtf;

width = CalculateWidth(box); // new

box.Size = new Size(width, height); // new



… remainder unchanged



will get you CLOSER to what you want. I still see some behavior where it seems to wrap where it shouldn’t… like the CalculateWidth isn’t working perfectly (when you grab random code off the internet, you get what you pay for).

Thanks Jake.

But it doesn't even get me closer. Nothing changed in behavior. Still behaves as bad on my end. Wrapping all the time.
Sido

I tried changing the Paint() and GetImage() code with same changes proposed for Measure() and it becomes extremely huge and deformed.

Seems like nothing is working.
Please help as fast as you can.
Thank you,
Sido

hmm… works for me, I double checked.



If you’ve made changes to RichText… go back to the version from NodeLinkDemo and insert my changes there. In fact, just try them in NodeLinkDemo first.



I didn’t change Paint or GetImage.

Thanks Jake.

I'm sorry but it still doesn't work for me. I made very small changes to RichText (mainly WordWrap = false and scrollbars).
Here is entire code:
2 files seperated by ************
This is code is very very simple yet it doesn't work.
*******************************************************
namespace Node_Link_Demo { [StructLayout(LayoutKind.Sequential)] internal struct RECT { public int left; public int top; public int right; public int bottom; } [StructLayout(LayoutKind.Sequential)] internal struct CHARRANGE { public int cpMin; public int cpMax; } [StructLayout(LayoutKind.Sequential)] internal struct FORMATRANGE { public IntPtr hdc; public IntPtr hdcTarget; public RECT rc; public RECT rcPage; public CHARRANGE chrg; } [Serializable] public class RichText : GoObject { public RichText() { } public override GoObject CopyObject(GoCopyDictionary env) { RichText newobj = (RichText)base.CopyObject(env); if (newobj != null) { // might as well share myImage, if any, but not any GoControl editor newobj.myEditor = null; } return newobj; } [Category("Appearance"), DefaultValue("")] [Description("The rich text string to be formatted and displayed.")] public virtual String Rtf { get { return myString; } set { String old = myString; if (old != value) { myString = value; Changed(ChangedRtf, 0, old, NullRect, 0, value, NullRect); ResetImage(); if (this.AutoResizes) UpdateSize(); } } } [Category("Appearance")] [Description("The background color for this rich text object.")] public virtual Color BackgroundColor { get { return myBackgroundColor; } set { Color old = myBackgroundColor; if (old != value) { myBackgroundColor = value; Changed(ChangedBackgroundColor, 0, old, NullRect, 0, value, NullRect); ResetImage(); } } } [Category("Behavior"), DefaultValue(true)] [Description("Whether the bounds are recalculated when the rich text string changes.")] public virtual bool AutoResizes { get { return (myInternalTextFlags & flagAutoResizes) != 0; } set { bool old = (myInternalTextFlags & flagAutoResizes) != 0; if (old != value) { if (value) myInternalTextFlags |= flagAutoResizes; else myInternalTextFlags &= ~flagAutoResizes; Changed(ChangedAutoResizes, 0, old, NullRect, 0, value, NullRect); } } } protected override void OnBoundsChanged(RectangleF old) { base.OnBoundsChanged(old); if (this.Size != old.Size) { ResetImage(); } } private void ResetImage() { myImage = null; } [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); private const int WM_USER = 0x400; private const int EM_FORMATRANGE = WM_USER + 57; private const int EM_SETZOOM = WM_USER + 225; internal Bitmap GetImage() { if (myImage == null) { RectangleF drect = this.Bounds; int width = Math.Max((int)Math.Ceiling(drect.Width), 10); int height = Math.Max((int)Math.Ceiling(drect.Height), 10); RichTextBox box = GetRichTextBox(); box.Size = new Size(width, height); box.Rtf = this.Rtf; box.BackColor = this.BackgroundColor; myImage = new Bitmap(width, height); Graphics g = Graphics.FromImage(myImage); g.FillRectangle(new SolidBrush(this.BackgroundColor), 0, 0, width, height); float dpix = g.DpiX; float dpiy = g.DpiY; IntPtr imgdc = g.GetHdc(); FORMATRANGE fr; fr.hdc = imgdc; fr.hdcTarget = imgdc; fr.rc.left = 0; fr.rc.top = 0; fr.rc.right = (int)(width * 1440 / dpix); // convert to TWIPS fr.rc.bottom = (int)(height * 1440 / dpiy); fr.rcPage.left = 0; fr.rcPage.top = 0; fr.rcPage.right = (int)(width * 1440 / dpix); fr.rcPage.bottom = (int)(height * 1440 / dpiy); fr.chrg.cpMin = 0; fr.chrg.cpMax = -1; IntPtr lpar = Marshal.AllocCoTaskMem(Marshal.SizeOf(fr)); Marshal.StructureToPtr(fr, lpar, false); IntPtr result = SendMessage(box.Handle, EM_FORMATRANGE, new IntPtr(1), lpar); // 1=render SendMessage(box.Handle, EM_FORMATRANGE, new IntPtr(0), new IntPtr(0)); // free up cached data Marshal.FreeCoTaskMem(lpar); g.ReleaseHdc(imgdc); g.Dispose(); } return myImage; } public override void Paint(Graphics g, GoView view) { if (view == null) return; RectangleF drect = this.Bounds; if (view.IsPrinting /* && false ??? */) { RichTextBox box = GetRichTextBox(); int width = Math.Max((int)Math.Ceiling(drect.Width), 10); int height = Math.Max((int)Math.Ceiling(drect.Height), 10); box.Size = new Size(width, height); box.Rtf = this.Rtf; box.BackColor = this.BackgroundColor; float dpix = g.DpiX; float dpiy = g.DpiY; Rectangle vrect = view.ConvertDocToView(drect); Point[] points = new Point[] { new Point((int)((vrect.X * 1440)/dpix), (int)((vrect.Y * 1440)/dpiy)), new Point((int)(((vrect.X+vrect.Width) * 1440)/dpix), (int)(((vrect.Y+vrect.Height) * 1440)/dpiy)) }; g.TransformPoints(CoordinateSpace.Device, CoordinateSpace.Page, points); IntPtr imgdc = g.GetHdc(); FORMATRANGE fr; fr.hdc = imgdc; fr.hdcTarget = imgdc; fr.rc.left = (int)points[0].X; fr.rc.top = (int)points[0].Y; fr.rc.right = (int)points[1].X; fr.rc.bottom = (int)points[1].Y; fr.rcPage.left = (int)points[0].X; fr.rcPage.top = (int)points[0].Y; fr.rcPage.right = (int)points[1].X; fr.rcPage.bottom = (int)points[1].Y; fr.chrg.cpMin = 0; fr.chrg.cpMax = -1; IntPtr lpar = Marshal.AllocCoTaskMem(Marshal.SizeOf(fr)); Marshal.StructureToPtr(fr, lpar, false); IntPtr result = SendMessage(box.Handle, EM_FORMATRANGE, new IntPtr(1), lpar); // 1=render SendMessage(box.Handle, EM_FORMATRANGE, new IntPtr(0), new IntPtr(0)); // free up cached data Marshal.FreeCoTaskMem(lpar); g.ReleaseHdc(imgdc); } else { GetImage(); g.DrawImage(myImage, drect); } } private static RichTextBox GetRichTextBox() { if (myRichTextBox == null) { myRichTextBox = new RichTextBox(); myRichTextBox.BorderStyle = BorderStyle.None; myRichTextBox.ScrollBars = RichTextBoxScrollBars.None; } return myRichTextBox; }

private int CalculateWidth(RichTextBox box)
{
Point p = box.GetPositionFromCharIndex(box.TextLength);
for (int i = 0; i < box.Lines.Length; i++)
{
Point lp = box.GetPositionFromCharIndex(box.Lines.Length);
if (lp.X > p.X)
{
p = lp;
}
}
return p.X + 1;
}

private SizeF Measure()
{

/* JAKE MOD
RectangleF drect = this.Bounds;
int width = Math.Max((int)Math.Ceiling(drect.Width), 10);
int height = 10; // doesn’t matter?
RichTextBox box = GetRichTextBox();
box.Size = new Size(width, height);
/
/
JAKE MOD /
RectangleF drect = this.Bounds;
int width = 999999999; // new - was: Math.Max((int)Math.Ceiling(drect.Width), 10);
int height = 10; // doesn’t matter?
RichTextBox box = GetRichTextBox();
box.Size = new Size(width, height);
box.Rtf = this.Rtf;
width = CalculateWidth(box); // new
box.Size = new Size(width, height); // new

/
END JAKE MOD */

box.Rtf = this.Rtf;
Image img = new Bitmap(width, height);
Graphics g = Graphics.FromImage(img);
float dpix = g.DpiX;
float dpiy = g.DpiY;
IntPtr imgdc = g.GetHdc();
FORMATRANGE fr;
fr.hdc = imgdc;
fr.hdcTarget = imgdc;
fr.rc.left = 0;
fr.rc.top = 0;
fr.rc.right = (int)(width * 1440 / dpix); // convert to TWIPS
fr.rc.bottom = 999999999;
fr.rcPage.left = 0;
fr.rcPage.top = 0;
fr.rcPage.right = (int)(width * 1440 / dpix);
fr.rcPage.bottom = 999999999;
fr.chrg.cpMin = 0;
fr.chrg.cpMax = -1;
IntPtr lpar = Marshal.AllocCoTaskMem(Marshal.SizeOf(fr));
Marshal.StructureToPtr(fr, lpar, false);
IntPtr result = SendMessage(box.Handle, EM_FORMATRANGE, new IntPtr(0), lpar); // 0=measure
FORMATRANGE fr2 = (FORMATRANGE)Marshal.PtrToStructure(lpar, typeof(FORMATRANGE));
float newheight = (fr2.rc.bottom - fr2.rc.top) * dpiy / 1440;
if (newheight > 999999)
newheight = 20;
SizeF size = new SizeF(width, newheight);
SendMessage(box.Handle, EM_FORMATRANGE, new IntPtr(0), new IntPtr(0)); // free up cached data
Marshal.FreeCoTaskMem(lpar);
g.ReleaseHdc(imgdc);
g.Dispose();
return size;
}
private void UpdateSize()
{
SizeF newsize = Measure();
SetSizeKeepingLocation(newsize);
}

public class RichTextBoxControl : System.Windows.Forms.RichTextBox, IGoControlObject
{ // nested class
public RichTextBoxControl()
{
this.AllowDrop = false;
this.AutoSize = false;
this.ScrollBars = RichTextBoxScrollBars.ForcedBoth;
this.WordWrap = false;
}
public GoControl GoControl
{
get { return myGoControl; }
set
{
GoControl old = myGoControl;
if (old != value)
{
myGoControl = value;
if (value != null)
{
RichText gorichtext = value.EditedObject as RichText;
if (gorichtext != null)
{
this.Rtf = gorichtext.Rtf;
this.BackColor = gorichtext.BackgroundColor;
}
}
}
}
}
public GoView GoView
{
get { return myGoView; }
set
{
myGoView = value;
this.ZoomFactor = myGoView.DocScale;
}
}
protected override bool ProcessDialogKey(System.Windows.Forms.Keys key)
{
if (key == System.Windows.Forms.Keys.Escape)
{
GoControl ctrl = this.GoControl;
if (ctrl != null)
ctrl.DoEndEdit(this.GoView);
this.GoView.Focus();
return true;
}
else if (key == System.Windows.Forms.Keys.Tab)
{
GoControl ctrl = this.GoControl;
if (ctrl != null)
{
RichText gotext = ctrl.EditedObject as RichText;
if (gotext != null)
{
gotext.Rtf = this.Rtf;
}
ctrl.DoEndEdit(this.GoView);
this.GoView.Focus();
}
return true;
}
else
{
return base.ProcessDialogKey(key);
}
}
protected override void OnLeave(EventArgs evt)
{
GoControl ctrl = this.GoControl;
if (ctrl != null)
{
RichText gotext = ctrl.EditedObject as RichText;
if (gotext != null)
{
gotext.Rtf = this.Rtf;
}
ctrl.DoEndEdit(this.GoView);
}
base.OnLeave(evt);
}
// TextBoxControl state
private GoControl myGoControl = null;
private GoView myGoView = null;
} // end of TextBoxControl

public override bool OnSingleClick(GoInputEventArgs evt, GoView view)
{
if (!CanEdit()) return false;
if (!view.CanEditObjects()) return false;
if (evt.Shift || evt.Control) return false;
DoBeginEdit(view);
return true;
}
public override void DoBeginEdit(GoView view)
{
if (view == null) return;
if (this.Editor != null) return; // already editing
view.StartTransaction();
RemoveSelectionHandles(view.Selection);
myEditor = CreateEditor(view);
this.Editor.EditedObject = this; // associate editor with this text object
view.EditControl = this.Editor; // add GoControl object to view layer
System.Windows.Forms.Control ctrl = this.Editor.GetControl(view); // create Control in view
if (ctrl != null)
{
ctrl.Focus();
}
}
public override GoControl CreateEditor(GoView view)
{
GoControl editor = new GoControl();
editor.ControlType = typeof(RichTextBoxControl);
// make somewhat bigger, for a border
RectangleF rect = this.Bounds;
rect.X -= 2;
rect.Y -= 2;
rect.Width += 4 + SystemInformation.VerticalScrollBarWidth * view.DocScale;
rect.Height += 4;
editor.Bounds = rect;
return editor;
}
public override GoControl Editor
{
get { return myEditor; }
}
public override void DoEndEdit(GoView view)
{
if (this.Editor != null)
{
this.Editor.EditedObject = null; // disassociate
if (view != null)
{
view.EditControl = null; // remove GoControl from view
}
myEditor = null;
if (view != null)
{
view.RaiseObjectEdited(this);
view.FinishTransaction(GoUndoManager.TextEditName);
}
}
}
public override void ChangeValue(GoChangedEventArgs e, bool undo)
{
switch (e.SubHint)
{
case ChangedRtf:
this.Rtf = (String)e.GetValue(undo);
return;
case ChangedBackgroundColor:
this.BackgroundColor = (Color)e.GetValue(undo);
return;
case ChangedAutoResizes:
this.AutoResizes = (bool)e.GetValue(undo);
return;
default:
base.ChangeValue(e, undo);
return;
}
}
public const int ChangedRtf = 1551;
public const int ChangedBackgroundColor = 1552;
public const int ChangedAutoResizes = 1553;
private const int
flagAutoResizes = 0x0100; // if true, reset bounding rect when text changes
private static RichTextBox myRichTextBox = null;
private String myString = “”;
private Color myBackgroundColor = Color.White;
private int myInternalTextFlags = flagAutoResizes;
[NonSerialized]
private Bitmap myImage = null;
[NonSerialized]
private GoControl myEditor = null;
}
public class GeneratorRichText : GoSvgGenerator
{
public GeneratorRichText() { this.TransformerType = typeof(RichText); }
public override void GenerateDefinitions(Object obj)
{
base.GenerateDefinitions(obj);
RichText rt = (RichText)obj;
Image image = rt.GetImage();
if (image != null && image.Width > 0 && image.Height > 0)
this.Writer.DefineObject(image);
}
public override void GenerateBody(Object obj)
{
RichText rt = (RichText)obj;
base.GenerateBody(obj);
Image image = rt.GetImage();
if (image != null && this.Writer.FindShared(image) != null)
{
String id = this.Writer.FindShared(image);
RectangleF r = rt.Bounds;
WriteStartElement(“use”);
float xscale = r.Width / image.Width; // already checked to be non-zero
float yscale = r.Height / image.Height;
WriteAttrVal(“transform”, “translate(” + r.X.ToString() + “,” + r.Y.ToString() + “) scale(” + xscale.ToString() + “,” + yscale.ToString() + “)”);
if (id != null)
WriteAttrVal(“xlink:href”, “#S” + id);
WriteEndElement();
}
}
}
}

********************************************
public partial class Form1 : Form { public Form1() { InitializeComponent(); Node_Link_Demo.RichText temp = new RichText(); temp.Size = new SizeF(300, 300); temp.Position = new PointF(25, 25); temp.Editable = true; temp.BackgroundColor = Color.LightGreen; goView.Document.Add(temp);



goView.Refresh();
}
}

*************************************

By the way, AutoResize is at false. I don’t want to auto-resize. I just want it to look exactly as it is when I’m in edit mode.

Edit mode = no word wrapping
Leave edit mode = automatic word wrapping = why??? how to disable that?
Thanks

If you don’t AutoResize, it’s going to wrap. The editor doesn’t grow while you are typing and making lines longer… but the Bounds have to grow to fit the new text when you edit more.