Reputation: 1098
I have to show information to the user that updates every 100 milliseconds, this means the contents of the textbox I show it in are constantly being changed and if they are scrolling through them when they are being changed the update will cause them to loose their scroll bar position
How do I prevent this? I've reduced the effect a lot by adding all text at once.
Current code:
string textboxStr = "";
foreach (string debugItem in debugItems)
{
textboxStr += debugItem + Environment.NewLine;
}
debugForm.Controls[0].Text = textboxStr;
Update 1:
Used the solution provided below and it's not working, the scroll bar still resets to its default position meaning you loose your position and your pointer resets too.
Implementation:
In class:
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool LockWindowUpdate(IntPtr hWndLock);
In function:
var originalPosition = ((TextBox)debugForm.Controls[0]).SelectionStart;
LockWindowUpdate(((TextBox)debugForm.Controls[0]).Handle);
debugForm.Controls[0].Text = textboxStr;
((TextBox)debugForm.Controls[0]).SelectionStart = originalPosition;
((TextBox)debugForm.Controls[0]).ScrollToCaret();
LockWindowUpdate(IntPtr.Zero);
Update 2: Used the 2nd solution provided below and it's not working. The scroll bar still jumps to the top even mid way while scrolling. Then sometimes when you're not even on it the scroll bar will start jumping up and down (Every 100 ms, when it updates the text).
Implementation: In class:
[DllImport("user32.dll")]
static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
private const int SB_VERT = 0x1;
private const int SB_THUMBPOSITION = 4;
private const int WM_VSCROLL = 0x115;
In function:
var currentPosition = GetScrollPos(debugForm.Controls[0].Handle, SB_VERT);
debugForm.Controls[0].Text = textboxStr;
SetScrollPos(debugForm.Controls[0].Handle, SB_VERT, currentPosition, false);
PostMessageA(debugForm.Controls[0].Handle, WM_VSCROLL, SB_THUMBPOSITION + 65535 * currentPosition, 0);
Example text:
Active Scene: Level0
--------------------------------------------------
Settings
Fps: 60
GameSize: {Width=600, Height=600}
FreezeOnFocusLost: False
ShowCursor: False
StaysOnTop: False
EscClose: True
Title:
Debug: True
DebugInterval: 100
--------------------------------------------------
Entities
Entity Name: Player
moveSpeed: 10
jumpSpeed: 8
ID: 0
Type: 0
Gravity: 1
Vspeed: 1
Hspeed: 0
X: 20
Y: 361
Z: 0
Sprites: System.Collections.Generic.List`1[GameEngine.Sprite]
SpriteIndex: 0
SpriteSpeed: 0
FramesSinceChange: 0
CollisionHandlers: System.Collections.Generic.List`1[GameEngine.CollisionHandler]
--------------------------------------------------
Key Events
Key: Left
State: DOWN
Key: Left
State: UP
Key: Right
State: DOWN
Key: Right
State: UP
Key: Up
State: DOWN
Key: Up
State: UP
Upvotes: 0
Views: 2568
Reputation: 21
After searching and never finding a legitimate solution that works with and without focus as well as horizontally and vertically, I stumbled across an API solution that works (at least for my platform - Win7 / .Net4 WinForms).
using System;
using System.Runtime.InteropServices;
namespace WindowsNative
{
/// <summary>
/// Provides scroll commands for things like Multiline Textboxes, UserControls, etc.
/// </summary>
public static class ScrollAPIs
{
[DllImport("user32.dll")]
internal static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
internal static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll")]
internal static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
public enum ScrollbarDirection
{
Horizontal = 0,
Vertical = 1,
}
private enum Messages
{
WM_HSCROLL = 0x0114,
WM_VSCROLL = 0x0115
}
public static int GetScrollPosition(IntPtr hWnd, ScrollbarDirection direction)
{
return GetScrollPos(hWnd, (int)direction);
}
public static void GetScrollPosition(IntPtr hWnd, out int horizontalPosition, out int verticalPosition)
{
horizontalPosition = GetScrollPos(hWnd, (int)ScrollbarDirection.Horizontal);
verticalPosition = GetScrollPos(hWnd, (int)ScrollbarDirection.Vertical);
}
public static void SetScrollPosition(IntPtr hwnd, int hozizontalPosition, int verticalPosition)
{
SetScrollPosition(hwnd, ScrollbarDirection.Horizontal, hozizontalPosition);
SetScrollPosition(hwnd, ScrollbarDirection.Vertical, verticalPosition);
}
public static void SetScrollPosition(IntPtr hwnd, ScrollbarDirection direction, int position)
{
//move the scroll bar
SetScrollPos(hwnd, (int)direction, position, true);
//convert the position to the windows message equivalent
IntPtr msgPosition = new IntPtr((position << 16) + 4);
Messages msg = (direction == ScrollbarDirection.Horizontal) ? Messages.WM_HSCROLL : Messages.WM_VSCROLL;
SendMessage(hwnd, (int)msg, msgPosition, IntPtr.Zero);
}
}
}
With a multiline textbox (tbx_main) use like:
int horzPos, vertPos;
ScrollAPIs.GetScrollPosition(tbx_main.Handle, out horzPos, out vertPos);
//make your changes
//i did something like the following where m_buffer is a string[]
tbx_main.Text = string.Join(Environment.NewLine, m_buffer);
tbx_main.SelectionStart = 0;
tbx_main.SelectionLength = 0;
ScrollAPIs.SetScrollPosition(tbx_main.Handle, horzPos, vertPos);
Upvotes: 2
Reputation: 65077
You can store the SelectionStart
then use ScrollToCaret
after you update. Use LockWindowUpdate
to stop the flicker. Something like this:
[DllImport("user32.dll")]
public static extern bool LockWindowUpdate(IntPtr hWndLock);
var originalPosition = textBox.SelectionStart;
LockWindowUpdate(textBox.Handle);
// ---- do the update here ----
textBox.SelectionStart = originalPosition;
textBox.ScrollToCaret();
LockWindowUpdate(IntPtr.Zero);
As long as the textbox doesn't change size (which it doesn't sound like it will) this should work fine. The other option is to use EM_LINESCROLL to store and set the scrollbar value for the textbox.. but that's more involved.
EDIT:
Since that didn't work.. here's another option.
First, some Windows APIs:
[DllImport("user32.dll")]
static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
..and some values:
private const int SB_VERT = 0x1;
private const int SB_THUMBPOSITION = 4;
private const int WM_VSCROLL = 0x115;
You can now do this:
var currentPosition = GetScrollPos(textBox.Handle, SB_VERT);
// ---- update the text here ----
SetScrollPos(textBox.Handle, SB_VERT, currentPosition, false);
PostMessageA(textBox.Handle, WM_VSCROLL, SB_THUMBPOSITION + 65535 * currentPosition, 0);
This works perfectly for me. The only problem I have is that it jumps around sometimes purely because my randomly generated string of characters has widely varying widths. As long as yours is roughly similar after each update, it should be fine.
Upvotes: 3