TableLayoutPanels synchroniseren

Ik heb verschillende TableLayoutPanels, die elk een categorie naam/waarde-informatie in twee kolommen weergeven: een met informatieve labels en een met gegevenslabels.

In elk daarvan heb ik de eerste kolom ingesteld om alle labels automatisch in te stellen en rechts uit te lijnen, wat prima werkt. Het werkt echter afzonderlijk op elk TableLayoutPanel (uiteraard) en ziet er ongeveer zo uit:

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 |
+------------------+--------+

Ik ben op zoek naar een manier om alle van de naamlabels te overwegen bij het automatisch aanpassen van alle van de eerste kolommen, zodat het er als volgt uitziet:

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 |
+------------------+--------+

Ik kan niet alle gegevens in één tabel plaatsen, omdat elke tabel een andere categorie informatie vertegenwoordigt en zich binnen een aangepast besturingselement met een verzameling samenvouwbare panelen bevindt (zodat u elke categorie afzonderlijk kunt weergeven of verbergen).

Ik heb dit geprobeerd te bereiken door de containerbesturingselementen OnLayout() te overschrijven, alle eerste kolommen van de TableLayoutPanels in te stellen om automatisch te maken, al hun breedten te krijgen, het maximum te vinden en vervolgens al hun eerste kolommen op een vaste grootte te zetten van de grootste breedte. Dit werkt, maar ziet er vreselijk uit elke keer dat de lay-out optreedt, omdat alle kolommen naar automaat springen en dan terug naar vaste grootte.

Ik ga ervan uit dat ik ControlAdded en ControlRemoved voor elke tabel moet haken, en vervolgens SizeChanged voor elk besturingselement voor kinderen, om te weten wanneer de grootte van een onderliggende controle is gewijzigd en vervolgens handmatig de kolombreedte in te stellen, maar ik ' Ik weet niet zeker hoe je op betrouwbare wijze de juiste breedtes krijgt.

Ik heb een variant van de eerste methode geprobeerd - met GetPreferredSize() op alle besturingselementen in de eerste kolommen, om te proberen de grootste breedte te vinden en vervolgens alle eerste kolommen op een vaste grootte in te stellen, maar het leek alsof de breedte iets lager was te klein. Moet ik wat extra afstand toepassen?

Weet iemand een manier om het TableLayoutPanel te vragen autosize-berekeningen uit te voeren zonder dat het deze visueel daadwerkelijk toepast? Of misschien, liggend tegen de tafels om te 'pretenderen' dat er een controle is over een bepaalde breedte, zodat het hiermee rekening houdt? Ik kan geen werkelijke besturingselementen toevoegen, omdat deze dan meer cellen voor hen wil toewijzen. Ik heb met ILSpy geprobeerd naar de bron te kijken, maar goed, het is niet mooi. Het meeste werk wordt gedaan door de TableLayout-les, die natuurlijk intern is en ik niet kon volgen wat hij deed.

Bedankt voor alle ideeën ...

3
Ik weet dat je kunt bepalen hoelang je tekst zal zijn gebaseerd op het lettertype en de tekst. Kun je dat op al je tekst doen om het langst te vinden en vervolgens handmatig de breedtes van de eerste kolommen instellen voor al je lay-outpanelen?
toegevoegd de auteur Daryl, de bron
Goed gedaan op de ascii-tafels, hoe dan ook ...
toegevoegd de auteur Daryl, de bron
@ Dubl: door ik weet dat je kunt bedoel je "weet je hoe", of "ik weet hoe"? Je idee is haalbaar, maar misschien moet je het illustreren met een of andere code.
toegevoegd de auteur Gert Arnold, de bron
Jammer dat je wpf niet kunt gebruiken. Dit is triviaal in WPF.
toegevoegd de auteur Ritch Melton, de bron
@ Dubl: Ik denk dat dit in feite is wat ik kreeg door GetPreferredSize() op de labels te gebruiken - het leek de breedte die lichtjes te klein was voor het breedste element, dus ik kan alleen maar aannemen dat ik ' m verwaarlozing rekening te houden met een paar pixels van opvulling of iets, hoewel ik ben niet zeker van waar. Ik denk dat ik gewoon +8 kan krijgen tot de breedte die ik krijg of zoiets, maar het zou leuk zijn om het 'goed' te doen als er een manier is. Ook hoopte ik een algemene oplossing te vinden waarbij de bedieningselementen niet noodzakelijkerwijs labels waren ... misschien een beetje te hoopgevend echter, het meten van tekst zou mijn onmiddellijke probleem oplossen.
toegevoegd de auteur Ashley, de bron

3 antwoord

U kunt de Graphics.Measurestring gebruiken om de lengte in pixels zonder daadwerkelijk te bepalen het tekenen. Er zijn enkele kleine onvolkomenheden mee , dus je kunt denken aan het toevoegen of wat opvulling verwijderen. Na een test of twee kun je redelijk dichtbij komen. Dat is net zo goed als ik weet en het betekent niet dat de tekst in een label staat.

Ook proberen om een ​​manier te vinden om het TableLayoutPanel te krijgen om maten te berekenen zonder het visueel weer te geven, klinkt alsof je het probeert te hacken om iets te doen waar het niet voor ontworpen was.

3
toegevoegd
Ik ben blijven hangen met GetPreferredSize (), omdat het kan werken met elk besturingselement, met of zonder tekst. Bedankt voor de suggestie.
toegevoegd de auteur Ashley, de bron

Deze aanpak flikkert niet of veroorzaakt sprongen met het formaat:

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;
    }
}
0
toegevoegd
@Ashley - Ah, dat is rot. Ik was geïnteresseerd omdat ik me afvroeg of WinForms een equivalent had van de WPF-functie die deze taak triviaal maakt.
toegevoegd de auteur Ritch Melton, de bron
Ja, je kunt best een beetje doen met winforms (flowlayout, databinding, presenter-scheiding), maar voor het gemakkelijk composteren van kick-ass controls, wpf rocks.e
toegevoegd de auteur Ritch Melton, de bron
Hmm, dat was in essentie wat ik aan het doen was, maar het werkte niet goed voor mij. Ik heb het zojuist opnieuw geprobeerd (kopieer en plakte je code), en inderdaad, het is prima met slechts een paar rijen, maar met twee tabellen van slechts 15 rijen elk, duurt het hele lay-outproces bijna een seconde voor mij en is het zeer zichtbaar. 'Springen' is duidelijker als u één tabel lange lijnen en één tabel korte lijnen geeft. Mijn computer is niet precies traag (quad-core Phenom II) ... is TableLayoutPanel net zo inefficiënt?
toegevoegd de auteur Ashley, de bron
SuspendLayout ()/ResumeLayout() op de tafels voordat de labels worden gewijzigd lijkt het springgedrag te elimineren, maar het duurt nog steeds bijna een seconde en is zichtbaar stukje bij beetje opnieuw getekend. Misschien kan dubbele buffering voor de tekening zorgen, maar niet de snelheid.
toegevoegd de auteur Ashley, de bron
Ik wil echt heel graag overstappen naar WPF. In mijn huidige project heb ik al meer dan 80 aangepaste besturingselementen gemaakt en ik denk dat meer dan 75% van hen onnodig of triviaal zou zijn om te implementeren in WPF. Helaas ben ik op dit moment niet voldoende bekend met de architectuur om de kans te grijpen.
toegevoegd de auteur Ashley, de bron

Het bleek dat de breedte geretourneerd door GetPreferredSize() was nuttig, het was gewoon 'te laat'; Ik werkte de juiste grootte uit en retourneerde het binnen de code die werd aangeroepen vanuit de OnLayout() -methode van TableLayoutPanels en het instellen van de kolombreedte heeft geen effect tot de lay-out volgende .

Ik had een oplossing met een afzonderlijke component die IExtenderProvider implementeerde, die kon worden gebruikt om samen tabellen samen te voegen, maar vanwege het bovenstaande probleem bleef het altijd achter op besturingswijzigingen. Zelfs als je SizeChanged aanslaat op alle onderliggende knoppen, krijgt het TableLayoutPanel de gebeurtenis eerst en begint de lay-out.

Ik kon dus geen andere manier zien dan het lay-outproces zelf opheffen. Ik heb geprobeerd een LayoutEngine te maken die de benodigde berekeningen uitvoerde, de grootte van de kolommen veranderde, en vervolgens het eigenlijke lay-outwerk delegeerde naar de oude layout-engine, maar helaas is Control.LayoutEngine alleen-lezen en gebruikt TableLayoutPanel niet eens een achtergrondveld. retourneert een ander object rechtstreeks, dus ik kon er niet eens omheen komen door reflectie te gebruiken om het privé-achtergrondveld toe te wijzen.

Uiteindelijk moest ik een beroep doen op subclassificatie van de besturing om OnLayout() te overschrijven. Hier is het resultaat:

public class SynchronizedTableLayoutPanel : TableLayoutPanel
    {
    /// 
/// Specifies a key used to group s together. ///
 
    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 Data;

    static SynchronizedTableLayoutPanel()
        {
        Data = new Dictionary();
        }

    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
    }

Deze versie geeft alleen om de eerste kolom, maar deze kan worden aangepast om andere kolommen of rijen te synchroniseren.

0
toegevoegd