Хорошо разработанные расширяемые текстовые поля на расширяемой панели - PullRequest
0 голосов
/ 20 января 2020

У меня есть записи в моем приложении. NET WinForms, которое я выкладываю с улучшенными элементами управления TextBox на панелях, когда записи доступны для редактирования, но я установил для TextBoxes значение ReadOnly, когда записи не доступны для редактирования. При нажатии кнопки «Сохранить» на редактируемой записи текст сохраняется в базе данных, а затем отображается как недоступная для редактирования запись (до нажатия кнопки «Изменить»). Пожалуйста, смотрите следующий снимок экрана:

Non-editable and editable record panels

Как вы можете надеяться, первая запись не редактируется, а вторая есть. У меня проблема в том, что я хотел бы, чтобы TextBox увеличивался по высоте, если текст слишком большой, чтобы уместиться. Кажется, что TextBox делает WordWrap, но он показывает только одну строку текста или только первые две. Внизу всегда что-то обрезается.

Я просмотрел несколько других постов на этом сайте, в том числе, в частности, Расширяемый 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 в Расширяемая 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. Кроме того, мне нужно каким-то образом положить sh второй TextBox вниз, когда первый растет. Есть ли у панели хороший способ узнать, когда ExpandoField получает событие OnSizeChanged? Похоже, что в результате роста этой панели остальная часть списка панелей должна быть перерисована в более низких местах. Я не уверен, как заставить этот каскадный эффект работать правильно ...

Я также думаю, что использование таймера кажется неэффективным kluge ...

Я все еще учусь WinForms. Есть какой-то хорошо продуманный способ, которым я могу получить поведение, которое я хочу? Есть ли какое-то событие, которое я могу уловить, когда происходит WordWrap (или когда текст превышает размер TextBox)? Это позволило бы мне изменить размер TextBox. И как TextBox сообщает панели, что она изменилась? Нужно ли вызывать обработчик OnSizeChanged для родительской панели? Нужно ли панели вызывать обработчик OnSizeChanged для родительского списка?

Есть предложения?

1 Ответ

0 голосов
/ 23 января 2020

Я думаю, что у меня есть ответ после 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 и т. Д. c очень просто.) Не стесняйтесь предлагать улучшения.

...