6

을 발사하지 않고, 비동기 적으로 다시 포맷하는 방법이 나는하여 RichTextBox으로 윈폼 응용 프로그램이
WinForms RichTextBox: how to perform a formatting on TextChanged?윈폼를 RichTextBox가 : TextChanged 이벤트

에 후속하고, 응용 프로그램 자동 하이라이트의 내용이 상자는 말했다. 서식이 큰 문서의 경우 10 초 이상 소요될 수 있으므로 서식있는 텍스트를 다시 서식 지정하기 위해 BackgroundWorker를 설정했습니다. 그것은 텍스트를 안내하며 이러한 일련의 수행

rtb.Select(start, length); 
rtb.SelectionColor = color; 

가이 일을하는 동안을, UI는 반응 남아있다.

BackgroundWorker는 TextChanged 이벤트에서 시작됩니다. 같은 : 무한 루프 :의 SelectionColor에 대한 모든 할당은 화재 TextChanged 이벤트가 발생,

private void DoBackgroundColorizing(object sender, DoWorkEventArgs e) 
{ 
    do 
    { 
     wantFormat.WaitOne(); 
     wantFormat.Reset(); 

     while (moreToRead()) 
     { 
      rtb.Invoke(new Action<int,int,Color>(this.SetTextColor, 
         new object[] { start, length, color}) ; 
     }     

    } while (true); 
} 

private void SetTextColor(int start, int length, System.Drawing.Color color) 
{ 
    rtb.Select(start, length); 
    rtb.SelectionColor= color; 
} 

을하지만 :

private ManualResetEvent wantFormat = new ManualResetEvent(false); 
private void richTextBox1_TextChanged(object sender, EventArgs e) 
{ 
    xpathDoc = null; 
    nav = null; 
    _lastChangeInText = System.DateTime.Now; 
    if (this.richTextBox1.Text.Length == 0) return; 
    wantFormat.Set(); 
} 

배경 작업자 방법은 다음과 같습니다.

포맷을 수행하는 BackgroundWorker에서 발생한 텍스트 변경과 외부에서 발생한 텍스트 변경을 구분하려면 어떻게합니까?

텍스트 형식 변경과 독립적으로 텍스트 내용 변경을 감지 할 수 있으면이 문제를 해결할 수도 있습니다.

답변

6

나는 BackgroundWorker에의 포맷터 로직을 실행하는 것이었다했다 접근. 형식이 "긴"시간 (1 초 또는 2 초 이상)이 걸릴 것이므로 UI ​​스레드에서이 작업을 수행 할 수 없기 때문에이 옵션을 선택했습니다.

RichTextBox.SelectionColor의 Setter에 대한 BackgroundWorker의 모든 호출이 다시 TextChanged 이벤트를 발생시켜 다시 BG 스레드를 다시 시작합니다. TextChanged 이벤트 내에서 "사용자가 입력 한 이벤트"이벤트를 "프로그램의 텍스트 서식 지정"이벤트와 구별 할 수있는 방법이 없습니다. 그래서 여러분은 그것이 무한한 변화의 진행이 될 것임을 알 수 있습니다.

간단한 방법은 일반적인 방법 (as suggested by Eric)는 텍스트 변경 처리기 내에서 실행하는 동안 처리를 "해제"텍스트 변경 이벤트가

입니다 작동하지 않습니다.물론 텍스트 변경 (SelectionColor 변경)은 배경 스레드에 의해 생성되므로 내 경우에는 작동하지 않습니다. 텍스트 변경 처리기의 범위 내에서 수행되지 않습니다. 따라서 사용자가 시작한 이벤트를 필터링하는 간단한 접근 방식은 배경 스레드가 변경되는 내 경우에는 작동하지 않습니다. 사용자가 시작한 변경

내가 의해 만들어진하여 RichTextBox의 변화에서 내 포맷터 스레드에서 유래하는를 RichTextBox의 변화를 구별하는 방법으로 RichTextBox.Text.Length를 사용하여 시도을 감지하는

다른 시도 사용자. Length가 변경되지 않았다면 변경된 내용은 사용자 코드가 아닌 내 코드에 의해 수행 된 형식 변경이었습니다. 그러나 RichTextBox.Text 속성을 검색하는 것은 비용이 많이 들며 모든 TextChange 이벤트마다이를 수행하면 전체 UI가 부 풀릴 정도로 느려집니다. 이것이 충분히 빠르지 만 사용자가 형식을 변경하기 때문에 일반적인 경우에는 작동하지 않습니다. 또한 사용자 편집은 유형 오버 조작 유형 인 경우 동일한 길이의 텍스트를 생성 할 수 있습니다.

나는 TextChange 이벤트를 잡아서 사용자가 보낸 변경 사항 만 감지하기를 원합니다. 이후로 나는 KeyPress 이벤트와 Paste 이벤트를 사용하도록 앱을 변경했습니다. 결과적으로 서식 변경 (예 : RichTextBox.SelectionColor = Color.Blue)으로 인해 가짜 TextChange 이벤트가 발생하지 않습니다. 그 일을

확인을 수행하는 작업자 스레드를 시그널링

, 나는 서식 변경을 할 수있는 스레드 실행을 가지고있다. 개념적으로이 작업을 수행합니다.

서식을 시작하려면 BG 스레드에 어떻게 알 수 있습니까?

나는 ManualResetEvent을 사용했습니다. KeyPress가 감지되면 keypress 핸들러는 해당 이벤트를 설정합니다 (ON으로 설정). 백그라운드 작업자가 동일한 이벤트를 기다리고 있습니다. 이 기능을 켜면 BG 스레드가이 기능을 해제하고 포맷을 시작합니다.

BG 직원이 인 경우 이미으로 서식을 지정하면 어떻게 될까요? 이 경우 새 키 누르기로 인해 텍스트 상자의 내용이 변경 될 수 있으며 지금까지 수행 된 모든 서식이 유효하지 않을 수 있으므로 서식을 다시 시작해야합니다. 내가 정말 포맷터 스레드가 원하는 것은이 같은 것입니다 :이 논리와

while (forever) 
    wait for the signal to start formatting 
    for each line in the richtextbox 
     format it 
     check if we should stop and restart formatting 
    next 
next 

은으로 ManualResetEvent이 설정되어있는 경우 (점등), 포맷터 스레드, 감지 및 재설정이 (그것을 해제) 포맷을 시작합니다. 그것은 텍스트를 살펴보고 형식을 지정하는 방법을 결정합니다. 포맷터 스레드는 주기적으로 ManualResetEvent를 다시 확인합니다. 형식화 중에 다른 키 누르기 이벤트가 발생하면 이벤트는 다시 신호 상태가됩니다. 포맷터가 다시 신호를 받았다고 생각하면 포맷터가 작동하여 Sisyphus와 같은 텍스트의 시작 부분에서 다시 포맷을 시작합니다. 보다 지능적인 메커니즘은 변경이 발생한 문서의 지점에서 서식을 다시 시작합니다.

은 발병 포맷

또 다른 트위스트를 지연 : 나는 포맷터가 모든 키 누르기와 즉시의 포맷 작업 을 시작하고 싶지 않아요. 인간 유형의 경우 키 입력 사이의 정상적인 일시 중지는 600-700ms 미만입니다. 포맷터가 지연없이 포맷을 시작하면 키 입력간에 포맷을 시작하려고합니다. 꽤 무의미한.

따라서 포매터 로직은 600ms 이상의 키 입력에서 일시 중지가 감지되면 포맷 작업을 시작하기 만합니다. 신호를 수신 한 후 600ms 동안 기다렸다가 끼어 들기가없는 경우 입력이 중지되고 형식이 시작됩니다. 개입이 변경되면 포맷터는 사용자가 입력하는 것으로 간주하여 아무 것도하지 않습니다. 코드에서 :

private System.Threading.ManualResetEvent wantFormat = new System.Threading.ManualResetEvent(false); 

키 누르기 이벤트 다음 colorizer 방법에

private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e) 
{ 
    _lastRtbKeyPress = System.DateTime.Now; 
    wantFormat.Set(); 
} 

, 백그라운드 스레드에서 실행 :

.... 
do 
{ 
    try 
    { 
     wantFormat.WaitOne(); 
     wantFormat.Reset(); 

     // We want a re-format, but let's make sure 
     // the user is no longer typing... 
     if (_lastRtbKeyPress != _originDateTime) 
     { 
      System.Threading.Thread.Sleep(DELAY_IN_MILLISECONDS); 
      System.DateTime now = System.DateTime.Now; 
      var _delta = now - _lastRtbKeyPress; 
      if (_delta < new System.TimeSpan(0, 0, 0, 0, DELAY_IN_MILLISECONDS)) 
       continue; 
     } 

     ...analyze document and apply updates... 

     // during analysis, periodically check for new keypress events: 
     if (wantFormat.WaitOne(0, false)) 
      break; 

사용자 경험은 어떤 형식이 발생되지 않도록 그들이하는 동안 타이핑 중입니다. 입력을 일시 중지하면 서식이 시작됩니다. 입력이 다시 시작되면 서식이 중지되고 다시 대기합니다. 형식 동안 스크롤을 사용하지

하나의 최종 문제가 발생했습니다 변경하십시오를 RichTextBox의 텍스트를 포맷하면를 RichTextBox에 포커스가있는 경우, 선택한 텍스트에 RichTextBox to automatically scroll 원인 RichTextBox.Select()에 대한 호출을 필요로합니다. 사용자가 컨트롤에 집중하고 텍스트를 읽고 편집하는 것과 동시에 서식이 적용되기 때문에 스크롤을 억제하는 방법이 필요했습니다. RTB의 공용 인터페이스를 사용하여 스크롤하는 것을 방지 할 수있는 방법을 찾지 못했습니다. 비록 인터 튜브에서 많은 사람들이 그것에 대해 묻지 만 말입니다. 몇 가지 실험을 한 후 Select32()를 호출 할 때 User32.dll에서 Win32 SendMessage() 호출을 사용하여 Select() 전후에 WM_SETREDRAW을 보내면 Select()를 호출 할 때 RichTextBox의 스크롤을 막을 수 있다는 것을 알았습니다.

내가 스크롤을 방지하기 위해 PInvoke를 의지했기 때문에, 나는 또한 얻거나 텍스트 상자 (EM_GETSEL 또는 EM_SETSEL)의 선택 또는 캐럿을 설정하고, 선택 (EM_SETCHARFORMAT)에 서식을 설정하는 sendMessage 첨부에 PInvoke를 사용했다. pinvoke 접근 방식은 관리 인터페이스를 사용하는 것보다 약간 더 빨랐습니다. 응답

및 일부 계산 오버 헤드를 발생 스크롤을 방지하기 때문에

일괄 업데이트, 나는 문서의 변경까지 일괄로 결정했다. 하나의 인접한 섹션이나 단어를 강조 표시하는 대신 로직은 하이라이트 또는 형식 변경 사항의 목록을 유지합니다. 모든 경우에 종종 한 번에 30 개의 변경 사항이 문서에 적용됩니다. 그런 다음 목록을 지우고 어떤 형식 변경이 필요한지 분석하고 대기열로 돌아갑니다. 이러한 변경 사항을 적용 할 때 문서의 입력이 중단되지 않을 정도로 빠릅니다.

입력 한 내용이 자동으로 서식이 지정되고 색이 구분 된 청크로 표시됩니다. 사용자 키 누르기 사이에 충분한 시간이 지나면 전체 문서가 결국 형식화됩니다. 1k XML 문서의 경우 200ms 미만이고 30k 문서의 경우 2s, 100k 문서의 경우 10s입니다. 사용자가 문서를 편집하면 진행중인 서식이 중단되고 서식이 다시 시작됩니다.


피우!

사용자가 입력하는 동안 richtextbox를 포맷하는 것처럼 보이는 것처럼 놀랍습니다.하지만 텍스트 상자를 잠그지 않았지만 이상한 스크롤 동작을 피할 수있는 더 간단한 것은 없었습니다. 제가 위에서 설명 된 일에 대한


할 수 있습니다 view the code

.

+0

내 솔루션과 의견을 통해 최종 솔루션에 대한 영감을 얻을 수있어서 기쁩니다. –

+0

대단히 고마워, 에릭. – Cheeso

+0

어쩌면 내 답변을 취소 할 수 있습니까? –

3

일반적으로 동일한 이벤트가 다시 발생하게하는 방식으로 이벤트 처리기에서 반응 할 때 이미 이벤트 처리기를 처리 중이라는 플래그를 설정하고 이벤트 처리기의 맨 위에있는 플래그를 확인하십시오 , 즉시 플래그가 설정되어있는 경우 반환 :

bool processing = false; 

TextChanged(EventArgs e) 
{ 
    if (processing) return; 

    try 
    { 
     processing = true; 
     // You probably need to lock the control here briefly in case the user makes a change 
     // Do your processing 
    } 
    finally 
    { 
     processing = false; 
    } 
} 

을 그것이 당신의 처리를 수행하는 동안 컨트롤을 잠글 받아 들일 수 있다면, 당신은 당신의 컨트롤에 KeyDown 이벤트를 확인하고 당신이 그것을받을 때 아마 (처리 플래그를 취소 할 수 또한 잠재적으로 길면 현재 TextChanged 처리를 종료하십시오.

편집 :

전체, 작업 코드

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Windows.Forms; 
using System.ComponentModel; 

namespace BgWorkerDemo 
{ 
    public class FormatRichTextBox : RichTextBox 
    { 
     private bool processing = false; 

     private BackgroundWorker worker = new BackgroundWorker(); 

     public FormatRichTextBox() 
     { 
      worker.DoWork += new DoWorkEventHandler(worker_DoWork); 
     } 

     delegate void SetTextCallback(string text); 
     private void SetText(string text) 
     { 
      Text = text; 
     } 

     delegate string GetTextCallback(); 
     private string GetText() 
     { 
      return Text; 
     } 

     void worker_DoWork(object sender, DoWorkEventArgs e) 
     { 
      try 
      { 
       GetTextCallback gtc = new GetTextCallback(GetText); 
       string text = (string)this.Invoke(gtc, null); 

       StringBuilder sb = new StringBuilder(); 
       for (int i = 0; i < text.Length; i++) 
       { 
        sb.Append(Char.ToUpper(text[i])); 
       } 

       SetTextCallback stc = new SetTextCallback(SetText); 
       this.Invoke(stc, new object[]{ sb.ToString() }); 
      } 
      finally 
      { 
       processing = false; 
      } 
     } 

     protected override void OnTextChanged(EventArgs e) 
     { 
      base.OnTextChanged(e); 

      if (processing) return; 

      if (!worker.IsBusy) 
      { 
       processing = true; 
       worker.RunWorkerAsync(); 
      } 
     } 

     protected override void OnKeyDown(KeyEventArgs e) 
     { 
      if (processing) 
      { 
       BeginInvoke(new MethodInvoker(delegate { this.OnKeyDown(e); })); 
       return; 
      } 

      base.OnKeyDown(e); 
     } 

    } 
} 
+0

형식이 비동기 적으로 완료되면이 기능이 작동하지 않습니다. – Cheeso

+0

예. 형식화 중에 입력을 위해 컨트롤을 잠글 필요가 있습니다 (그렇지 않으면 충돌하는 변경은 메시지 대기열 스레드와 서식 지정 스레드에서 올 수 있습니다). 컨트롤을 잠깐 읽기 전용으로 설정하거나 키 관련 이벤트, 일부 마우스 이벤트 [영역을 선택할 수 있기 때문에] 및 드래그 앤 드롭 이벤트를 허용하면 이벤트를 변경할 수있는 이벤트를 연결하여 잠글 수 있습니다 그). –

+0

내 방법을 사용하여 완전하고 작동하는 코드로 나의 대답을 업데이트했습니다. –

관련 문제