Laden...

ValueConverter-Parameter Type übergeben

Erstellt von perlfred vor einem Jahr Letzter Beitrag vor einem Jahr 740 Views
P
perlfred Themenstarter:in
251 Beiträge seit 2010
vor einem Jahr
ValueConverter-Parameter Type übergeben

Hallo!

Ich möchte einen Type dem ich einem ValueConverter als Parameter übergebe im ValueConverter zurück casten.

Type dem ConvertParameter übergeben:



<Path Data="{Binding SortierungsRichtung, Converter={StaticResource SortToGeoKonv}, ConverterParameter={x:Type local:EnumSortierungsRichtung } }" 


Versuch den Type im ValueConverter zurück zu casten:



public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
            if (parameter != null)
            {
                var typParameter = parameter.GetType().GetType();          // Runtime-Type
            }


Im ValuConverter sehe ich zwar das der parameter-Parameter den übergebenen Type enthält, aber als object{System.RuntimeType}. GetType() ergibt immer RuntimeType!

Wie bekomme ich aber eine Type-Variable vom übergebenen Type?

2.078 Beiträge seit 2012
vor einem Jahr

Object.GetType Methode
Das gibt den Typ der Instanz zurück und der ist eben RuntimeType.

Und was Du suchst:
[FAQ] Casten aber richtig: Boxing/Unboxing - () / is / as / Pattern Matching

P
perlfred Themenstarter:in
251 Beiträge seit 2010
vor einem Jahr

Hallo Palladin007!

Danke das du mir so spät noch geantwortet hast!

Leider habe ich in deinem Link keinen Hinweis gefunden, wie ich einen Runtime-Type in den adäquaten Type (auf den er verweist) caste, ohne dass ich den Zieltyp angeben kann. (Es soll immer der Type sein, auf dem im RunTime-Type verwiesen wird.)

Zur Vorgehensweise:

Ich übergebe in XAML, im ConverterParameter einen Type (=ZielType).

In der Convert-Methode möchte ich diesen Type als ZielType zum casten eines Objektes verwenden. Der ZielType steht also nicht fest, sondern wird als Parameter übergeben.

Nun habe ich das Problem, dass der übergebene ZielType ein Runtime-Type ist. Ich muss also diesen Runtime-Type erst in den Type der übergeben wurde casten, um dann ein Objekt mit dem ZielType erstellen zu können.

In der Anwendung:

Ich möchte einen universellen ValueConverter erstellen, der aus einem Enum-Wert den Index ermittelt.
Normalerweise ganz einfach, einfach den Enum-Wert in einen Integer casten ((int)EnumWert). Das casten gestaltet sich in XAML aber recht "tricky". Deshalb der Weg über den ValueConverter.

Dem universellen ValueConverter übergebe ich als Value den Enum-Wert und als Parameter den Enum-Type.

Im Converter wollte ich von diesem Enum-Type eine Enum-Instanz erstellen und über den Enum-Wert den Enum-Index zurückgeben.

Nun bin ich aber an der Hürde gescheitert, dass der übergebene Enum-Typ nicht als Type sondern als Runtime-Type "ankommt".

Hast du noch einen Hinweis?

Anmerkung:

Jeweils Converter für einen festen Enum-Typ zu erstellen ist natürlich kein Problem, aber auch nicht die Lösung.

2.078 Beiträge seit 2012
vor einem Jahr

Type type = (Type)parameter;

Steht im zweiten Link, so wie andere/bessere Möglichkeiten dafür.
RuntimeType leitet von Type ab, ist die interne Implementierung.

P
perlfred Themenstarter:in
251 Beiträge seit 2010
vor einem Jahr

Hallo Palladin007!

Wir reden aneinander vorbei.

Das was du als Lösung vorschlägst, ist ein ganz normales casten.

Das setzt voraus, dass der Typ in den gecastet werden soll bekannt ist.

Ich versuche die ganze Zeit zu erklären, das der Zieltyp erst aus dem parameter-Type (der aber als Zieltype:Runtime-Type ankommt) ermittelt werden muss (bevor ich ihn, den parameter, in genau diesen Type casten kann!!!).

Es wäre nett, wenn du ein Beispiel postest, in dem ein ValueConverter als ConverterParameter einen Type übergeben bekommt und genau dieser Type in der Convert-Methode ermittelt wird.


ConverterParameter={x:Type local:EnumSortierungsRichtung } }

Dieser Type soll in der Convert-Methode in einer Variable vom Type: Type ermittelt werden. In der Variable soll also der jeweils übergebene Type (aus der Angabe im XAML-ConvertParameter) stehen. (Und nicht RealType!)

Wahrscheinlich geht es mit Reflection der parameter-Variable in der Convert-Methode den "ursprünglichen" Type zu ermitteln.

Lässt sich nicht so einfach erklären
Sorry!

2.078 Beiträge seit 2012
vor einem Jahr

Vergiss RuntimeType, damit hast Du nichts zu tun, das ist ein rein internes Implementierungsdetail.

Du übergibst als Parameter ein Type-Objekt, das den "EnumSortierungsRichtung"-Typ darstellt.
Und Du bekommst als Parameter ein Type-Objekt, das den "EnumSortierungsRichtung"-Typ darstellt.
Das Objekt castest Du auf Type und hast das Type-Objekt, das den "EnumSortierungsRichtung"-Typ darstellt.
Eine konkrete typisierte Variable von diesem Typ bekommst Du nicht, C# ist statisch typisiert, da geht das nicht.

Den Wert musst Du also manuell per Reflection konvertieren, oder ggf. helfen die Convert- oder Enum-Klassen.

T
2.219 Beiträge seit 2008
vor einem Jahr

Die Frage wäre was dein Ziel dieser Konvertierung sein soll.
Was genau bringt es dir den Typen zurück zu konvertieren?
Aktuell ist dein Use Case nicht bekannt.
Was dein Converter machen soll, ist klar aber was du damit beabsichtigst, ist es nicht.

Ggf. bist du mit deinem Ansatz auch völlig falsch aber ohne zu erklären was du damit erreichen willst, kann man dir kaum helfen.

Nachtrag:
Wenn es dir nur um konkrete Typen geht, kannst du auch mit einer einfachen is Abfrage prüfen welchen Typen du hast und kannst dann auch direkt ein Casting machen.
Das dürfte bei einer kleinen Zahl von Typen auch möglich sein.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

P
perlfred Themenstarter:in
251 Beiträge seit 2010
vor einem Jahr

Hallo Palladin007!

Ja, jetzt habe ich dich verstanden!

Ich sollte beim casten von parameter nicht auf einen spezifischen Type casten (den ich nicht angeben kann), sondern parameter auf die Klasse Type casten. 🙂 🙂 🙂

Funktioniert wunderbar!



Type EnumType = (Type)parameter;


Vielen Dank!

P
perlfred Themenstarter:in
251 Beiträge seit 2010
vor einem Jahr

Hallo T-Virus!

Danke das du dir Gedanken machst!

Was dein Converter machen soll ist klar, aber was du damit beabsichtigst, ist es nicht.

Ich erstelle allgemeine Converter, die ich dann verkette. Oben genannter hatte mir z.B. noch gefehlt.

Im konkretem Beispiel schalte ich damit ein Icon eines Buttons je nach gewählter Sortierungs-Richtung um. Die Sortierungsrichtung (aufsteigend, fallend oder ohne) ist in einer Aufzählung definiert.

ValueConverter 1 konvertiert den Aufzählungs-Wert in einen Integer-Wert. (Value = Aufzählungs-Wert, Parameter Aufzählungs-Typ)
ValueConverter 2 konvertiert den / einen Integer-Wert in ein Geometry-Objekt (Value = Integer-Wert, Parameter: Pro Integer-Wert einen Parameter dem das zugehörige Geometry-Objekt übergeben wird.)

Ich könnte das alles fest verzurren und mit einem ValuConverter je nach Enum-Wert ein zugeordnetes Geometry-Objekt zurückgeben.

Mit den allgemeinen ValueConvertern kann ich aber flexibel zu jeder beliebigen Aufzählung Geometry-Objekte zuordnen.

Wünsche Euch ein relaxes Wochenende!

P
perlfred Themenstarter:in
251 Beiträge seit 2010
vor einem Jahr

Hallo!

Für Alle die es interessiert, hier der fertige ValueConverter: Kurz getestet 😉



[ValueConversion(typeof(string), typeof(int))]
/// <summary>
/// Gibt zu einem Aufzählungswert den Index-Wert zurück.
/// </summary>
public class EnumToIndexConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (parameter != null)                                              // ConvertParameter übergeben?
        {
            Type EnumType = (Type)parameter;                                // Enum-Typ "übernehmen"
            Enum myEnum = Activator.CreateInstance(EnumType) as Enum;       // Neues Objekt vom Typ der in der Variable EnumType steht, erstellen

            if (value != null && value.GetType() == EnumType)               // Ist der Wert ein Aufzählungs-Wert des übergebenen Enum-Typ's
            {
                string EnumKey = value.ToString();                          // Enum-Key "holen"
                List<string> EnumKeyListe = myEnum.GetType().GetFields().Select(x => x.Name).Where(x => x != "value__" ).ToList();   // Liste der Key's (per Reflection) "holen"
                if (EnumKeyListe.Contains(EnumKey)) return EnumKeyListe.FindIndex(x => x == EnumKey);   // Index zurückgeben
            }
        }
        return -1;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException(); // Back-Funktion nicht implementiert 
    }
}


Hinweis:

Der ValueConverter funktioniert nur bei klassischen Enum's (Datentyp Int32, ohne Flags, Schrittweite 1 ...)

2.078 Beiträge seit 2012
vor einem Jahr

Warum erstellst Du eine Instanz von dem Enum, nur um dann wieder den Typ abzurufen? Kannst Du dir sparen und direkt den Typ nutzen.
Außerdem solltest Du noch prüfen, ob der Typ auch wirklich ein Enum ist, sonst gibt's ne Exception.
Es gibt Enum.GetNames/Enum.Parse/Enum.TryParse-Methode, damit sparst Du dir die Reflection.
Außerdem solltest Du nicht mit dem Index arbeiten, sondern dem Wert und da reicht ein Cast auf int.

Also in etwa so:


public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    var name = value?.ToString();
    var type = parameter as Type;

    if (name is null || type is null || !type.IsEnum)
        return -1;

    if (Enum.TryParse(type, name, out var enumValue))
    {
        if (Enum.IsDefined(type, enumValue))
            return (int)enumValue;
    }

    return -1;
}

Ein Nebeneffekt davon ist, dass auch der int-Wert des Enums als String erlaubt wird, da Enum.TryParse das auch parsen kann.
Und das Enum.IsDefined sorgt dafür, dass nicht jede x-beliebige Zahl erlaubt wird, da man prinzipiell jede Zahl als jedes Enum casten kann.

4.931 Beiträge seit 2008
vor einem Jahr

Anstatt den String zu parsen, sollte man auch direkt entweder Convert.ChangeType() oder speziell für enum-Typen Enum.ToObject aufrufen können.

P
perlfred Themenstarter:in
251 Beiträge seit 2010
vor einem Jahr

Hallo Palladin007

Das in dieser Konsequenz zu lösen! ... Jetzt kann ich nur den Hut ziehen!

Und um noch deine Frage zu beantworten ...

Warum erstellst Du eine Instanz von dem Enum, nur um dann wieder den Typ abzurufen?

Weil mir kein anderer Weg eingefallen ist um an die Eigenschaften des Enum-Typ's ranzukommen.

Die statischen Enum-Methoden (besonders das TryParse) sind natürlich die Lösung für diesen Fall. (Daumen hoch! 3x 🙂)

Im Prinzip sind es ja nur die zwei Abfragen (incl. "Wandlung"):


if (Enum.TryParse(type, name, out var enumValue))
{
    if (Enum.IsDefined(type, enumValue)) return (int)enumValue;
}

Vielen Dank (auch für die Hinweise was ich nicht beachtet habe)!

P
perlfred Themenstarter:in
251 Beiträge seit 2010
vor einem Jahr

Hallo Th69!

Danke für deinen Hinweis!

Um ehrlich zu sein, weiß ich jetzt gar nicht, auf welche Stelle du dich beziehst.

Beide Vorschläge konvertieren ja eher einen (Enum) Wert in den zugeordneten Enum-Key.

2.078 Beiträge seit 2012
vor einem Jahr

Anstatt den String zu parsen, sollte man auch direkt entweder Convert.ChangeType() oder speziell für enum-Typen Enum.ToObject aufrufen können.

Convert.ChangeType() wirft Exceptions, wenn er nicht konvertieren kann, daher nutze ich das nur, wenn es wirklich keinen besseren Weg gibt.
Und Enum.ToObject() braucht den konkreten Wert des Enums, mit dem Namen kann es nichts anfangen - hier kommt, so wie ich das verstanden habe, aber der Name an, richtig?
Wenn hier aber doch der konkrete Wert ankommt, dann wäre Enum.ToObject() in der Tat besser, könnte man ja vorher prüfen, ob value ein int oder ein string ist und dann entsprechend reagieren. Das Enum.IsDefined() braucht man aber immer noch.
Wobei der Converter keinen Sinn hätte, wenn der Wert ankommen würde, da er ja den Wert zurückliefern soll.

Weil mir kein anderer Weg eingefallen ist um an die Eigenschaften des Enum-Typ's ranzukommen.

Einfach den Typ direkt nehmen.


Type enumType = (Type)parameter;
Enum myEnum = Activator.CreateInstance(enumType) as Enum;
Type enumType2 = myEnum.GetType();
Console.WriteLine(enumType == enumType2); // true

4.931 Beiträge seit 2008
vor einem Jahr

Ich bin davon ausgegangen, daß es um die Konvertierung von einem zu einem anderem enum-Typ geht (bezogen auf den Namen SortToGeoKonv) und nicht, daß SortierungsRichtung ein String-Typ ist.

@perlfred: Vllt. solltest du mal ein konkretes Beispiel (mit Angabe der Typen) posten?

P
perlfred Themenstarter:in
251 Beiträge seit 2010
vor einem Jahr

Hallo Th69!

Wieder Danke für deine (auch berechtigten) "Überlegungen"!

SortToGeoKonv ist nicht ein eizelner ValueConverter sonder eine Converter-Gruppe.

Ich erstelle allgemeine Converter, die ich dann verkette.
...
ValueConverter 1 konvertiert den Aufzählungs-Wert in einen Integer-Wert. (Value = Aufzählungs-Wert, Parameter Aufzählungs-Typ)
ValueConverter 2 konvertiert den / einen Integer-Wert in ein Geometry-Objekt (Value = Integer-Wert, Parameter: Pro Integer-Wert einen Parameter dem das zugehörige Geometry-Objekt übergeben wird.)

Korrekter hätte ich schreiben müssen: ValueConverter 1 konvertiert einen Aufzählungs-Namen in den adäquaten Aufzählungs-Wert. (Value = Aufzählungs-Name, Parameter Aufzählungs-Typ)

Und genau bei diesem hatte ich das Problem mit der Übernahme des Aufzählungs-Typ's (vom ConverterParameter).

In der View gestaltet sich das Ganze so:



<Window.Resources>
    <arWPFVK:ValueConverterGroup x:Key="SortToGeoKonv">
        <local:EnumNameToWertConverter/>
        <local:IntegerToGeometryConverter Path0="{StaticResource icoArrowUp16x12}" Path1="{StaticResource icoArrowDown16x12}" Path2="{StaticResource icoEmpty}" />
    </arWPFVK:ValueConverterGroup>
</Window.Resources>

...

<Button Command="{Binding SortierungRichtungsWechselCommand}"  ... >
    <Path Data="{Binding SortierungsRichtung, Converter={StaticResource SortToGeoKonv}, ConverterParameter={x:Type local:EnumSortierungsRichtung }}" />
</Button>


Ich habe den Converter 1 (von dem wir hier sprechen) noch ein wenig umbenannt: EnumNameToWertConverter. Die VM-Eigenschaft: SortierungsRichtung ist eine String-Eigenschaft die einen Namen aus der Auflistung der EnumSortierungsRichtung enthält. Wird im Command: SortierungRichtungsWechsel umgeschalten.

Ich denke, jetzt ist die Funktion der ValueConverter eindeutig ersichtlich.

Vielen Dank für Eure Hilfe!