5

My earlier post here shows how to obtain the position of the horizontal or vertical scrollbars in a RichTextbox. However, these only work if scrollbars are enabled. If you set scrollbars to None (via richTextBox1.ScrollBars = RichTextBoxScrollBars.None;), then you can still scroll down off the bottom of the box (and off to the right if you disable WordWrap). However, the getVerticalScroll() and getHorizontalScroll() methods (as shown in the link I posted) only return 0 now. They seem to need to 'see' the scrollbars to actually work.

So how can I get (and set) the 'scroll position' whilst scroll bars are disabled?

Community
  • 1
  • 1
Dan W
  • 3,520
  • 7
  • 42
  • 69

2 Answers2

6

The RichTextBox class has several helper methods that lets you discover the position of text inside the window. Which is what you need to use here since you don't have a direct feedback from a scrollbar anymore.

You can find which line is displayed as the first visible line with RichTextBox.GetCharIndexFromPosition() to find the first visible character, followed by GetLineFromCharIndex() to figure out the line that contains that character:

    public static double GetVerticalScrollPos(RichTextBox box) {
        int index = box.GetCharIndexFromPosition(new Point(1, 1));
        int topline = box.GetLineFromCharIndex(index);
        int lines = box.Lines.Length;
        return lines == 0 ? 0 : 100.0 * topline / lines; 
    }

The horizontal scroll position can be found by looking on which line the caret is currently located. And mapping the start of the line back to a position:

    public static double GetHorizontalScrollPos(RichTextBox box) {
        int index = box.SelectionStart;
        int line = box.GetLineFromCharIndex(index);
        int start = box.GetFirstCharIndexFromLine(line);
        Point pos = box.GetPositionFromCharIndex(start);
        return 100 * (1 - pos.X) / box.ClientSize.Width;
    }

You may want to redefine what 100% means, your original question didn't give any guidance.

This question has the aspects of an XY Problem and it smells that what you really want to do is to replace the scrollbars with bigger ones. Don't overlook the silly simple solution, you can just overlay the existing ones by placing yours correctly so they overlap and hide them. You just need to change the border so it isn't so obvious.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks very much for the answer too. I might stick with the other answer for now (I awarded the bounty before I saw yours), unless you add Set() versions as well (as I already have those for the interop version). I can't blame you for mentioning the XY Problem thing. You're right, my question is hinting at a deeper problem, but if you knew what it was, you'd run a mile (which is 1/10th the size of the giant rabbit hole it would open up, thanks to the quirks of the RTB). (clue: vertically syncing two RTBs with changing RTF contents, keeping the cursor in the same position, all without flicker). – Dan W Apr 05 '14 at 14:38
  • That's [trivial to do](http://stackoverflow.com/a/3823319/17034). Always a mistake to hide intent. – Hans Passant Apr 05 '14 at 15:07
  • I think I probably saw that ages ago, but it's only a small component of the overall problem, due to its complexity and RTB's glitchiness. Again I really doubt you'd want to know what's involved. I researched for days, or even weeks on the issue. Maybe there could be a day when I revisit the problem, but for now, user3449857's answer (or yours probably) in this Q work a treat. Even if you did help, it would mean a massive rework of my code. (Sidenote, wow you made that answer too, no wonder your rep is so high!). – Dan W Apr 05 '14 at 16:07
5

I think you will find something interesting here. It's description of RichEdit used behind RichTextBox in .Net.

Also solution of your question:

var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(POINT)));
Marshal.StructureToPtr(new POINT(), ptr, false);
SendMessage(this.richTextBox1.Handle, EM_GETSCROLLPOS, IntPtr.Zero, ptr);
var point = (POINT)Marshal.PtrToStructure(ptr, typeof(POINT));
Marshal.FreeHGlobal(ptr);

Where:

EM_GETSCROLLPOS = WM_USER + 221

And POINT structure from pinvoke.net.

user3449857
  • 672
  • 3
  • 7
  • Thanks this looks promising, but I still get zero after returning point.X or point.Y, even with scrollbars enabled. I changed WM_USER to 1024 - is that okay? I also added the lines: `[DllImport("User32.Dll"]` and `static extern bool SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);`. Is that okay too? – Dan W Apr 04 '14 at 17:15
  • Yes, WM_USER and DllImport is ok. But why do you still get zeros? I have tested this code before posting, works fine for me. Will be great if I can see your code. Also you have to release `ptr` (added code to the answer). – user3449857 Apr 05 '14 at 12:23
  • My bad. I accidentally had `EntryPoint = "PostMessageA"` in the DllImport bit. I thought I removed that earlier, but maybe something else went wrong, and so I undid that removal and then forgot about it later. Anyway, the bounty is yours - thank you very much! :D – Dan W Apr 05 '14 at 14:01
  • I think I accidentally forgot to award you the bounty before (ticking isn't enough obviously - doh!). However it looks like it has been awarded by the community, but I still think I should owe the debt. However I'm offline until around 22nd April and I can't award this new 100 point one (the minimum I can do now) until then, by which time this new bounty would have expired too I think. By that time the minimum I can award would then be 150/200 points. That's a lot for me, so I might pass, but only considering your bounty has been given by the community. Apologies to you and everyone involved. – Dan W Apr 10 '14 at 23:41