views:

472

answers:

3

I have extended the RichTextBox control to implement much of the missing functionality provided in the native RichEdit class.

I'm running into an issue where if the control is set to wrap to the window or to wrap to printer the horizontal scrollbar appears even though it shouldn't when the control is resized.

Cycling the wordwrap to none and back seems to resolve the problem but can be relatively slow when wrapping to a printer (ie: much too slow to call on every Resize event).

Here's my wordwrap code:

    private void ChangeWordWrap(WordWrap wordWrap)
    {   
        switch (wordWrap)
        {
            case WordWrap.NoWrap:
                {
                    User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_SETTARGETDEVICE, 0, 1);
                    break;                        
                }
            case WordWrap.WrapToPrintDocument:
                {
                    using (Graphics g = PrintDocument.PrinterSettings.CreateMeasurementGraphics(PrintDocument.DefaultPageSettings))
                    {
                        int lParam = ConvertEx.HundredthInchToTwips((PrintDocument.DefaultPageSettings.Bounds.Width - PrintDocument.DefaultPageSettings.Margins.Left - PrintDocument.DefaultPageSettings.Margins.Right));
                        IntPtr wParam = g.GetHdc();
                        User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_SETTARGETDEVICE, wParam, lParam);
                        g.ReleaseHdc();
                    }
                    break;                                                
                }
            case WordWrap.WrapToControl:
                {
                    User32.SendMessage(_RichTextBox.Handle, ApiConstants.EM_SETTARGETDEVICE, 0, 0);
                    break;
                }
        }
    }

Originially I thought the problem might be related to the fact I'm releasing the graphics handle but the issue also occurs when I'm wrapping to the control and no handle is needed.

Adding screenshots:

Correct behavior:

alt text

Incorrect behavior (after resizing the form VERY slightly):

alt text

The wrap to window/no wrap code came from a comment at http://msdn.microsoft.com/en-us/library/bb774282%28VS.85%29.aspx

*Calling ::SendMessage(hwnd, EM_SETTARGETDEVICE, NULL, 0) will wrap text to the window, and ::SendMessage(hwnd, EM_SETTARGETDEVICE, NULL, 1) will disable word-wrap entirely. I'm not sure this is documented anywhere else.*

My p/invoke:

    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

Related Constants:

    public const int WM_USER = 0x400;
    public const int EM_SETTARGETDEVICE = (WM_USER + 72);

Edit:

I've been researching this more and I believe the .NET RichTextBox control is probably sending a SetScrollRange() with incorrect values when the control is resized. Which would make sense since it doesn't necessarily know about the EM_SETTARGETDEVICE message.

I could probably execute SetScrollRange() or something similiar after a resize but my problem is that I have no idea what the correct values would be or how I could go about figuring that out.

Another thing I've noticed is that when the problem is occuring I can resize the control to a size that would actually wrap the text. At this point the scrollbar goes back to a functional state and I can resize until the word wrap is cycled to none and back.

Edit: (Also some more detail in my non-functional answer below)

It looks like EM_GETRECT is not what I actually want since it's size changes when the control size changes. Here's the MSDN description:

EM_GETRECT Message Gets the formatting rectangle of an edit control. The formatting rectangle is the limiting rectangle into which the control draws the text. The limiting rectangle is independent of the size of the edit-control window. You can send this message to either an edit control or a rich edit control.

My initial understanding was that this was the rectangle for the entire text and from it I would be able to determine if the client window was smaller that the formatting rectangle and thus a scrollbar should be shown.

Looks like the real purpose of this formatting rectangle is to make the text display in an area smaller than the Edit Control (ie: margins).

New question:

So, is there a rectangle that is what I thought EM_GETRECT would give me? A rectangle that will tell me how wide the text (including the part that runs off the screen) is? For instance if I had a 400px control and a line of text that was 800px long I want to get the 800px value so I can compare it to control width and show/not show the scrollbar.

Actually I don't care about the length of the off-control text so much as I care about know IF text extends beyond the bounds of the control.

Thanks for all the help so far.

+2  A: 

I haven't tried your code, it isn't easy to run without the P/Invoke declarations. Although your SendMessage's LPARAM argument declaration looks wrong, it should be IntPtr. Passing 0 for WPARAM shouldn't compile either, not sure what you did.

Be sure to check the return value of SendMessage(), it returns IntPtr.Zero if RichEdit isn't happy with your arguments. Passing 0 for the device context handle isn't mentioned in the SDK docs as an acceptable value, you may need to pass an HDC for the screen. Easy to get from Control.CreateGraphics().

I'm also unclear why you wouldn't want a horizontal scrollbar to appear. I would expect you'd see one when you switch to printer WYSIWYG mode.

Hans Passant
The return value of sendmessage is good and the wordwrap switches correctly. The problem manifests when the control is resized after that.As far as the horizontal scrollbar I'm fine if it is there but the problem is that it is there in a non functional state (see screenshots).I also have a p/invoke signature that takes an IntPtr lParam but didn't think it was necessary since I only need to pass an int value into the message I didn't think an IntPtr was necessary.
Cory Charlton
By "non-functional" I mean: The scroll bar is active when the text doesn't go beyond the control. The width of the scroll button and the length of the bar are in a state that indicate the text extends very far beyond the edge of the control. Moving the scroll button moves the "text content" and then resets it's position to the left.
Cory Charlton
A: 

Did you try the EM_SHOWSCROLLBAR Message ?

Or, you could try WM_SETREDRAW (0) before the resize, and WM_SETREDRAW (1), after it's done.

Cheeso
Yes the EM_SCROLLBAR message doesn't change anything (assuming the RichTextBox already sends this when the `ScrollBars` property is set to `Both`
Cory Charlton
And WM_SETREDRAW doesn't change anything either.
Cory Charlton
A: 

I've got something sort of working but it's not what I really want.

What I really want is to make the RichTextBox do what I want it to do. Instead I am letting the RichTextBox do what it wants and then trying to fix it after the fact. It's a chatty solution and sometime results in the scrollbar flashing.

Here's what I'm doing:

SCROLLINFO scrollinfo = new SCROLLINFO();
scrollinfo.cbSize = Marshal.SizeOf(scrollinfo);
scrollinfo.fMask = ApiConstants.SIF_ALL;
bool flag1 = User32.GetScrollInfo(_RichTextBox.Handle, ApiConstants.SB_HORZ, ref scrollinfo);

Logging.LogMessage("Resize - ScrollInfo: Max: " + scrollinfo.nMax + " Min: " + scrollinfo.nMin + " Page: " + scrollinfo.nPage + " Pos: " + scrollinfo.nPos + " TrackPos: " + scrollinfo.nTrackPos + " || RichtTextBox.RightMargin == " + _RichTextBox.RightMargin + " || RichTextBox.WordWrap == " + WordWrap + " / " + _RichTextBox.WordWrap + " Size: " + Size + " ClientRectangle: " + _RichTextBox.ClientSize);

switch (WordWrap)
{
    case WordWrap.WrapToControl:
    {
        if (scrollinfo.nMax > _RichTextBox.ClientSize.Width)
        {
            User32.ShowScrollBar(_RichTextBox.Handle, ApiConstants.SB_HORZ, false);
        }
        break;
    }
    case WordWrap.WrapToPrintDocument:
    {
        if (scrollinfo.nMax > _PrintableWidth)
        {
            User32.ShowScrollBar(_RichTextBox.Handle, ApiConstants.SB_HORZ, false);
        }
        break;
    }
}

The logic in the WordWrap.WrapToControl condition is broken and I'm not sure how to fix it. The problem is that when in the error condition the max scroll is larger than the ClientSize. However when the text actually runs off the screen (consider a left indent) the max scroll is also larger than the ClientSize.

I think I might be able to solve this by using the EM_GETRECT message but I still need to do more testing. It's possible that the EM_GETRECT solution will also work for the WrapToPrintDocument condition but if it does why doesn't the RichTextBox already do this? Really wish I could find out where the RichTextBox was getting it's SCROLLINFO parameters from because then I could possibly make the RichTextBox control do what I want without the need for a second "fix" message.

Note: I don't need cases to show the scrollbar since the RichTextBox handles that part fine after I hide it (sigh)

Edit:

It looks like EM_GETRECT is not what I actually want since it's size changes when the control size changes. Here's the MSDN description:

EM_GETRECT Message Gets the formatting rectangle of an edit control. The formatting rectangle is the limiting rectangle into which the control draws the text. The limiting rectangle is independent of the size of the edit-control window. You can send this message to either an edit control or a rich edit control.

My initial understanding was that this was the rectangle for the entire text and from it I would be able to determine if the client window was smaller that the formatting rectangle and thus a scrollbar should be shown.

Looks like the real purpose of this formatting rectangle is to make the text display in an area smaller than the Edit Control (ie: margins).

New question:

So, is there a rectangle that is what I thought EM_GETRECT would give me? A rectangle that will tell me how wide the text (including the part that runs off the screen) is? For instance if I had a 400px control and a line of text that was 800px long I want to get the 800px value so I can compare it to control width and show/not show the scrollbar.

Thanks for all the help so far.

Cory Charlton