可扩展面板中精心设计的可扩展文本框

问题描述 投票:0回答:1

我在.NET WinForms应用程序中有记录,当记录可编辑时,我在面板上使用增强的TextBox控件进行布局,但是当记录不可编辑时,我将TextBoxes设置为ReadOnly。单击可编辑记录上的保存按钮会将文本保存到数据库,然后将其显示为不可编辑的记录(直到单击了编辑按钮)。请查看以下屏幕抓取:

Non-editable and editable record panels

您可以看到,第一个记录不可编辑,但是第二个记录是可编辑的。我的问题是,如果文本太多而无法容纳,我希望TextBox的高度增加。似乎TextBox正在执行WordWrap,但它要么只显示一行文本,要么仅显示前两行。总会在底部切断某些内容。

我已经查看了该网站上的其他几篇文章,尤其是Expandable WinForms TextBox

这里是面板的一些示例代码:

AutoSize = true;
AutoSizeMode = AutoSizeMode.GrowAndShrink;
        ...
Field1 = new ExpandoField { Multiline = true, WordWrap = true };
Field1.Location = new System.Drawing.Point(42, 3);
if (CanEdit)
{
    Field1.BackColor = System.Drawing.Color.White;
    Field1.TabIndex = 20;
}
else
{
    ((ExpandoField) Field1).ReadOnly = true;
    Field1.ForeColor = System.Drawing.Color.FromArgb(0, 50, 0);
    Field1.BackColor = System.Drawing.Color.Snow;
    Field1.TabIndex = 0;
    Field1.TabStop = false;
}
Field1.Text = Text1;
Field1.Dock = DockStyle.None;
Field1.Size = new System.Drawing.Size(538 - 25, 34);
Field1.MinimumSize = Field1.Size;
Field1.AutoSize = true;
Controls.Add(Field1);

如您所见,我将面板的AutoSize设置为true。 Field2的代码类似于Field1。

ExpandoField基于我从dstran在Expandable WinForms TextBox中的响应中看到的示例代码。这似乎是建议的最完整实施,被标记为该帖子的答案。这是代码:

class ExpandoField : TextBox
{
    private double m_growIndex = 0.0;
    private Timer m_timer;

    public ExpandoField()
    {
        AutoSize = false;
        this.Height = 20;

        // Without the timer, I got a lot of AccessViolationException in the System.Windows.Forms.dll.
        m_timer = new Timer();
        m_timer.Interval = 1;
        m_timer.Enabled = false;
        m_timer.Tick += new EventHandler(m_timer_Tick);

        this.KeyDown += new KeyEventHandler(ExpandoField_KeyDown);
    }

    void ExpandoField_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Modifiers == Keys.Control && e.KeyCode == Keys.A)
            this.SelectAll();
    }

    void m_timer_Tick(object sender, EventArgs e)
    {
        var sz = new System.Drawing.Size(Width, Int32.MaxValue);
        sz = TextRenderer.MeasureText(Text, Font, sz, TextFormatFlags.TextBoxControl);

        m_growIndex = (double)(sz.Width / (double)Width);

        if (m_growIndex > 0)
            Multiline = true;
        else
            Multiline = false;

        int tempHeight = (int) (20 * m_growIndex);

        if (tempHeight <= 20)
            Height = 20;
        else
            Height = tempHeight;

        m_timer.Enabled = false;
    }

    public override sealed bool AutoSize
    {
        get { return base.AutoSize; }
        set { base.AutoSize = value; }
    }

    protected override void OnTextChanged(EventArgs e)
    {
        base.OnTextChanged(e);
        m_timer.Enabled = true;
    }

    protected override void OnFontChanged(EventArgs e)
    {
        base.OnFontChanged(e);
        m_timer.Enabled = true;
    }

    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);
        m_timer.Enabled = true;
    }
}

这显然不是很有效。我已将面板设置为AutoSize,但是它不能适应第二个TextBox。另外,当第一个TextBox增大时,我需要以某种方式向下推第二个TextBox。面板有什么好方法可以知道ExpandoField何时获得OnSizeChanged事件吗?这样看来,该面板的增长将需要使其余面板列表在较低位置重新绘制。我不确定如何使这种级联效果正常工作...

我也认为使用计时器似乎效率很低……

我仍在学习WinForms。是否有一些精心设计的方法可以使我获得想要的行为?发生自动换行时(或文本超出TextBox的大小时),我是否可以捕获某些事件?那将允许我调整TextBox的大小。以及TextBox如何使面板知道它已更改?它是否需要为其父面板调用OnSizeChanged处理函数?面板是否需要为其父列表调用OnSizeChanged处理函数?

有什么建议吗?

c# winforms textbox word-wrap autosize
1个回答
0
投票

我相信,经过3或4次失败尝试后,我有了答案...

class ExpandoField : TextBox
{
    private bool UpdateInProgress = false;
    private static System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(@"\r\n");

    public delegate void CallbackFn();
    CallbackFn VSizeChangedCallback;

    public ExpandoField(CallbackFn VSizeChanged)
    {
        AutoSize = false;
        VSizeChangedCallback = VSizeChanged;

        this.KeyDown += new KeyEventHandler(ExpandoField_KeyDown);
    }

    public void ExpandoField_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Modifiers == Keys.Control && e.KeyCode == Keys.A)
            this.SelectAll();
    }

    public void UpdateSize()
    {
        if (UpdateInProgress == false && Text.Length > 0)
        {
            UpdateInProgress = true;

            int numLines = 0;
            System.Drawing.Size baseSize = new System.Drawing.Size(Width, Int32.MaxValue);
            System.Drawing.Size lineSize = baseSize;  // compiler thinks we need something here...

            // replace CR/LF with single character (paragraph mark '¶')
            string tmpText = rgx.Replace(Text, "\u00B6");

            // split text at paragraph marks
            string[] parts = tmpText.Split(new char[1] { '\u00B6' });

            numLines = parts.Count();

            foreach (string part in parts)
            {
                // if the width of this line is greater than the width of the text box, add needed lines
                lineSize = TextRenderer.MeasureText(part, Font, baseSize, TextFormatFlags.TextBoxControl);
                numLines += (int) Math.Floor(((double) lineSize.Width / (double) Width));
            }

            if (numLines > 1)
                Multiline = true;
            else
                Multiline = false;

            int tempHeight = Margin.Top + (lineSize.Height * numLines) + Margin.Bottom;

            if (tempHeight > Height ||                 // need to grow...
                Height - tempHeight > lineSize.Height) // need to shrink...
            {
                Height = tempHeight;
                VSizeChangedCallback();
            }

            UpdateInProgress = false;
        }
    }

    public override sealed bool AutoSize
    {
        get { return base.AutoSize; }
        set { base.AutoSize = value; }
    }

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

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

    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);
        UpdateSize();
    }
}

注意,在构造函数上,TextBox的此子类现在接受委托回调,以使父类知道TextBox的大小已更改。 (我想我应该已经在这里处理了空值的可能性...)

非常感谢,此解决方案不再需要计时器。

我已经很好地测试了此代码,并且看到了它们的增长和缩小。它尊重MaximumSize,甚至可以处理回车/换行对的存在。 (此代码假定使用Windows;为Linux等修改它应该很简单。)请提出改进​​建议。

© www.soinside.com 2019 - 2024. All rights reserved.