C# Keeping the caret in position while setting textCreated by maxtingle on Sat 5th Apr 2014 in category C#. 1769 Views | Tags: C#, pinvoking, textbox, caret, richtextboxWhile writing an upcoming project, I needed to debug information. Now the .Net just in time debugger is great, but it's not convenient for checking values as they change in live. So I wanted to make a debugger, the information it displays updates every 100ms. This caused huge issues, when you set the text property of a TextBox or RichTextBox the caret position is reset and I was displaying a ton of information, so scrolling back to the top of it isn't acceptable.
I did a bit of research into this, there were a few "solutions" but they often didn't work, one was to freeze the drawing of the textbox while the text was set, then resume it afterwords. This still ran into the same issue, but delayed the time before the caret was reset, however since the redraw was also delayed, you were just viewing old info for longer. Useless.
The next solution involved the SelectionStart and SelectionLength properties on RichTextBox, you can set the position of SelectionStart and set the SelectionLength to 0 then call ScrollToCaret to make the scroll bar move to the position. Theoretically you could just store the caret position, set the text and then set the caret position again. However, SelectionStart is actually the position of the highlighted text, meaning if you scroll down using the scroll bar, its value will not be updated. And lets be honest, you're not gonna be highlighting text. So this solution, was also useless.
Now it's time for my solution, the idea is to use pinvoking to get the scroll position and set the scroll position. This is the part that I myself had come up with, the code is as follows:
Class definition:
[DllImport("User32.dll")]
internal extern static int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("User32.dll")]
internal extern static int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
int scrollPos = GetScrollPos(debugForm.Controls[0].Handle, 1);
debugForm.Controls[0].Text = textboxStr;
SetScrollPos(debugForm.Controls[0].Handle, 0x1, scrollPos, true);
However, it had some serious problems. It only set the caret position, as in only the scroll bar moves. The content that is normally effected by the caret position, still resets and displays the content as if the caret position was 0. Making this solution, useless.
And now the final solution, the only way I was able to get this to actually work. My solution was fine, but it was missing one part, PostMessage. PostMessage adds a message to the message queue which will result in the control being updated. With this added, the code looks like so
[DllImport("User32.dll")]
internal extern static int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("User32.dll")]
internal extern static int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
int scrollPos = GetScrollPos(debugForm.Controls[0].Handle, 1);
debugForm.Controls[0].Text = textboxStr;
SetScrollPos(debugForm.Controls[0].Handle, 0x1, scrollPos, true);
PostMessage(debugForm.Controls[0].Handle, 0x115, 4 + 0x10000 * scrollPos, 0);
This code works brilliantly! One would say if it worked brilliantly. There is but a single issue left, the redraw causes a rather large amount of flickering most of it is perfectly fine because you can't really see it. But every so often the content flickers up as if the caret was set to 0. It's a small issue so you can probably get away with using this code and ignoring it. If I ever do solve it, I'll update this post.
Sign up to comment!