3

いくつかの TableLayoutPanels があり、それぞれが名前/値情報のカテゴリを 2 つの列 (1 つは情報ラベル付き、もう 1 つはデータ ラベル付き) に表示します。

それぞれで、最初の列をオートサイズに設定し、すべてのラベルを右揃えにしました。これは問題なく機能します。ただし、それは (明らかに) 各 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 |
+------------------+--------+

すべてのデータを 1 つのテーブルにまとめることはできません。各テーブルは異なるカテゴリの情報を表し、折りたたみ可能なパネルのコレクションを含むカスタム コントロール内にあるためです (各カテゴリを個別に表示または非表示にすることができます)。

コンテナー コントロールの OnLayout() をオーバーライドし、TableLayoutPanels の最初の列をすべて自動サイズに設定し、すべての幅を取得し、最大値を見つけてから、最初の列をすべて固定サイズに設定することで、これを達成しようとしています。最大幅の。これは機能しますが、すべての列が自動サイズにジャンプしてから固定サイズに戻るため、レイアウトが発生するたびに恐ろしいように見えます。

テーブルごとに ControlAdded と ControlRemoved をフックし、子コントロールごとに SizeChanged をフックして、子コントロールのサイズがいつ変更されたかを把握し、手動で列幅を設定する必要があると想定していますが、私は '正しい幅を確実に取得する方法がわかりません。

最初の方法のバリエーションを試しました。最初の列のすべてのコントロールで GetPreferredSize() を使用して最大幅を見つけようとし、最初のすべての列を固定サイズに設定しましたが、わずかに幅が返されたように見えました。小さいに。追加の間隔を適用する必要がありますか?

実際に視覚的に適用せずに自動サイズ計算を実行するように TableLayoutPanel に依頼する方法を知っている人はいますか? それとも、テーブルに嘘をついて、特定の幅のコントロールがあると「ふり」をして、それを考慮に入れるのでしょうか? 実際のコントロールを追加することはできません。これは、コントロールにさらにセルを割り当てたいからです。ILSpy でソースを見てみましたが、まあ、きれいではありません。ほとんどの作業は、もちろん内部の TableLayout クラスによって行われているようで、私はそれが何をしているのかを理解できませんでした。

アイデアをありがとう...

4

3 に答える 3

3

Graphics.Measurestringを使用して、実際に描画せずに長さをピクセル単位で決定できます。多少の欠けがありますので、パディングの追加や削除を検討してください。1つか2つのテストの後、かなり近づくことができます。それは私が知っている限り適切な方法であり、テキストがラベルに含まれているわけではありません。

また、視覚的に表示せずに TableLayoutPanel にサイズを計算させる方法を見つけようとすると、ハッキングして設計されていないことを実行しようとしているように思えます。

于 2011-11-13T05:06:52.037 に答える
0

このアプローチでは、サイズ変更によるちらつきやジャンプが発生しません。

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;
    }
}
于 2011-11-12T06:16:21.900 に答える
0

GetPreferredSize() によって返された幅が有用であることが判明しましたが、それは「遅すぎた」だけです。私は正しいサイズを計算し、TableLayoutPanels の OnLayout() メソッドから呼び出されたコード内でそれを返しました。列幅を設定しても、次のレイアウトまで効果がありません。

テーブルを結合するために使用できる 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
    }

このバージョンは最初の列のみを考慮しますが、他の列または行を同期するように適応させることができます。

于 2011-11-13T21:28:47.293 に答える