为什么TextBox.GetFirstCharIndexFromLine总是返回0?

时间:2019-02-08 14:39:42

标签: c# winforms textbox

我为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:“在此处输入文本...”。

更新:我不使用“行”,因为我需要物理行(“换行”后产生的行),因此我可以知道是否需要显示或隐藏垂直滚动条。文本框设置为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循环,尽管我发现在调试中经常在c2行上放置一个断点来调用OnResize处理程序,然后重复单击继续。然后,如果我按几次快速继续键,该程序将启动并可用。

更新3:注释掉UpdateVisibleScrollBars处理程序中的OnResize调用,一切正常。调整文本框大小后,如何更改滚动条的可见性?

当前代码:

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;
}

0 个答案:

没有答案