Einzelnen Beitrag anzeigen

Benutzerbild von jfheins
jfheins

Registriert seit: 10. Jun 2004
Ort: Garching (TUM)
4.579 Beiträge
 
#18

AW: Wozu sind Attribute gut ?

  Alt 18. Jul 2013, 21:46
Tja, also ich kann gerne mal die Verwendung des enums demonstrieren.

Vorab: Alle Codeschnipsel sind auch auf pastebin zu finden, Code-Highlighting ist ja manchmal schon ganz nett.

Ich hole zuerst mal etwas aus:

Wie oben angedeutet, befinden sich auf dem Formular zwei Comboboxen. Die erste ist für die Kategorie, die zweite für den Typ.

Zu jedem Tupel kann es 1..3 Befehle (enumwerte) geben. So hätte zum Beispiel {"Variable setzen", "Status"} folgende enumwerte zugeordnet:
REQ_STATE = 6, // Status soll abgefragt werden (REQ=Request)
RES_STATE = 70, // Rückmeldung (RES=Response)
SET_STATE = 135, // Status soll gesetzt werden
das Tupel {"Befehl", "Notaus"} hat hingegen nur:
CMD_NOTAUS = 205, // Ein Befehl hat keine zusätzlichen Daten und es kommen auch keine Daten zurück.

Die Kategorie ist übrigens ebenfalls ein enum:
Code:
    public enum Category
    {
        [Description("Allgemein")]
        General,
        [Description("Sensor auslesen")]
        GetSensor,
        [Description("Variable auslesen")]
        GetVariable,
        [Description("Konstante auslesen")]
        GetConstant,
        [Description("Variable setzen")]
        SetVariable,
        [Description("Konstante setzen")]
        SetConstant,
        Response,
        [Description("Befehl")]
        Command
    }
Erläuterung:
Allgemein => Was woanders nicht reinpasste (=PING und Rohdaten verschicken)
Sensor auslesen => Sensoren können abgefragt, aber nicht gesetzt werden
Variablen können ausgelesen und gesetzt werden, ändern sich aber auch mit der Zeit
Konstante => Kann ausgelesen und gesetzt werden, beim Lesen kommt immer der zuletzt geschriebene Wert zurück
Befehle transportieren keine Daten. Der Empfang wird mit einem ACK bestätigt.

Netter Effekt: Die eine Combobox, die die Kategorien enthält, wird mit den Attributen gefüttert. Response steht also im enum drin, aber nicht in der Auswahlliste.

=============================
Nun zur eigentlichen Verwendung: Die Attribute können ja nur mittels Reflection ausgelesen werden. Dazu habe ich eine nette Erweiterungsmethode gebastelt. Nur so geht das typeof(...).ToList das eine Liste mit den passenden Attributwerten zurückgibt.

Der Eventhandler für doe Änderung der Kategorie schaut so aus:
Code:
        private void category_cb_SelectedIndexChanged(object sender, EventArgs e)
        {
            var selected = type_cb.SelectedItem; // Ausgewählten Wert merken

            Category cat = (Category)category_cb.SelectedValue; // Ausgewählte Kategorie
           
// Meine Erweiterungsmethode. Hier steht "Liefere mir bitte anhand des enums 'COMM_Command' eine Liste mit Tupeln {enumwert, Displayname}, bei denen die Kategorie gleich cat ist"
             List<KeyValuePair<Enum, string>> list = typeof(COMM_Command).ToList<CommandInfoAttribute>(x => x.Category == cat, x => x.DisplayName);
// Die Liste dann natürlich gleich in die zweite Combobox schieben
            type_cb.DataSource = list;

// Und falls vorher ein bestimmter Wert ausgewählt war, diesen nach Möglichkeit wieder auswählen.
            if (selected != null)
            {
                string text = ((KeyValuePair<Enum, string>)selected).Value;
                var item = list.FirstOrDefault(x => x.Value == text);
                if (item.Value != null)
                    type_cb.SelectedValue = item.Key;
            }
        }
Ich hoffe die Kommentare machen das Vorgehen deutlich. Hier dann noch der Handler für die Änderung des Typs:
Code:
        private void type_cb_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Einheiten herausfinden
            string name = type_cb.Text;
             List<KeyValuePair<Enum, string>> list = typeof(COMM_Command).ToList<CommandInfoAttribute>(x => x.DisplayName == name, x => x.Unit);

            unit_lbl.Visible = list.Count == 1;
            unit_cb.Visible = list.Count != 1;

// Falls ein Wert geschickt werden soll, bitte die Einheit neben dem Editfeld anzeigen
            // Bei einer Einheit Label benutzen, sonst Combobox
            unit_lbl.Text = list[0].Value;
            if (list.Count > 1)
            {
                unit_cb.DisplayMember = "Value";
                unit_cb.ValueMember = "Key";
                unit_cb.DataSource = list;
            }
// Mit getEnumValue() bekomme ich den enumwert, der durch die aktuelle Auswahl genriert wird.
            CommandInfoAttribute attrib = getEnumValue().GetInfo();

            unit_lbl.Visible = list.Count == 1 && attrib.NeedsPrimaryValue();
            unit_cb.Visible = list.Count != 1 && attrib.NeedsPrimaryValue();

            // Sekundärwert Beschreibung zuweisen und ggf. sichtbar machen
            value2_lbl.Text = attrib.SecondaryName + ":";
            value2_lbl.Visible = attrib.NeedsSecondaryValue();
            value2_txt.Visible = attrib.NeedsSecondaryValue();

            // Bei Auslesen-Befehlen die Werteeingabe verstecken
            value_lbl.Text = attrib.PrimaryName + ":";
            value_lbl.Visible = attrib.NeedsPrimaryValue();
            value_txt.Visible = attrib.NeedsPrimaryValue();
            value_txt.Clear();
        }

        // Welcher Befehl entspricht den aktuellen GUI Einstellungen?
        private COMM_Command getEnumValue()
        {
            var unit = unit_lbl.Visible ? unit_lbl.Text : unit_cb.Text; // Einheit bestimmen
            var enumvalue = typeof(COMM_Command).ToList<CommandInfoAttribute>(x =>
                x.Category == (Category)category_cb.SelectedValue
                && x.DisplayName == type_cb.Text
                && (!x.NeedsPrimaryValue() || x.Unit == unit), x => "").First();
            return (COMM_Command)enumvalue.Key;
        }
Soo, falls ihr das jetzt bis hier durchhabt: Danke dass ihr das gelesen habt Und falls euch jetzt eine bessere Lösung einfällt, nur zu - es hieß nämlich eh "Sag mal, diese Befehle und so, könnte man dafür nicht ne xml Datei machen?"

Das ganze ist natürlich nicht in einer Datei. Falls ihr euch den Code im natürlichen Habitat anschauen wollt, habe ich euch noch eine zip Datei angehängt, in der die meisten Funktionen drin sind. Darin ist auch noch ein bisschen zu sehen, wie das ganze benutzt wird.

Zitat:
Ich persönlich finde das gezeigte Beispiel mit den Enums auf den ersten Blick übrigens schlecht lesbar.
Nicht dass ich jetzt meine Arbeit abwälzen will, aber mich würden andere Ansätz schon interessieren. Der einzige Ansatz auf einem Ähnlichen Abstraktionsniveau war wie folgt: Einen normalen Enum basteln. Danach ein Dictionary<COMM_Command, CCinfo> anlegen. In einem langen Methode dann 212 mal sowas:
Code:
dict.Add(COMM_Command.REQ_APPROACH_BOTTOM_DIST, new CCinfo(Category.GetConstant, "Annäherungsdistanz (unten)") { Unit = "m", Conversion = CommType.thousands });
Hat mich nicht so begeistert
  Mit Zitat antworten Zitat