Question

Dans mon application WPF, j'ai un certain nombre de zones de texte liées aux données.Le UpdateSourceTrigger pour ces liaisons est LostFocus.L'objet est enregistré à l'aide du menu Fichier.Le problème que j'ai est qu'il est possible de saisir une nouvelle valeur dans une TextBox, de sélectionner Enregistrer dans le menu Fichier et de ne jamais conserver la nouvelle valeur (celle visible dans la TextBox) car l'accès au menu ne supprime pas le focus de la TextBox. .Comment puis-je réparer cela?Existe-t-il un moyen de forcer la liaison de données avec tous les contrôles d'une page ?

@cheval pâle:Bon point.Malheureusement, je dois utiliser LostFocus comme UpdateSourceTrigger afin de prendre en charge le type de validation que je souhaite.

@dmo :J'y avais pensé.Cela semble cependant être une solution vraiment inélégante à un problème relativement simple.En outre, cela nécessite qu'il y ait un certain contrôle sur la page qui est toujours visible pour recevoir le focus.Cependant, ma candidature est à onglets, donc aucun contrôle de ce type ne se présente facilement.

@Nidonocu :Le fait que l'utilisation du menu ne déplace pas le focus de TextBox m'a également dérouté.C'est cependant le comportement que je constate.L'exemple simple suivant illustre mon problème :

<Window x:Class="WpfApplication2.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <ObjectDataProvider x:Key="MyItemProvider" />
    </Window.Resources>
    <DockPanel LastChildFill="True">
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="File">
                <MenuItem Header="Save" Click="MenuItem_Click" />
            </MenuItem>
        </Menu>
        <StackPanel DataContext="{Binding Source={StaticResource MyItemProvider}}">
            <Label Content="Enter some text and then File > Save:" />
            <TextBox Text="{Binding ValueA}" />
            <TextBox Text="{Binding ValueB}" />
        </StackPanel>
    </DockPanel>
</Window>
using System;
using System.Text;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication2
{
    public partial class Window1 : Window
    {
        public MyItem Item
        {
            get { return (FindResource("MyItemProvider") as ObjectDataProvider).ObjectInstance as MyItem; }
            set { (FindResource("MyItemProvider") as ObjectDataProvider).ObjectInstance = value; }
        }

        public Window1()
        {
            InitializeComponent();
            Item = new MyItem();
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show(string.Format("At the time of saving, the values in the TextBoxes are:\n'{0}'\nand\n'{1}'", Item.ValueA, Item.ValueB));
        }
    }

    public class MyItem
    {
        public string ValueA { get; set; }
        public string ValueB { get; set; }
    }
}
Était-ce utile?

La solution

Supposons que vous ayez une zone de texte dans une fenêtre et une barre d'outils contenant un bouton Enregistrer.Supposons que la propriété Text de TextBox soit liée à une propriété sur un objet métier et que la propriété UpdateSourceTrigger de la liaison soit définie sur la valeur par défaut de LostFocus, ce qui signifie que la valeur liée est repoussée vers la propriété de l'objet métier lorsque la TextBox perd le focus d'entrée.Supposons également que la propriété Command du bouton Enregistrer de la barre d’outils soit définie sur la commande ApplicationCommands.Save.

Dans cette situation, si vous modifiez le TextBox et cliquez sur le bouton Enregistrer avec la souris, il y a un problème.Lorsque vous cliquez sur un bouton dans une barre d'outils, la TextBox ne perd pas le focus.Étant donné que l’événement LostFocus de TextBox ne se déclenche pas, la liaison de propriété Text ne met pas à jour la propriété source de l’objet métier.

Évidemment, vous ne devez pas valider et enregistrer un objet si la valeur la plus récemment modifiée dans l'interface utilisateur n'a pas encore été insérée dans l'objet.C'est exactement le problème que Karl avait résolu, en écrivant du code dans sa fenêtre qui recherchait manuellement une TextBox avec le focus et mettait à jour la source de la liaison de données.Sa solution a bien fonctionné, mais elle m'a fait réfléchir à une solution générique qui serait également utile en dehors de ce scénario particulier.Entrez CommandGroup…

Tiré de l'article CodeProject de Josh Smith sur Groupe de commandes

Autres conseils

J'ai constaté que la suppression des éléments de menu dont la portée dépendait du FocusScope du menu entraînait une perte correcte du focus de la zone de texte.Je ne pense pas que cela s'applique à TOUS les éléments du menu, mais certainement pour une action de sauvegarde ou de validation.

<Menu FocusManager.IsFocusScope="False" >

En supposant qu'il y ait plus d'un contrôle dans la séquence d'onglets, la solution suivante semble complète et générale (juste copier-coller)...

Control currentControl = System.Windows.Input.Keyboard.FocusedElement as Control;

if (currentControl != null)
{
    // Force focus away from the current control to update its binding source.
    currentControl.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
    currentControl.Focus();
}

C'est un hack UGLY mais devrait aussi fonctionner

TextBox focusedTextBox = Keyboard.FocusedElement as TextBox;
if (focusedTextBox != null)
{
    focusedTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}

Ce code vérifie si une TextBox a le focus...Si 1 est trouvé...mettez à jour la source de liaison !

Une solution simple consiste à mettre à jour le code Xaml comme indiqué ci-dessous

    <StackPanel DataContext="{Binding Source={StaticResource MyItemProvider}}"> 
        <Label Content="Enter some text and then File > Save:" /> 
        <TextBox Text="{Binding ValueA, UpdateSourceTrigger=PropertyChanged}" /> 
        <TextBox Text="{Binding ValueB, UpdateSourceTrigger=PropertyChanged}" /> 
    </StackPanel> 

Avez-vous essayé de définir UpdateSourceTrigger sur PropertyChanged ?Alternativement, vous pouvez appeler la méthode UpdateSOurce(), mais cela semble un peu excessif et va à l'encontre de l'objectif de la liaison de données TwoWay.

J'ai rencontré ce problème et la meilleure solution que j'ai trouvée a été de modifier la valeur focalisable du bouton (ou de tout autre composant tel que MenuItem) en true :

<Button Focusable="True" Command="{Binding CustomSaveCommand}"/>

La raison pour laquelle cela fonctionne est parce qu'il force le bouton à se concentrer avant d'invoquer la commande et rend donc la TextBox ou tout autre UIElement d'ailleurs pour perdre leur focus et déclencher un événement de focus perdu qui appelle la modification de la liaison.

Dans le cas où vous utilisez une commande limitée (comme je le soulignais dans mon exemple), l'excellente solution de John Smith ne conviendra pas très bien puisque vous ne pouvez pas lier StaticExtension à une propriété limitée (ni DP).

Pourriez-vous définir le focus ailleurs juste avant d'enregistrer ?

Vous pouvez le faire en appelant focus() sur un élément de l'interface utilisateur.

Vous pouvez vous concentrer sur n'importe quel élément qui appelle la "sauvegarde".Si votre déclencheur est LostFocus, vous devez déplacer le focus quelque part.Save présente l’avantage de ne pas être modifié et d’avoir un sens pour l’utilisateur.

En recherchant ceci pour y répondre, je suis un peu confus quant au comportement que vous constatez, sûrement le fait de cliquer sur le menu Fichier ou quoi d'autre, devriez-vous déconcentrer la zone de texte et la définir dans le menu ?

Le moyen le plus simple est de mettre l'accent quelque part.
Vous pouvez rétablir le focus immédiatement, mais définir le focus n'importe où déclenchera l'événement LostFocus sur tout type de contrôle et faites-lui mettre à jour ses éléments :

IInputElement x = System.Windows.Input.Keyboard.FocusedElement;
DummyField.Focus();
x.Focus();

Une autre façon serait d'obtenir l'élément ciblé, d'obtenir l'élément de liaison de l'élément ciblé et de déclencher la mise à jour manuellement.Un exemple pour TextBox et ComboBox (vous devrez ajouter tout type de contrôle que vous devez prendre en charge) :

TextBox t = Keyboard.FocusedElement as TextBox;
if ((t != null) && (t.GetBindingExpression(TextBox.TextProperty) != null))
  t.GetBindingExpression(TextBox.TextProperty).UpdateSource();

ComboBox c = Keyboard.FocusedElement as ComboBox;
if ((c != null) && (c.GetBindingExpression(ComboBox.TextProperty) != null))
  c.GetBindingExpression(ComboBox.TextProperty).UpdateSource();

Que penses-tu de cela?Je crois avoir trouvé un moyen de le rendre un peu plus générique en utilisant la réflexion.Je n’ai vraiment pas aimé l’idée de maintenir une liste comme certains des autres exemples.

var currentControl = System.Windows.Input.Keyboard.FocusedElement;
if (currentControl != null)
{
    Type type = currentControl.GetType();
    if (type.GetMethod("MoveFocus") != null && type.GetMethod("Focus") != null)
    {
        try
        {
            type.GetMethod("MoveFocus").Invoke(currentControl, new object[] { new TraversalRequest(FocusNavigationDirection.Next) });
            type.GetMethod("Focus").Invoke(currentControl, null);
        }
        catch (Exception ex)
        {
            throw new Exception("Unable to handle unknown type: " + type.Name, ex);
        }
    }
}

Vous voyez des problèmes avec ça ?

Depuis que j'ai remarqué que ce problème était toujours difficile à résoudre de manière très générique, j'ai essayé différentes solutions.

Finalement, celui qui a fonctionné pour moi :Chaque fois qu'il est nécessaire que les modifications de l'interface utilisateur soient validées et mises à jour dans ses sources (vérifier les modifications lors de la fermeture d'une fenêtre, effectuer des opérations de sauvegarde, ...), j'appelle une fonction de validation qui fait diverses choses :- Assurez-vous qu'un élément ciblé (comme TextBox, ComboBox, ...) perd sa mise au point qui déclenchera le comportement de mise à jour par défaut - valider tous les contrôles dans l'arboresx élément

La fonction elle-même renvoie vrai si tout est en ordre (la validation est réussie) -> votre action originale (fermeture avec demande facultative de confirmation, sauvegarde, ...) peut continuer.Sinon la fonction retournera false et votre action ne pourra pas continuer car il y a des erreurs de validation sur un ou plusieurs éléments (à l'aide d'un ErrorTemplate générique sur les éléments).

Le code (la fonctionnalité de validation est basée sur l'article Détection des erreurs de validation WPF):

public static class Validator
{
    private static Dictionary<String, List<DependencyProperty>> gdicCachedDependencyProperties = new Dictionary<String, List<DependencyProperty>>();

    public static Boolean IsValid(DependencyObject Parent)
    {
        // Move focus and reset it to update bindings which or otherwise not processed until losefocus
        IInputElement lfocusedElement = Keyboard.FocusedElement;
        if (lfocusedElement != null && lfocusedElement is UIElement)
        {
            // Move to previous AND to next InputElement (if your next InputElement is a menu, focus will not be lost -> therefor move in both directions)
            (lfocusedElement as UIElement).MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
            (lfocusedElement as UIElement).MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
            Keyboard.ClearFocus();
        }

        if (Parent as UIElement == null || (Parent as UIElement).Visibility != Visibility.Visible)
            return true;

        // Validate all the bindings on the parent 
        Boolean lblnIsValid = true;
        foreach (DependencyProperty aDependencyProperty in GetAllDependencyProperties(Parent))
        {
            if (BindingOperations.IsDataBound(Parent, aDependencyProperty))
            {
                // Get the binding expression base. This way all kinds of bindings (MultiBinding, PropertyBinding, ...) can be updated
                BindingExpressionBase lbindingExpressionBase = BindingOperations.GetBindingExpressionBase(Parent, aDependencyProperty);
                if (lbindingExpressionBase != null)
                {
                    lbindingExpressionBase.ValidateWithoutUpdate();
                    if (lbindingExpressionBase.HasError)
                        lblnIsValid = false;
                }
            }
        }

        if (Parent is Visual || Parent is Visual3D)
        {
            // Fetch the visual children (in case of templated content, the LogicalTreeHelper will return no childs)
            Int32 lintVisualChildCount = VisualTreeHelper.GetChildrenCount(Parent);
            for (Int32 lintVisualChildIndex = 0; lintVisualChildIndex < lintVisualChildCount; lintVisualChildIndex++)
                if (!IsValid(VisualTreeHelper.GetChild(Parent, lintVisualChildIndex)))
                    lblnIsValid = false;
        }

        if (lfocusedElement != null)
            lfocusedElement.Focus();

        return lblnIsValid;
    }

    public static List<DependencyProperty> GetAllDependencyProperties(DependencyObject DependencyObject)
    {
        Type ltype = DependencyObject.GetType();
        if (gdicCachedDependencyProperties.ContainsKey(ltype.FullName))
            return gdicCachedDependencyProperties[ltype.FullName];

        List<DependencyProperty> llstDependencyProperties = new List<DependencyProperty>();
        List<FieldInfo> llstFieldInfos = ltype.GetFields(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static).Where(Field => Field.FieldType == typeof(DependencyProperty)).ToList();
        foreach (FieldInfo aFieldInfo in llstFieldInfos)
            llstDependencyProperties.Add(aFieldInfo.GetValue(null) as DependencyProperty);
        gdicCachedDependencyProperties.Add(ltype.FullName, llstDependencyProperties);

        return llstDependencyProperties;
    }
}

J'utilise BindingGroup.

XAML :

<R:RibbonWindow Closing="RibbonWindow_Closing" ...>

    <FrameworkElement.BindingGroup>
        <BindingGroup />
    </FrameworkElement.BindingGroup>

    ...
</R:RibbonWindow>

C#

private void RibbonWindow_Closing(object sender, CancelEventArgs e) {
    e.Cancel = !NeedSave();
}

bool NeedSave() {
    BindingGroup.CommitEdit();

    // Insert your business code to check modifications.

    // return true; if Saved/DontSave/NotChanged
    // return false; if Cancel
}

Cela devrait fonctionner.

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