11
votes

DataContext de liaison à ValidationRule

J'ai une validation personnalisée qui nécessite un accès à la vue de la vue afin de valider une valeur fournie conjointement avec d'autres propriétés de la vue. J'ai précédemment essayé de l'accomplir en utilisant un groupe de validation, mais a abandonné cette idée car le code que je modifie nécessiterait beaucoup de refactoring afin de permettre à cet itinéraire.

J'ai trouvé un Fil sur un groupe de discussion qui a montré un moyen de lier le DataContext d'une commande dans laquelle la validationdrule est exécutée à cette validationrule à titre d'une classe intermédiaire héritée de dépendanceObject, mais je ne peux pas l'obtenir pour se lier. P>

peut aider quelqu'un? P>

My Validationdrule est comme suit ... p>

<UserControl x:Class="..."
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:val="clr-namespace:Validators" x:Name="myUserControl">

    <TextBox Name="myTextBox">
        <TextBox.Text>
            <Binding NotifyOnValidationError="True" Path="myViewModelProperty" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <val:TotalQuantityValidator>
                        <val:TotalQuantityValidator.Context>
                            <val:TotalQuantityValidatorContext ViewModel="{Binding ElementName=myUserControl, Path=DataContext}" />
                        </val:TotalQuantityValidator.Context>
                    </val:TotalQuantityValidator>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>

</UserControl>


1 commentaires

Je sais que cette question est similaire à Stackoverflow.com / Questions / 3577899 / ... , mais je cherche à accéder à DataContext et pas seulement à une autre propriété.


7 Réponses :


3
votes

Le problème que vous rencontrez est que votre DataContext est en cours de définition après avoir créé la règle de validation et qu'il n'y a aucune notification qu'elle a changé. Le moyen le plus simple de résoudre le problème est de modifier le XAML sur les éléments suivants: xxx pré>

puis configurez le contexte directement après la définition du DataContext: P>

public MainWindow()
{
    this.DataContextChanged += new DependencyPropertyChangedEventHandler(MainWindow_DataContextChanged);
    InitializeComponent();
}

private void MainWindow_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    this.validator.Context = new TotalQuantityValidatorContext { ViewModel = (MyViewModel)this.DataContext };
}


3 commentaires

J'ai un document DataContext initial dans le XAML, qui pourrait ensuite être remplacé par le code-derrière (il s'agit d'un formulaire de saisie de données - lorsqu'il est utilisé pour la première fois, il utilise le fichier DataContext défini par XAML, après une enregistrement, attribue ensuite un nouveau modèle vide. au DataContext). Pourquoi alors le setter n'est-il pas appelé à la valeur de DataContext d'origine (en particulier depuis que le changement de DataContext est reflété par le reste des liaisons)?


Eh bien, j'ai au moins compris pourquoi il ne fonctionnera pas :). L'objet que vous attachez (totalQuCanTityValidatorContext) ne fait pas partie de l'arbre visuel ou logique et, en tant que tel, il ne peut pas voir "myusercontrol" dans sa liaison. Vous devez spécifier une source directe mais comme votre source (la machine virtuelle elle-même) change que vous ne pouvez pas le lier directement. Ma suggestion (si vous le faites toujours dans xaml), c'est faire comme je l'ai signalé ci-dessus (juste la modifier afin de mieux fonctionner avec un VM modifié)


Droite - Réponse mise à jour - qui a pris un peu de suivi - merci pour le plaisir :)



7
votes

J'éviterais d'utiliser des règles de validation. Si vous avez besoin d'accéder aux informations dans la vue de la vue pour effectuer une validation, il est préférable de mettre la logique de validation dans la fenêtre même.

Vous pouvez faire votre implémentation de ViewModel idataerrrorinfo et activez simplement la validation des informations sur les informations sur la liaison.

Même si vous n'exécutez pas dans ce problème (très courant) de besoin d'informations contextuelles, les règles de validation ne sont pas vraiment un excellent moyen d'exprimer la validation: les règles de validation sont généralement liées à la logique commerciale ou au moins aux aspects sémantiques de vos informations. XAML semble être le mauvais endroit pour mettre de telles choses - pourquoi devrais-je mettre une règle d'entreprise dans le fichier source dont le travail principal est de déterminer la mise en page et la conception visuelle de mon application?

La logique de validation appartient plus loin dans votre application. Même la viewModel pourrait être la mauvaise couche, mais dans ce cas, vous pouvez simplement en faire la responsabilité de la viewModel de déterminer où trouver la logique de validation.


4 commentaires

J'aimerais convertir en cette méthodologie; C'est juste que je maintienne une application existante et je ne veux pas vraiment y réécrire à moins que je ne dois vraiment pas. Merci.


Assez juste. Dans ce cas, avez-vous essayé d'allumer le débogage de la liaison de données? BEA.STollnitz.com/blog/?p=52 parle à ce sujet, et Si vous utilisez VS 2010, vous devrez également transformer la sortie de débogage de la liaison de données sur, à l'aide de la fenêtre Outils-> Options, sous le débogage-> Fenêtre de sortie - dans les paramètres de trace WPF Régler la liaison à tous. Cela fournira des informations de diagnostic sur les progrès de la liaison de données, ce qui est généralement une excellente aide pour comprendre pourquoi une contrainte n'a pas fonctionné.


J'ai décidé de marquer cela comme la bonne réponse, même si cela ne convenait pas à ma condition particulière, je pense que c'est la bonne façon de procéder à l'avenir.


Joli ancien poste, mais voici la version archivée du poste que @iangriffiths liés à: web.archive.org/web/20090119093501/http://bea.stollnitz.com: 80 / ...



6
votes

Je viens de trouver une réponse parfaite!

Si vous définissez la propriété validationStep de la validationrule sur validationStep.UnupdatedValue, la valeur passée à la méthode de validation est en réalité une controlingexpression. Vous pouvez ensuite interroger la propriété Dataitem de l'objet BindingExpression pour obtenir le modèle auquel la liaison est liée.

Cela signifie que je peux maintenant valider la valeur qui a été attribuée avec les valeurs existantes d'autres propriétés que je veux.


0 commentaires

2
votes

Après quelques recherches, j'ai proposé le code suivant qui fonctionne exactement comme fonctionne la fonction DataerrorvalidationRule.

class VJValidationRule : System.Windows.Controls.ValidationRule
{
    public VJValidationRule()
    {
        //we need this so that BindingExpression is sent to Validate method
        base.ValidationStep = System.Windows.Controls.ValidationStep.UpdatedValue;
    }

    public override System.Windows.Controls.ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        System.Windows.Controls.ValidationResult result = System.Windows.Controls.ValidationResult.ValidResult;

        System.Windows.Data.BindingExpression bindingExpression = value as System.Windows.Data.BindingExpression;

        System.ComponentModel.IDataErrorInfo source = bindingExpression.DataItem as System.ComponentModel.IDataErrorInfo;

        if (source != null)
        {
            string msg = source[bindingExpression.ParentBinding.Path.Path];

            result = new System.Windows.Controls.ValidationResult(msg == null, msg); 
        }

        return result;
    }


0 commentaires

0
votes

Je sais que c'est une vieille question, mais j'étais dans la même situation que l'affiche initiale en maintenant une application existante et je ne voulais pas la réécrire totalement et j'ai fini par trouver un moyen de trouver un moyen qui fonctionne au moins dans ma situation. .

J'essayais de valider une valeur placée dans une zone de texte par l'utilisateur, mais je ne voulais pas valider la valeur au modèle si la valeur n'était pas valide. Cependant, afin de valider, je devais accéder à d'autres propriétés de l'objet DataContext pour savoir si l'entrée était valide ou non. P>

Ce que j'ai fini par faire était de créer une propriété sur la classe de validateur que j'avais créé que Peut contenir un objet du type que le DataContext devrait être. Dans ce gestionnaire, j'ai ajouté ce code: p>

        if (myFilter == null)
        { return new ValidationResult(false, "Error getting filter for validation, please contact program creators."); }


1 commentaires

P.s. Modification de la liaison à la "mise à jour" vous permettra d'accéder à la liaison à l'exception, mais cela en retourne également la valeur du modèle et vous perdez la valeur d'origine que je ne voulais pas que cela ne fonctionne pas pour moi.



0
votes

J'utilise une approche différente. Utilisé des objets congélables pour rendre vos reliures

p>

  public class BindingProxy : Freezable
    {
            

        
            static BindingProxy()
            {
                var sourceMetadata = new FrameworkPropertyMetadata(
                delegate(DependencyObject p, DependencyPropertyChangedEventArgs args)
                {
                    if (null != BindingOperations.GetBinding(p, TargetProperty))
                    {
                        (p as BindingProxy).Target = args.NewValue;
                    }
                });

                sourceMetadata.BindsTwoWayByDefault = false;
                sourceMetadata.DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;

                SourceProperty = DependencyProperty.Register(
                    "Source",
                    typeof(object),
                    typeof(BindingProxy),
                    sourceMetadata);

                var targetMetadata = new FrameworkPropertyMetadata(
                    delegate(DependencyObject p, DependencyPropertyChangedEventArgs args)
                    {
                        ValueSource source = DependencyPropertyHelper.GetValueSource(p, args.Property);
                        if (source.BaseValueSource != BaseValueSource.Local)
                        {
                            var proxy = p as BindingProxy;
                            object expected = proxy.Source;
                            if (!object.ReferenceEquals(args.NewValue, expected))
                            {
                                Dispatcher.CurrentDispatcher.BeginInvoke(
                                    DispatcherPriority.DataBind, 
                                    new Action(() =>
                                    {
                                        proxy.Target = proxy.Source;
                                    }));
                            }
                        }
                    });

                targetMetadata.BindsTwoWayByDefault = true;
                targetMetadata.DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
                TargetProperty = DependencyProperty.Register(
                    "Target",
                    typeof(object),
                    typeof(BindingProxy),
                    targetMetadata);
            }
          
public static readonly DependencyProperty SourceProperty;   
            public static readonly DependencyProperty TargetProperty;
       
            public object Source
            {
                get
                {
                    return this.GetValue(SourceProperty);
                }

                set
                {
                    this.SetValue(SourceProperty, value);
                }
            }

           
            public object Target
            {
                get
                {
                    return this.GetValue(TargetProperty);
                }

                set
                {
                    this.SetValue(TargetProperty, value);
                }
            }

            protected override Freezable CreateInstanceCore()
            {
                return new BindingProxy();
            }
        }

sHould This have the problem of binding the value too late after the application started. I use Blend Interactions to resolve the problem after the window loads 

<!-- begin snippet: js hide: false -->


0 commentaires

0
votes

J'utilise une autre approche. Utilisez des objets congélables pour rendre vos reliures xxx

comme pour le proxy de reliure, vous allez ici: Classe publique BindingProxy: gelable { xxx

}


0 commentaires