TextBox 출력 라인에 제한을 두자 ( C#/.Net 4.0 )
문제상황
TextBox에 원격 장비로 부터 수신되는( COM포트 및 TCP 소켓을 통해) 모든 메시지를 출력해 주는 로그창이 있었다. 아주 많은 로그들이 창에 뿌려지고, 상황에 따라서 하루종일 이 로그메시지를 뿌리고 있을 수 있는 상황이었다. ( 물론 거의 그럴일은 없지만.. ) 메모리가 워낙 넉넉하고 가상메모리를 사용하기 때문에 OutOfMemoryException 예외가 발생할일이야 있겟나 싶어서 별다른 처리를 해주지 않았는데 고객으로 부터 요구사항이 왔다.
"TextBox에 문자열 출력 중에 OutOfMemoryExeption 예외가 발생할 수 있으니, 출력 라인을 제한하고 제한 라인 수를 초과할 경우에는 오래된 라인부터 삭제하도록 해주세요."
요구사항을 들었을 때는 뭐 어려울 게 있겠나 싶어서 알겠다고 그렇게 하겠다고 햇는데 구현을 위해 검토를 조금 해보니 그 당시 내가 가진 지식으로는 해결하기가 만만치 않았다.
결론적으로 내가 작성한 코드이다. 내가 격었던 이슈들 및 해결 방법을 아래에 정리한다.
해결 코드
..........
using System.Runtime.InteropServices;
public partial class SbcTransferMessageView : Form { [DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam); private const int WM_SETREDRAW = 11;
.............
private void _Print(string message) { const int nLimitPrintLines = 1000;
SendMessage(this.Handle, WM_SETREDRAW, false, 0);
try { tbView.AppendText(message);
if (tbView.Lines.Length >nLimitPrintLines) { LinkedList<string> tempLines = new LinkedList<string>(tbView.Lines);
while ((tempLines.Count - nLimitPrintLines) >0) { tempLines.RemoveFirst(); }
tbView.Lines = tempLines.ToArray();
SendMessage(this.Handle, WM_SETREDRAW, true, 0);
tbView.Select(tbView.Text.Length, 0); tbView.ScrollToCaret(); }
} finally { SendMessage(this.Handle, WM_SETREDRAW, true, 0); }
}
................
이슈 1 - TextBox에 출력문자열 추가 시 자동 스크롤 다운되도록 하는 방법.
1. TextBox.AppendText() 함수를 이용하면 자동으로 스크롤 다운되는 기능이 제공된다.
2. TextBox.Text = str; 와 같이 직접 문자열을 치환하려는 경우
TextBox.Select( TextBox.Text.Length, 0 );
TextBox.ScrollToCaret();
와 같이 하면 치환한 텍스트의 끝으로 스크롤 다운 수행한다.
이슈 2 - 자동 스크롤 다운 기능 구현 시 스크롤이 깜빡거리는 문제 해결방법.
결론이야 아래의 Windows API 하나를 이용하면 되는데 찾기가 어렸엇던 부분이다.
WM_SETREDRAW 메시지는 윈도우가 일시적으로 화면 Update를 활성화/비활성화 할 수 있는 기능을 제공한다. 자세한 설명은 MSDN을 참조..
[DllImport("user32.dll")]public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
소스코드를 보면 SendMessage(this.Handle, WM_SETREDRAW, true, 0) 코드가 어디에 삽입되었는가가 중요하다. 잘 보면 스크롤한 후에 그리기를 활성화 해주는 것이 아니라 문자열을 치환해 준후 그리기를 활성화 해 준다. 만약 스크롤을 수행한 후 그리기를 활성화 하면 아예 아무런 문자열이 TextBox에 출력되지 않는다. 아래와 같이 하면 문자열 치환 동작과 스크롤 다운 동작이 한번에 그려짐으로 깜빡임이 제거 된다
tbView.Lines = tempLines.ToArray();
SendMessage(this.Handle, WM_SETREDRAW, true, 0);
tbView.Select(tbView.Text.Length, 0);tbView.ScrollToCaret();
이슈 3 - TextBox에 출력되는 문자열에서 앞에 라인을 잘라는주는 방법.
방법은 위의 소스코드를 보면 어렵지 않게 알 수 있을 거다. 몇 가지 코멘트를 달면 아래 줄이 중요했던 것 같다.
처음에는 TextBox.Lines 프로퍼티를 직접 수정해서 앞에 줄을 삭제해주려했는데 TextBox.Lines는 읽기 전용으로 되어 있어서 수정이 안되었다.
그래서 아래와 같이 수정을 위해 LinkedList를 별도로 생성해 주었다. 이렇게 하면 O(n) ( n = TextBox.Lines.Length ) 연산이 필요하지만 어쩔 수 없는 것 같다.
LinkedList<string> tempLines = new LinkedList<string>(tbView.Lines);
그리고 아래와 같이 필요한 라인만큼 제거를 해주는데 여기서 중요한 점은 LinkedList를 사용했다는 것이다. 일반 List 제너릭 클래스를 사용하면 내부적으로 배열을 사용하기 때문에 요소 삭제시 또 다시 O(n)의 연산을 필요로 한다. 반면 LinkedList를 사용하면 O(1)의 연산으로 삭제 연산이 처리가능하다.
tempLines.RemoveFirst();