Синхронизация TableLayoutPanels - PullRequest
3 голосов
/ 12 ноября 2011

У меня есть несколько таблиц TableLayoutPanels, каждая из которых отображает категорию информации имя / значение в двух столбцах - один с информационными метками, а другой с метками данных.

В каждом из них я установил первый столбец для автоматического изменения размера и выровнял по правому краю все метки, что прекрасно работает. Однако он работает отдельно для каждой TableLayoutPanel (очевидно) и выглядит примерно так:

TableLayoutPanel 1:

+--------+--------+
| Name 1 | Data 1 |
+--------+--------+
| Name 2 | Data 2 |
+--------+--------+
| Name 3 | Data 3 |
+--------+--------+

TableLayoutPanel 2:

+------------------+--------+
|      Long Name 1 | Data 1 |
+------------------+--------+
| Very Long Name 2 | Data 2 |
+------------------+--------+
|      Long Name 3 | Data 3 |
+------------------+--------+

Я ищу способ учитывать все меток имен при автонастройке всех первых столбцов, поэтому это выглядит так:

TableLayoutPanel 1:

+------------------+--------+
|           Name 1 | Data 1 |
+------------------+--------+
|           Name 2 | Data 2 |
+------------------+--------+
|           Name 3 | Data 3 |
+------------------+--------+

TableLayoutPanel 2:

+------------------+--------+
|      Long Name 1 | Data 1 |
+------------------+--------+
| Very Long Name 2 | Data 2 |
+------------------+--------+
|      Long Name 3 | Data 3 |
+------------------+--------+

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

Я пытался добиться этого, переопределяя элементы управления контейнера OnLayout (), устанавливая все первые столбцы TableLayoutPanels для автоматического изменения размера, получая всю их ширину, находя максимум, а затем устанавливая все свои первые столбцы на фиксированный размер наибольшей ширины. Это работает, но выглядит ужасно каждый раз, когда происходит макет, когда все столбцы переходят к автоматическому размеру и затем возвращаются к фиксированному размеру.

Я предполагаю, что мне придется подключить ControlAdded и ControlRemoved для каждой таблицы, а затем SizeChanged для каждого дочернего элемента управления, чтобы узнать, когда изменился размер любого дочернего элемента управления, и затем каким-то образом вручную установить ширину столбца, но я не уверен, как надежно получить правильную ширину.

Я попробовал вариант первого метода - используя GetPreferredSize () для всех элементов управления в первых столбцах, чтобы попытаться найти наибольшую ширину, а затем установил для всех первых столбцов фиксированный размер, но, похоже, он возвращает ширину которые были немного малы. Должен ли я применить дополнительный интервал?

Кто-нибудь знает какой-либо способ попросить TableLayoutPanel выполнить автоматические вычисления без фактического применения их визуально? Или, может, лгать столам, чтобы «притвориться», что есть элемент управления определенной ширины, просто так, чтобы он учитывал это? Я не могу добавить фактические элементы управления, так как тогда они захотят выделить больше ячеек для них. Я попытался посмотреть на источник с помощью ILSpy, но это не очень красиво. Кажется, что большая часть работы выполняется классом TableLayout, который, конечно, является внутренним, и я не мог следить за тем, что он делал.

Спасибо за любые идеи ...

Ответы [ 3 ]

3 голосов
/ 13 ноября 2011

Вы можете использовать Graphics.Measurestring , чтобы определить длину в пикселях без фактического ее рисования. Есть незначительные недостатки с , так что вы можете подумать о добавлении или удалении некоторых отступов. После одного или двух тестов вы можете подойти довольно близко. Это так хорошо, как я знаю, и это не связано с тем, что текст находится на ярлыке.

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

0 голосов
/ 14 ноября 2011

Оказалось, что ширина, возвращаемая GetPreferredSize () , была полезной, просто «слишком поздно»;Я определял правильный размер и возвращал его в коде, который был вызван из метода OnLayout () TableLayoutPanels, и установка ширины столбца там не действует до макета next .

У меня было решение, в котором использовался отдельный компонент, реализующий IExtenderProvider, который можно было использовать для объединения таблиц, но из-за описанной выше проблемы он всегда отставал от изменений элемента управления.Даже подключив SizeChanged ко всем дочерним элементам управления, TableLayoutPanel сначала получает событие и запускает макет.

Так что я не мог найти другого выхода, кроме как переопределить сам процесс макета.Я попытался создать LayoutEngine, который выполнил необходимые вычисления, изменил размеры столбцов, затем делегировал реальную работу над макетом старому механизму макетов, но, к сожалению, Control.LayoutEngine доступен только для чтения, а TableLayoutPanel даже не использует вспомогательное поле, он простовозвращает другой объект напрямую, поэтому я даже не смог обойти его, используя отражение для назначения частного вспомогательного поля.

В конце мне пришлось прибегнуть к созданию подкласса элемента управления, чтобы переопределить OnLayout ().Вот результат:

public class SynchronizedTableLayoutPanel : TableLayoutPanel
    {
    /// <summary>
    /// Specifies a key used to group <see cref="SynchronizedTableLayoutPanel"/>s together.
    /// </summary>
    public String SynchronizationKey
        {
        get { return _SynchronizationKey; }
        set
            {
            if (!String.IsNullOrEmpty(_SynchronizationKey))
                RemoveSyncTarget(this);

            _SynchronizationKey = value;

            if (!String.IsNullOrEmpty(value))
                AddSyncTarget(this);
            }
        } private String _SynchronizationKey;

    #region OnLayout(), Recalculate()

    protected override void OnLayout(LayoutEventArgs levent)
        {
        if (ColumnCount > 0 && !String.IsNullOrEmpty(SynchronizationKey))
            {
            Recalculate();
            ColumnStyles[0] = new ColumnStyle(SizeType.Absolute, GetMaxWidth(SynchronizationKey));
            }

        base.OnLayout(levent);
        }

    public void Recalculate()
        {
        var LargestWidth = Enumerable.Range(0, RowCount)
            .Select(i => GetControlFromPosition(0, i))
            .Where(c => c != null)
            .Select(c => (Int32?)((c.AutoSize ? c.GetPreferredSize(new Size(Width, 0)).Width : c.Width)+ c.Margin.Horizontal))
            .Max();

        SetMaxWidth(this, LargestWidth.GetValueOrDefault(0));
        }

    #endregion

    #region (Static) Data, cctor, AddSyncTarget(), RemoveSyncTarget(), SetMaxWidth(), GetMaxWidth()

    private static readonly Dictionary<SynchronizedTableLayoutPanel, Int32> Data;

    static SynchronizedTableLayoutPanel()
        {
        Data = new Dictionary<SynchronizedTableLayoutPanel, Int32>();
        }

    private static void AddSyncTarget(SynchronizedTableLayoutPanel table)
        {
        Data.Add(table, 0);
        }

    private static void RemoveSyncTarget(SynchronizedTableLayoutPanel table)
        {
        Data.Remove(table);
        }

    private static void SetMaxWidth(SynchronizedTableLayoutPanel table, Int32 width)
        {
        Data[table] = width;

        foreach (var pair in Data.ToArray())
            if (pair.Key.SynchronizationKey == table.SynchronizationKey && pair.Value != width)
                pair.Key.PerformLayout();
        }

    private static Int32 GetMaxWidth(String key)
        {
        var MaxWidth = Data
            .Where(p => p.Key.SynchronizationKey == key)
            .Max(p => (Int32?) p.Value);

        return MaxWidth.GetValueOrDefault(0);
        }

    #endregion
    }

Эта версия заботится только о первом столбце, но ее можно адаптировать для синхронизации других столбцов или строк.

0 голосов
/ 12 ноября 2011

Этот подход не мерцает и не вызывает скачков с размерами:

public partial class Form1 : Form
{
    private readonly Timer _timer = new Timer();

    public Form1()
    {
        InitializeComponent();

        _timer.Interval = 500;
        _timer.Tick += (o, ea) => UpdateWithRandomSizes();
        _timer.Start();
    }

    private void UpdateWithRandomSizes()
    {
        var rand = new Random();
        label1.Text = new string('A', rand.Next(10));
        label2.Text = new string('B', rand.Next(10));
        label3.Text = new string('C', rand.Next(10));
        label4.Text = new string('D', rand.Next(10));

        tableLayoutPanel1.ColumnStyles[0].SizeType = SizeType.AutoSize;
        tableLayoutPanel2.ColumnStyles[0].SizeType = SizeType.AutoSize;
        var width1 = tableLayoutPanel1.GetColumnWidths()[0];
        var width2 = tableLayoutPanel2.GetColumnWidths()[0];

        var max = Math.Max(width1, width2);

        tableLayoutPanel1.ColumnStyles[0].Width = max;
        tableLayoutPanel1.ColumnStyles[0].SizeType = SizeType.Absolute;
        tableLayoutPanel2.ColumnStyles[0].Width = max;
        tableLayoutPanel2.ColumnStyles[0].SizeType = SizeType.Absolute;
    }
}
...