Остановка мерцания TextBox во время обновления

В моем приложении WinForms есть текстовое поле, которое я использую в качестве файла журнала. Я добавляю текст без мерцания формы, используя TextBox.AppendText(string);, однако, когда я пытаюсь очистить старый текст (поскольку свойство .Text элемента управления достигает предела .MaxLength), я получаю ужасное мерцание.

Я использую следующий код:

public static void AddTextToConsoleThreadSafe(TextBox textBox, string text)
{
    if (textBox.InvokeRequired)
    {
        textBox.Invoke(new AddTextToConsoleThreadSafeDelegate(AddTextToConsoleThreadSafe), new object[] { textBox, text });
    }
    else
    {
        // Ensure that text is purged from the top of the textbox
        // if the amount of text in the box is approaching the
        // MaxLength property of the control

        if (textBox.Text.Length + text.Length > textBox.MaxLength)
        {
            int cr = textBox.Text.IndexOf("\r\n");
            if (cr > 0)
            {
                textBox.Select(0, cr + 1);
                textBox.SelectedText = string.Empty;
            }
            else
            {
                textBox.Select(0, text.Length);
            }
        }


        // Append the new text, move the caret to the end of the
        // text, and ensure the textbox is scrolled to the bottom

        textBox.AppendText(text);
        textBox.SelectionStart = textBox.Text.Length;
        textBox.ScrollToCaret();
    }
}

Есть ли более аккуратный способ удалить строки текста из верхней части элемента управления, не вызывающий мерцания? Текстовое поле не имеет методов BeginUpdate () / EndUpdate (), которые есть у ListView.

Элемент управления TextBox даже лучше всего подходит для журнала консоли?

Изменить: мерцание TextBox похоже на прокрутку текстового поля вверх (пока я очищаю текст в верхней части элемента управления), а затем он немедленно прокручивается обратно вниз. - все происходит очень быстро, поэтому я просто вижу повторяющееся мерцание.

Я также только что видел этот вопрос , и было предложено использовать ListBox, однако я не знаю, сработает ли это в моей ситуации, поскольку (в большинстве случаев) я получаю текст для ListBox по одному символу за раз.


person Bryan    schedule 11.10.2009    source источник
comment
Возможно, вы захотите изменить это if на while - в случае, если удаления первой строки текста недостаточно, чтобы новый текст поместился в TextBox.   -  person Noam Gal    schedule 11.10.2009
comment
В этом сообщении есть дополнительная информация об этом - stackoverflow.com/questions/1333393/   -  person Chris Vig    schedule 11.10.2009
comment
Фактически, у него есть очень четкое решение, что двойная буферизация не применяется к текстовым полям, поэтому вы должны делать это вручную ...   -  person Vinko Vrsalovic    schedule 12.10.2009
comment
Винко, пожалуйста, оставьте комментарий выше в качестве ответа, чтобы я мог его принять.   -  person Bryan    schedule 12.10.2009
comment
ПРОСТО ИСПОЛЬЗУЙТЕ вместо этого RichTextBox. ЭТО БЕСПЛАТНО. Установите для свойств DetectUrls и ShortcutsEnabled значение FALSE для большей совместимости с TextBox'ами. Это действительно отлично работает.   -  person TomeeNS    schedule 01.06.2016


Ответы (6)


Проблема в том, что вы многократно и быстро добавляете (удаляете) по одному символу за раз. Одним из решений было бы буферизовать символы по мере их добавления и обновлять текстовое поле с большими интервалами (независимо от количества символов), например, каждые 250 миллисекунд.

Для этого потребуются:

  • иметь массив или стек символов, куда они добавляются
  • иметь таймер, который будет вызывать делегата, который на самом деле будет выполнять обновление с символами, хранящимися в стеке

Другой вариант - использовать как каждые 250 мс, так и каждые 100 символов, что бы ни случилось раньше. Но это, вероятно, еще больше усложнит код без какой-либо ощутимой выгоды.

person Vinko Vrsalovic    schedule 11.10.2009
comment
Разве это не только увеличило бы скорость мерцания? - person Bryan; 11.10.2009
comment
Это дало мне идею, которую я могу использовать, пока не найду лучшее решение. Когда элемент управления заполняется, я очищаю около 20% содержимого (я могу позволить себе сбросить 20%, хотя я бы предпочел, чтобы это работало должным образом). Таким образом, вместо того, чтобы постоянно видеть мерцание при заполнении элемента управления, вы вряд ли заметите его, поскольку мерцание может происходить только один раз в час, а не несколько раз в секунду. - person Bryan; 11.10.2009
comment
О менструациях не особо задумывались, признаюсь :) - person Vinko Vrsalovic; 11.10.2009
comment
Несмотря на то, что я согласился с этим вопросом, лучшим ответом, который я нашел, был комментарий Винко в ответ на мой первоначальный вопрос. - person Bryan; 16.11.2009
comment
Люди, пожалуйста, используйте вместо этого RichTextBox. Он не мерцает. Установите для свойств DetectUrls и ShortcutsEnabled значение FALSE для большей совместимости с TextBox'ами. Это действительно отлично работает, иначе вам нужно переопределить сообщение PAINT в TextBox и самостоятельно управлять обрезкой / проверкой областей. - person TomeeNS; 01.06.2016

Ответ Mathijs работает для меня. Я немного изменил его, поэтому могу использовать с любым элементом управления - расширение элемента управления:

namespace System.Windows.Forms
{
    public static class ControlExtensions
    {
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        public static extern bool LockWindowUpdate(IntPtr hWndLock);

        public static void Suspend(this Control control)
        {
            LockWindowUpdate(control.Handle);
        }

        public static void Resume(this Control control)
        {
            LockWindowUpdate(IntPtr.Zero);
        }

    }
}

Итак, все, что вам нужно сделать, это:

myTextBox.Suspend();
// do something here.
myTextBox.Resume();

Работает хорошо. Все мерцание прекращается.

person mkaj    schedule 28.07.2013
comment
К вашему сведению, это не работает, если // do something here. является событием изменения размера. - person Dan Bechard; 01.05.2014
comment
LockWindowUpdate не предназначен для универсального подавления перерисовки окна. Используйте сообщение WM_SETREDRAW, чтобы отключить перерисовку определенного окна. docs.microsoft.com/en-us/ окна / рабочий стол / api / winuser / - person Sverrir Sigmundarson; 06.11.2018

Я нашел решение в Интернете:

    [System.Runtime.InteropServices.DllImport("user32.dll")]

    public static extern bool LockWindowUpdate(IntPtr hWndLock);

    internal void FillTB(TextBox tb, string mes) 
    {
       try
       {
          LockWindowUpdate(tb.Handle);

          // Do your thingies with TextBox tb
       }
       finally
       {
          LockWindowUpdate(IntPtr.Zero);
       }
    }
person Mathijs Beentjes    schedule 02.02.2012
comment
спасибо, у меня тоже работает - на самом деле я использовал расширение mkaj - person Alexa Adrian; 17.01.2014

Вы установили двойную буферизацию в главном окне?

этот код в вашем конструкторе после вызова InitializeComponent добавит двойную буферизацию и, возможно, уменьшит мерцание.

this.SetStyle( ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer,true);

person Alastair Pitts    schedule 11.10.2009
comment
Я пробовал это, к сожалению, без разницы. - person Bryan; 11.10.2009
comment
Это не поможет; TextBox по своей природе мерцает, и лучшее, на что вы можете надеяться, - это уменьшить его. stackoverflow.com/questions/1333393/ - person Roman Starkov; 26.01.2010

Я считаю, что использование SelectedText = text значительно снижает мерцание. Для очень быстрых обновлений мерцание будет локализовано только для нового текста, и вы не получите странного поведения от прыгающей полосы прокрутки.

void UpdateTextBox(string message)
{
   myTextBox.SelectionStart = myTextBox.Text.Length;
   myTextBox.SelectedText = message;
}

Вы также можете использовать это для перезаписи текста, написанного ранее, например, для обновления счетчика или процента загрузки:

void UpdateTextBox(string message, int jumpBack)
{
   myTextBox.SelectionStart = Math.Max(myTextBox.Text.Length - jumpBack, 0);
   myTextBox.SelectionLength = jumpBack;
   myTextBox.SelectedText = message;
}

Кроме этого, похоже, не существует простого метода уменьшения мерцания в .NET TextBox.

person Eric    schedule 02.11.2011

Вы пробовали SuspendLayout () / ResumeLayout () во всех операциях обновления?

Вы также можете вызвать Clear () в текстовом поле, а затем переназначить усеченный текст.

Если вы пытаетесь реализовать какое-то средство просмотра файлов журнала, вы можете вместо этого использовать ListBox.

person codymanix    schedule 11.10.2009
comment
Я пробовал как двойную буферизацию, так и SuspendLayout () / ResumeLayout (), к сожалению, они не имеют никакого значения. Я обновил свой вопрос об использовании ListBox, я не уверен, что он сработает, потому что я (обычно) добавляю по одному символу за раз. - person Bryan; 11.10.2009
comment
вы можете обновить последний элемент в списке, чтобы добавить символ, и если строка заполнена, вы можете добавить новый элемент ListBox. - person codymanix; 11.10.2009
comment
Вы включили все свои операции в SuspendLayout () / ResumeLayout (), включая вызов ScrollToCaret? - person codymanix; 11.10.2009
comment
Да, я поставил приостановку / возобновление в качестве первого и последнего оператора в вызываемой части метода. - person Bryan; 11.10.2009
comment
Кроме того, если подумать еще раз, я не думаю, что список будет работать, поскольку пользователи должны иметь возможность копировать текст в буфер обмена. Им необходимо иметь возможность выделять текст, который они хотят скопировать, с помощью щелчка / перетаскивания. - person Bryan; 11.10.2009
comment
Я также пробовал передать объект формы статическому методу и использовать для этого Suspend / resume. - person Bryan; 11.10.2009
comment
Вы можете реализовать функцию копирования для ListBox с помощью класса ClipBoard, если это поможет. - person codymanix; 12.10.2009