wz

C#: RichTextBox - Blikání při formátování textu

Vydáno: 19.8. 2015
Autor: TomoT Aqarel


Úvodník

Pokud jste se někdy pokusili formátovat text v RichTextBoxu, tak jste pravděpodobně narazili na problém, že obsah komponenty velmi nepříjemně problikává. V tomto článku vám ukáži, jak se tohoto nepříjemného jevu snadno zbavit.

Pointer

Úvodem

Toto je první(a snad ne poslední :) článek ze série Brouci ve winforms, takže bych rád řekl pár slov úvodem. Je tomu nějaký ten pátek, co programuji v jazyce C#. Při návrhu většinou využívám standardní winform komponenty. Až nemile často se setkávám s tím, že komponenty obsahují menší či větší brouky/bugy :).

V následující sérií mini článků bych právě chtěl upozornit na některé nalezené bugy a jejich fixy. Samozřejmě nejsem první, kdo na tyto bugy narazil a tak lze většinou fix celkem snadno vyhledat na internetu. Držím se pravidla nevymýšlet kolo, takže fixy jsou v menší či větší míře přímo přebrané od jiných autorů.

O co jde?

Jak již bylo naznačeno v úvodu, při každé změně formátování textu v RichTextBoxu dojde k překreslení jeho obsahu, což se uživateli bude jevit jako probliknutí. Pokud provádíte rozsáhlejší formátování tak se problikávání stává velmi nepříjemným.

Jak z toho ven?

Jedno z nejčastějších řešení spočívá v zakázání překreslování obsahu RichTextBoxu během formátování textu. Toto lze provést snadno pomocí WIN API funkce SendMessage(hWnd, Msg, wParam, lParam) a poslání zprávy WM_SETREDRAW. Nastaví-li se parametr wParam na 0, pak se překreslování obsahu RichTextBoxu zakáže. Nastavením na 1 se překreslování opět povolí.

Když se podíváte na zdrojový kód, tak vidíte, že je to své podstatě vše. Samotná funkce SendMessage je pouze zabalená ve třídě RichTextBoxExtensions a volaná s požadovanými parametry. Některé z vás určitě mate klíčové slovo this použité u parametru metod BeginUpdate() a EndUpdate(). Vytvořením statické třídy a použití klíčového slova this dáváte najevo, že vytváříte tzv. extension metody.

Pomocí extension metod lze rozšířit již existující třídu o nové metody aniž byste museli zasahovat přímo do samotné třídy. Toto se zejména hodí v případě, kdy chcete přidat nějakou novou funkcionalitu do třídy k níž nemáte zdrojové kódy. Je třeba upozornit, že technologie extension metod je implementována sice v C# jazyce od verze 3.0, ale až v .NET Frameworku 3.5. Potřebujete-li z nějakého důvodu použít nižší verzi, pak můžete využít dědičnosti a uvedené funkce si implementovat do odvozené třídy.

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Utils.Controls.Extensions
{
    /// 
    /// Rich text box with suppression of the flickering during text formatting
    ///  
    public static class RichTextBoxExtensions
    {

        #region WIN API

        private const int WM_SETREDRAW = 0x0b;

        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

        #endregion // WIN API

        #region Public Methods

        /// 
        /// Function stops redrawing content of the RichTextBox
        /// Function should be called before formatting of the RichTextBox
        ///  
        public static void BeginUpdate(this RichTextBox richTextBox)
        {
            SendMessage(richTextBox.Handle, WM_SETREDRAW, (IntPtr)0, IntPtr.Zero);
        }

        /// 
        /// Function resumes redrawing content of the RichTextBox
        /// Function should be called after formatting of the RichTextBox
        ///  
        public static void EndUpdate(this RichTextBox richTextBox)
        {
            SendMessage(richTextBox.Handle, WM_SETREDRAW, (IntPtr)1, IntPtr.Zero);
            richTextBox.Invalidate();
        }

        #endregion // Public Methods
    }
}

A co jako s tím?

Použití je velmi snadné. Prvně musíte uvést namespace třídy RichTextBoxExtensions, v našem případě Utils.Controls.Extensions. V obsluze události TextChanged, která se volá při každé změně textu v RichTextBoxu, je samotný kód formátující text. Nejdříve je nutné zavolat metodu BeginUpdate(), která zakáže překreslování obsahu RichTextBoxu během formátování a pak si uložíme pozici kurzoru. Následující kód nastaví stejný font pro celý text (samozřejmě by v tomto případě bylo vhodnější nastavit font pouze jednou např. při inicializaci). Aby jste si mohli vyzkoušet chování RichTextBox s a bez našeho rozšíření, tak se ve for smyčce nastavuje rozdílná barva (odstíny šedé) jednotlivých znaků a jejich pozadí. Nakonec obnovíme původní pozici kurzoru a povolíme překreslování obsahu RichTextBoxu zavoláním EndUpdate(). Nyní již můžete program spustit a vyzkoušet upravený RichTextBox (celá ukázka je k dispozici ke stažení v download sekci). Vyzkoušejte si v něm napsat několik vět. Nyní zakomentujte volání BeginUpdate() a EndUpdate(), spustě program a uvidíte ten rozdíl ;).

using System;
using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms;

using Utils.Controls.Extensions;

namespace GUI
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void richTextBox1_TextChanged(object sender, EventArgs e)
        {
            // Disable redrawing of the RichTextBox
            richTextBox1.BeginUpdate();

            // Store current cursor position
            int currentPosition = richTextBox1.SelectionStart;

            // Set same font for whole text
            richTextBox1.SelectAll();
            richTextBox1.Font = new Font("TimesNewRoman", 20, FontStyle.Bold);
            richTextBox1.DeselectAll();

            // Set different color and back color of each char
            richTextBox1.SelectionLength = 1;
            for (int i = 0; i < richTextBox1.Text.Length; i++)
            {
                richTextBox1.SelectionStart = i;
                richTextBox1.SelectionColor = Color.FromArgb(i % 256, i % 256, i % 256);
                richTextBox1.SelectionBackColor = Color.FromArgb(255 - i % 256, 255 - i % 256, 255 - i % 256); ;
            }
           
            // Restore cursor position
            richTextBox1.DeselectAll();
            richTextBox1.SelectionStart = currentPosition;

            // Enable redrawing of the RichTextBox
            richTextBox1.EndUpdate();
        }
    }
}
Demonstrační aplikace

Demonstrační aplikace.

 

DOWNLOAD & Odkazy



Na hlavní stránku