Почему TextBox.GetFirstCharIndexFromLine всегда возвращает 0? - PullRequest
0 голосов
/ 08 февраля 2019

Я создал подкласс для TextBox и протестировал следующий метод в отдельном тестовом проекте.

internal static int NumberOfPhysicalLinesInTextBox(TextBox tb)
{
    int lc = 0;
    while (tb.GetFirstCharIndexFromLine(lc) != -1)
    {
        ++lc;
    }
    return lc;
}

Код работает хорошо, но в моем подклассе - нет.Вышеуказанный статический метод вызывается только из метода UpdateVisibleScrollBars, который вызывается только в следующих местах:

  • из подкласса 'c-tor
  • OnTextChanged
  • OnFontChanged
  • OnResize

Единственная особенность этого подкласса заключается в том, что он имеет заполнитель, когда пользователь ничего не вводил в TextBox, и этоUpdateVisibleScrollBars.В этом подклассе NumberOfPhysicalLinesInTextBox не возвращается, он зацикливается бесконечно, потому что GetFirstCharIndexFromLine всегда возвращает 0, когда текст является заполнителем: «Введите текст здесь ...».

Обновление: Я не использую Линии, потому что мне нужны физические линии (строки, которые появляются после переноса в Word), поэтому я могу знать, нужно ли мне показать или скрыть вертикальную полосу прокрутки.TextBox установлен с WordWrap = true.Вот официальная документация метода GetFirstCharIndexFromLine.

Обновление 2: Весь код класса ниже (без неанглийских комментариев):

class EnhancedTextBox : TextBox
{
    internal string PlaceholderText = "Enter text here...";

    internal string ActualText
    {
        get
        {
            return PlaceholderShown ? "" : Text;
        }
        set
        {
            if (value == "" || value == null)
            {
                if (Text == PlaceholderText)
                {
                    PlaceholderShown = true;

                    ActualTextChanged?.Invoke(this, EventArgs.Empty);
                }
                else
                {
                    if (!Focused)
                    {
                        BeforeActualTextChanged?.Invoke(this, EventArgs.Empty);

                        ProgrammaticTextChange = true;
                        Text = PlaceholderText;
                        ProgrammaticTextChange = false;

                        PlaceholderShown = true;

                        ActualTextChanged?.Invoke(this, EventArgs.Empty);
                    }
                    else
                    {
                        PlaceholderShown = false;

                        ActualTextChanged?.Invoke(this, EventArgs.Empty);
                    }
                }
            }
            else
            {
                if (Text != value)
                {
                    BeforeActualTextChanged?.Invoke(this, EventArgs.Empty);

                    ProgrammaticTextChange = true;
                    Text = value;
                    ProgrammaticTextChange = false;
                }

                PlaceholderShown = false;

                ActualTextChanged?.Invoke(this, EventArgs.Empty);
            }
        }
    }

    internal Color _PlaceholderForeColor = Utils.GrayByPercent(50);
    internal Color PlaceholderForeColor
    {
        get
        {
            return _PlaceholderForeColor;
        }
        set
        {
            if (_PlaceholderForeColor != value)
            {
                _PlaceholderForeColor = value;
                Invalidate();
            }
        }
    }

    internal Color _NormalForeColor = Color.Empty;
    internal Color NormalForeColor
    {
        get
        {
            return _NormalForeColor;
        }
        set
        {
            if (_NormalForeColor != value)
            {
                _NormalForeColor = value;
                Invalidate();
            }
        }
    }

    internal bool _PlaceholderShown = true;
    internal bool PlaceholderShown
    {
        get
        {
            return _PlaceholderShown;
        }
        set
        {
            if (_PlaceholderShown != value)
            {
                _PlaceholderShown = value;
                ForceUpdatePlaceholderShown(value);
            }
        }
    }

    internal void ForceUpdatePlaceholderShown(bool value)
    {
        ForeColor = value ? PlaceholderForeColor :
                    NormalForeColor;
        Invalidate();
    }

    public EnhancedTextBox() : base()
    {
        NormalForeColor = ForeColor;

        ProgrammaticTextChange = true;
        Text = PlaceholderText;
        ProgrammaticTextChange = false;

        PlaceholderShown = true;
        ForceUpdatePlaceholderShown(true);

        UpdateVisibleScrollBars();
    }

    protected override void OnEnter(EventArgs e)
    {
        HidePlaceholder();

        base.OnEnter(e);
    }

    private void HidePlaceholder()
    {
        if (PlaceholderShown)
        {
            ProgrammaticTextChange = true;
            Text = "";
            ProgrammaticTextChange = false;

            PlaceholderShown = false;
        }
    }

    protected override void OnLeave(EventArgs e)
    {
        ShowPlaceholder();

        base.OnLeave(e);
    }

    private void ShowPlaceholder()
    {
        if (Text == "")
        {
            ProgrammaticTextChange = true;
            Text = PlaceholderText;
            ProgrammaticTextChange = false;

            PlaceholderShown = true;
        }
    }

    internal static int NumberOfPhysicalLinesInTextBox(TextBox tb)
    {
        int lc = 0;
        while (tb.GetFirstCharIndexFromLine(lc) != -1)
        {
            ++lc;
        }
        return lc;
    }

    internal bool ProgrammaticTextChange = false;

    /// <summary>
    /// Do not use this event using handlers. Use ActualTextChanged instead.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnTextChanged(EventArgs e)
    {
        if (ProgrammaticTextChange)
        {
            return;
        }

        ActualText = Text;

        base.OnTextChanged(e);

        UpdateVisibleScrollBars();
    }

    private bool busy = false;
    private void UpdateVisibleScrollBars()
    {
        if (busy) return;
        busy = true;

        bool c1 = false; // chars == Text.Length; // TODO: this not working for WordWrap = false
        bool c2 = NumberOfPhysicalLinesInTextBox(this) > 2;

        if (c1 && c2)
        {
            ScrollBars = ScrollBars.Both;
        }
        else if (c1)
        {
            ScrollBars = ScrollBars.Horizontal;
        }
        else if (c2)
        {
            ScrollBars = ScrollBars.Vertical;
        }
        else
        {
            ScrollBars = ScrollBars.None;
        }
        ScrollToCaret();

        busy = false;
    }

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

        UpdateVisibleScrollBars();
    }

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

        UpdateVisibleScrollBars();
    }

    public event EventHandler ActualTextChanged, BeforeActualTextChanged;
}

Если я заменим bool c2 = NumberOfPhysicalLinesInTextBox(this) > 2; на bool c2 = false;, то не будет бесконечного цикла while, хотя я вижу, что обработчик OnResize часто вызывается при отладке с точкой останова, установленной в строке c2, и многократным нажатиемПродолжить.Затем, если я несколько раз очень быстро нажму на Продолжить, программа запустится и станет доступной для использования.

Обновление 3: Комментирование вызова UpdateVisibleScrollBars внутри обработчика OnResize заставляет все работать.Как изменить видимость полос прокрутки при изменении размера TextBox?

Текущий код:

class EnhancedTextBox : TextBox
{
    internal string _PlaceholderText = "Enter text here...";
    internal string PlaceholderText
    {
        get
        {
            return _PlaceholderText;
        }
        set
        {
            _PlaceholderText = value;
            Invalidate();
        }
    }

    internal Color _PlaceholderForeColor = SystemColors.GrayText;
    public Color PlaceholderForeColor
    {
        get
        {
            return _PlaceholderForeColor;
        }
        set
        {
            _PlaceholderForeColor = value;
            Invalidate();
        }
    }

    [Obsolete]
    internal string ActualText
    {
        get
        {
            return Text;
        }
        set
        {
            if (Text != value)
            {
                Text = value;
            }
        }
    }

    internal Color _NormalForeColor = Color.Empty;
    internal Color NormalForeColor
    {
        get
        {
            return _NormalForeColor;
        }
        set
        {
            if (_NormalForeColor != value)
            {
                _NormalForeColor = value;
                ForeColor = value;
            }
        }
    }

    public EnhancedTextBox() : base()
    {
        NormalForeColor = ForeColor;

        WordWrap = true;
    }

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        if (m.Msg == 0xf)
        {
            if (!this.Focused && string.IsNullOrEmpty(this.Text)
                && !string.IsNullOrEmpty(this.PlaceholderText))
            {
                using (var g = this.CreateGraphics())
                {
                    TextRenderer.DrawText(g, this.PlaceholderText, this.Font,
                        this.ClientRectangle, this.PlaceholderForeColor, this.BackColor,
                         TextFormatFlags.Top | TextFormatFlags.Left);
                }
            }
        }
    }

    internal static int NumberOfPhysicalLinesInTextBox(TextBox tb)
    {
        int lc = 0;
        while (tb.GetFirstCharIndexFromLine(lc) != -1)
        {
            ++lc;
        }
        return lc;
    }

    internal bool ProgrammaticTextChange = false;

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

        ActualTextChanged?.Invoke(this, e);

        UpdateVisibleScrollBars();
    }

    private bool busy = false;
    private Size textSize = Size.Empty;
    private void UpdateVisibleScrollBars()
    {
        if (busy) return;
        busy = true;

        bool c1 = false; // chars == Text.Length; // TODO: this not working for WordWrap = false
        bool c2 = NumberOfPhysicalLinesInTextBox(this) > 2;

        if (c1 && c2)
        {
            ScrollBars = ScrollBars.Both;
        }
        else if (c1)
        {
            ScrollBars = ScrollBars.Horizontal;
        }
        else if (c2)
        {
            ScrollBars = ScrollBars.Vertical;
        }
        else
        {
            ScrollBars = ScrollBars.None;
        }
        ScrollToCaret();

        busy = false;
    }

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

        UpdateVisibleScrollBars();
    }

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

        //UpdateVisibleScrollBars();
    }

    [Obsolete]
    public event EventHandler ActualTextChanged;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...