Question

Je joue avec PropertyDescriptor et IcustomTypeDescriptor (toujours) Essayer de lier un dataGrid WPF à un objet, pour lequel les données sont stockées dans un dictionnaire.

Étant donné que si vous passez WPF DataGrid, une liste d'objets de dictionnaire, il générera automatiquement des colonnes basées sur les propriétés publiques d'un dictionnaire (comparateur, comptage, clés et valeurs), ma personne sous-classe le dictionnaire et implémente icustomtypedeScriptor.

IcustomTypeDescriptor définit une méthode GetProperties qui renvoie une propriété DescriptorCollection.

PropertyDescriptor est abstrait, vous devez donc le sous-classer, j'ai pensé que j'aurais un constructeur qui a pris Func et un paramètre d'action qui délégue l'obtention et le réglage des valeurs du dictionnaire.

Je crée ensuite un PersonpropertyDescriptor pour chaque clé du dictionnaire comme ceci:

            foreach (string s in this.Keys)
            {
                var descriptor = new PersonPropertyDescriptor(
                        s,
                        new Func<object>(() => { return this[s]; }),
                        new Action<object>(o => { this[s] = o; }));
                propList.Add(descriptor);
            }

Le problème est que chaque propriété obtient sa propre fonction et sa propre action, mais ils partagent tous la variable extérieure s Ainsi, bien que les colonnes DataGrid autogénérés pour "id", "premier nom", "lastname", "âge", "genre", ils obtiennent tous et setnt contre "genre" qui est la valeur de repos finale de s dans la boucle foreach.

Comment puis-je m'assurer que chaque délégué utilise la clé de dictionnaire souhaitée, c'est-à-dire la valeur de S au moment où la func / l'action est instanciée?

Infiniment reconnaissant.


Voici le reste de mon idée, j'expérimente ici ce ne sont pas des «vraies» classes ...

// DataGrid binds to a People instance
public class People : List<Person>
{
    public People()
    {
        this.Add(new Person());
    }
}

public class Person : Dictionary<string, object>, ICustomTypeDescriptor
{
    private static PropertyDescriptorCollection descriptors;

    public Person()
    {
        this["ID"] = "201203";
        this["FirstName"] = "Bud";
        this["LastName"] = "Tree";
        this["Age"] = 99;
        this["Gender"] = "M";        
    }        

    //... other ICustomTypeDescriptor members...

    public PropertyDescriptorCollection GetProperties()
    {
        if (descriptors == null)
        {
            var propList = new List<PropertyDescriptor>();

            foreach (string s in this.Keys)
            {
                var descriptor = new PersonPropertyDescriptor(
                        s,
                        new Func<object>(() => { return this[s]; }),
                        new Action<object>(o => { this[s] = o; }));
                propList.Add(descriptor);
            }

            descriptors = new PropertyDescriptorCollection(propList.ToArray());
        }

        return descriptors;
    }

    //... other other ICustomTypeDescriptor members...

}

public class PersonPropertyDescriptor : PropertyDescriptor
{
    private Func<object> getFunc;
    private Action<object> setAction;

    public PersonPropertyDescriptor(string name, Func<object> getFunc, Action<object> setAction)
        : base(name, null)
    {
        this.getFunc = getFunc;
        this.setAction = setAction;
    }

    // other ... PropertyDescriptor members...

    public override object GetValue(object component)
    {
        return getFunc();
    }

    public override void SetValue(object component, object value)
    {
        setAction(value);
    }
}
Était-ce utile?

La solution

La solution de Marc est bien sûr correcte, mais je pensais que j'élargirais pourquoi ci-dessous. Comme la plupart d'entre nous le savent, si vous déclarez une variable dans un for ou foreach déclaration, elle ne vit que aussi longtemps que ce qui est à l'intérieur, ce qui le fait sembler Comme la variable est la même qu'une variable déclarée dans le bloc de déclaration d'une telle déclaration, mais ce n'est pas vrai.

Pour mieux le comprendre, prenez la boucle suivante. Ensuite, je vais reposer la boucle "équivalente" en forme de temps.

for(int i = 0; i < list.Length; i++)
{
    string val;
    list[i] = list[i]++;
    val = list[i].ToString();
    Console.WriteLine(val);
}

Cela fonctionne dans la forme en forme ci-dessous: (ce n'est pas exactement la même chose, car continue agira différemment, mais pour les règles de cadrage, c'est la même chose)

{
    int i = 0;
    while(i < list.Length)
    {
        {
            string val;
            list[i] = list[i]++;
            val = list[i].ToString();
            Console.WriteLine(val);
        }
        i++;
    }
}

Lorsque "explosé" de cette façon, la portée des variables devient plus claire, et vous pouvez voir pourquoi il capture toujours la même valeur "S" dans votre programme, et pourquoi la solution de Marc montre où placer votre variable afin qu'un seul soit unique capturé à chaque fois.

Autres conseils

Simplement:

        foreach (string s in this.Keys)
        {
            string copy = s;
            var descriptor = new PersonPropertyDescriptor(
                    copy,
                    new Func<object>(() => { return this[copy]; }),
                    new Action<object>(o => { this[copy] = o; }));
            propList.Add(descriptor);
        }

Avec des variables capturées, c'est là que c'est déclaré c'est important. Ainsi, en déclarant la variable capturée à l'intérieur de la boucle, vous obtenez une instance différente de la classe de capture par itération (la variable de boucle, s, est techniquement déclaré à l'extérieur la boucle).

créer une copie locale de s à l'intérieur de votre for boucle et utilisez-le.

for(string s in this.Keys) {
string key = s;
//...
}

Pour quelques réflexions supplémentaires sur cette question, voir

http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top