Программное добавление элементов управления в TableLayoutPanel ведет себя по-разному в зависимости от исходного потока - PullRequest
8 голосов
/ 27 октября 2011

Я создаю проект WinForm, который отображает сообщения от сервера и клиента.Сообщения клиента добавляются с помощью стандартных событий пользовательского интерфейса, таких как Click и KeyPress.Сообщения сервера добавляются через фоновый поток, который прослушивает сообщения и «предупреждает» пользовательский интерфейс, когда они поступают.«Предупреждение» происходит через EventHandler, который затем использует метод Invoke, поскольку мы запрашиваем изменение пользовательского интерфейса из потока, который его не создавал.

Когда сообщения добавляются от клиента, они отображаются по желанию(т. е. правильно подобранного размера, обтекание и изменение размера выполняется правильно).Когда сообщения добавляются из фоновой цепочки через Invoke, только первое сообщение отображается правильно, и даже тогда только изначально.После изменения размера формы обертка перестает работать, и первое сообщение обрезается в зависимости от того, сколько места доступно.Сообщения после первого имеют совершенно неправильный размер, так как кажется, что их высота установлена ​​в 1 пиксель или что-то очень маленькое, так что видны только вершины символов из первой строки.Поскольку я новичок в WinForms, я подозреваю, что я делаю что-то не так, как правильный способ программного добавления элементов управления в пользовательский интерфейс.

И клиент, и фоновый поток вызывают один и тот же метод для добавления элементов управленияк форме;только фон вызывает это через Invoke.Я в недоумении, почему добавленные элементы управления ведут себя по-разному в зависимости от события, из которого они возникли, потому что в любом случае они добавляются точно так же.Моя единственная мысль - я упускаю что-то очевидное, потому что я новичок в WinForms.

В любом случае, достаточно объяснения, вот соответствующий код:

private void AddMessage(string message)
{
    tableLayoutPanel1.SuspendLayout();

    RichTextBox rtb = new RichTextBox();
    rtb.AppendText(message);
    rtb.Multiline = true;
    rtb.WordWrap = true;
    rtb.Dock = DockStyle.Top;

    rtb.ReadOnly = true;
    rtb.BorderStyle = BorderStyle.None;
    rtb.ScrollBars = RichTextBoxScrollBars.None;
    rtb.BackColor = Color.LightBlue;
    rtb.Font = new Font("Tahoma", 8, FontStyle.Italic);
    rtb.Resize += rtb_Resize;
    rtb.MinimumSize = new Size(150, 15);
    rtb.Margin = new Padding(1, 0, 0, 5);
    rtb.Padding = new Padding(3, 3, 3, 3);
    rtb.Height = rtb.GetPositionFromCharIndex(rtb.Text.Length).Y;

    tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.AutoSize));
    tableLayoutPanel1.RowCount = tableLayoutPanel1.RowStyles.Count;
    tableLayoutPanel1.Controls.Add(rtb);
    tableLayoutPanel1.SetColumnSpan(rtb, 2);

    tableLayoutPanel1.ResumeLayout(true);
    tableLayoutPanel1.AutoScrollPosition = new Point(0, tableLayoutPanel1.VerticalScroll.Maximum);
}

private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e)
{
    if (e.KeyChar == 13 && !String.IsNullOrEmpty(richTextBox1.Text))
    {
        AddMessage(richTextBox1.Text);
        richTextBox1.Clear();
    }
}

public void PostMessage(object sender, MessageEventArgs e)
{
    BeginInvoke(new AddMsg(AddMessage), new object[] { e.Message });
}

public delegate void AddMsg(string m);

private void rtb_Resize(object sender, EventArgs e)
{
    var rtb = (RichTextBox) sender;
    rtb.Height = rtb.GetPositionFromCharIndex(rtb.Text.Length).Y;
}

Хорошо, теперь еще немногообъяснение.Сообщения клиента исходят из richTextBox1_KeyPress, а сообщения сервера - из PostMessage (PostMessage назначается EventHandler для фонового потока, когда он получает сообщение).Как вы можете видеть вызов AddMessage, который добавляет RichTextBox с сообщением в TableLayoutPanel.Метод rtb_Resize содержит логику для способа, который я нашел в Интернете, чтобы получить RickTextBox для автоматического размера, так как, очевидно, он не поддерживает это;возможно, это возможный источник моей проблемы.

Я также предоставлю код конструктора ниже для TableLayoutPanel, так как это, кажется, полезно и актуально из других постов, которые я видел здесь.

// 
// tableLayoutPanel1
// 
this.tableLayoutPanel1.AutoScroll = true;
this.tableLayoutPanel1.AutoSize = true;
this.tableLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.tableLayoutPanel1.BackColor = System.Drawing.Color.White;
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50F));
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(1, 10);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(5, 5, 12, 5);
this.tableLayoutPanel1.RowCount = 1;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.Size = new System.Drawing.Size(272, 276);
this.tableLayoutPanel1.TabIndex = 0;

TableLayoutPanel содержит начальную строку, так как конструктор не позволяет иметь ее без строк, но я очищаю все строки в конструкторе формы после стандартного вызова InitializeComponent.

НадеюсьЯ предоставил здесь достаточно информации, чтобы дать людям необходимую информацию, но, пожалуйста, дайте мне знать, если у вас есть какие-либо вопросы или вам нужно просмотреть другие части кода, и я предоставлю их как можно быстрее.Также, если это имеет значение, я использую Visual Studio 2008 и .NET 3.5.

Спасибо.

1 Ответ

10 голосов
/ 10 ноября 2011

Исходя из того, что я могу прочитать, вы правильно добавляете элементы управления в TableLayoutPanel, однако следует помнить, что TableLayoutPanel не предназначен для программного изменения из-за необычного способа обработки его RowStyles как вы обнаружили.

Ответ

При этом причина, по которой элементы управления не переносятся должным образом, заключается в том, что ваш обработчик изменения размера происходит слишком "поздно". Ваш обработчик события rtb_resize, вероятно, вызывается после того, как TableLayoutPanel уже выложился. Теоретически вы могли бы решить эту проблему, заставив TableLayoutPanel повторно выполнить свой макет после того, как все его внутренние элементы управления были размечены. Для этого вам нужно обработать событие SizeChanged TableLayoutPanel, а в обработчике заново настроить размер всех внутренних элементов управления, а затем вызвать tableLayoutPanel1.PerformLayout через BeginInvoke.

Вот пример кода:

bool _layingOut = false;

void tableLayoutPanel1_SizeChanged(object sender, EventArgs e)
{
     if(_layingOut)
          return;

     //TODO: resize your inner controls here

     //this will force the TableLayoutPanel to lay itself out a second time
     _layingOut = true;
     tableLayoutPanel1.BeginInvoke(new Action(() => tableLayoutPanel1.PerformLayout());
     _layingOut = false;
}

Другой путь

Однако , я бы предложил найти другой подход, который не включает TableLayoutPanel. Более идеальным решением в WinForms, если вам нужен специальный макет, является создание собственного класса LayoutEngine . Это позволяет вам написать метод с именем Layout, который WinForms будет вызывать всякий раз, когда необходимо перекомпоновать элемент управления, например, во время изменения размера. Затем вы можете просто указать местоположение и размер каждого дочернего элемента управления любым удобным для вас способом. По сути, это все, что делает TableLayoutPanel.

Подробнее о создании LayoutEngine и удивительно приличном примере MSDN см. http://msdn.microsoft.com/en-us/library/system.windows.forms.layout.layoutengine(v=VS.90).aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...