143 votes

Mettre le focus sur une TextBox dans WPF à partir du modèle de vue

Tengo un TextBox y un Button à mon avis.

Maintenant, je vérifie une condition lors du clic sur le bouton et si la condition s'avère être fausse, j'affiche le message à l'utilisateur, et je dois ensuite placer le curseur sur l'élément TextBox contrôle.

if (companyref == null)
{
    var cs = new Lipper.Nelson.AdminClient.Main.Views.ContactPanels.CompanyAssociation(); 

    MessageBox.Show("Company does not exist.", "Error", MessageBoxButton.OK,
                    MessageBoxImage.Exclamation);

    cs.txtCompanyID.Focusable = true;

    System.Windows.Input.Keyboard.Focus(cs.txtCompanyID);
}

Le code ci-dessus se trouve dans le ViewModel.

El CompanyAssociation est le nom de la vue.

Mais le curseur n'est pas mis en place dans le fichier TextBox .

Le xaml est :

<igEditors:XamTextEditor Name="txtCompanyID" 
                         KeyDown="xamTextEditorAllowOnlyNumeric_KeyDown"
                         ValueChanged="txtCompanyID_ValueChanged"
                         Text="{Binding Company.CompanyId,
                                        Mode=TwoWay,
                                        UpdateSourceTrigger=PropertyChanged}"
                         Width="{Binding ActualWidth, ElementName=border}"
                         Grid.Column="1" Grid.Row="0"
                         VerticalAlignment="Top"
                         HorizontalAlignment="Stretch"
                         Margin="0,5,0,0"
                         IsEnabled="{Binding Path=IsEditable}"/>

<Button Template="{StaticResource buttonTemp1}"
        Command="{Binding ContactCommand}"
        CommandParameter="searchCompany"
        Content="Search"
        Width="80"
        Grid.Row="0" Grid.Column="2"
        VerticalAlignment="Top"
        Margin="0"
        HorizontalAlignment="Left"
        IsEnabled="{Binding Path=IsEditable}"/>

0 votes

Lorsque vous utilisez caliburn.micro este est une excellente solution.

296voto

Anvaka Points 9296

Permettez-moi de répondre à votre question en trois parties.

  1. Je me demande ce qu'est "cs.txtCompanyID" dans votre exemple ? S'agit-il d'un contrôle TextBox ? Si oui, alors vous êtes sur une mauvaise voie. En général, ce n'est pas une bonne idée d'avoir une référence à l'interface utilisateur dans votre ViewModel. Vous pouvez demander "Pourquoi ?" mais c'est une autre question à poster sur Stackoverflow :).

  2. La meilleure façon de trouver les problèmes avec Focus est... le débogage du code source .Net. Sans blague. Cela m'a permis de gagner beaucoup de temps à plusieurs reprises. Pour activer le débogage du code source .net, référez-vous à Shawn Bruke's blog.

  3. Enfin, l'approche générale que j'utilise pour définir le focus depuis le ViewModel est celle des propriétés attachées. J'ai écrit une propriété attachée très simple, qui peut être définie sur n'importe quel UIElement. Et elle peut être liée à la propriété "IsFocused" de ViewModel par exemple. C'est ici :

    public static class FocusExtension
    {
        public static bool GetIsFocused(DependencyObject obj)
        {
            return (bool) obj.GetValue(IsFocusedProperty);
        }
    
        public static void SetIsFocused(DependencyObject obj, bool value)
        {
            obj.SetValue(IsFocusedProperty, value);
        }
    
        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterAttached(
                "IsFocused", typeof (bool), typeof (FocusExtension),
                new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
    
        private static void OnIsFocusedPropertyChanged(
            DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
        {
            var uie = (UIElement) d;
            if ((bool) e.NewValue)
            {
                uie.Focus(); // Don't care about false values.
            }
        }
    }

    Maintenant, dans votre vue (en XAML), vous pouvez lier cette propriété à votre ViewModel :

    <TextBox local:FocusExtension.IsFocused="{Binding IsUserNameFocused}" />

J'espère que cela vous aidera :). Si ce n'est pas le cas, reportez-vous à la réponse n° 2.

A la vôtre.

8 votes

Bonne idée. Je dois définir IsUserNameFocused sur true, puis false à nouveau pour que cela fonctionne, est-ce correct ?

0 votes

Merci Sam. Cela dépend. Parfois, il suffit de le mettre à true une seule fois.

0 votes

@Anvaka comment faites-vous la liaison dans votre modèle de vue ? est-ce une propriété ou une ICommand ou quelque chose d'autre, je n'arrive pas à comprendre cette partie.

87voto

Zamotic Points 378

Je sais que la réponse à cette question a déjà été donnée des milliers de fois, mais j'ai apporté quelques modifications à la contribution d'Anvaka qui, je pense, aideront les autres qui ont eu les mêmes problèmes que moi.

Tout d'abord, j'ai modifié la propriété attachée ci-dessus comme suit :

public static class FocusExtension
{
    public static readonly DependencyProperty IsFocusedProperty = 
        DependencyProperty.RegisterAttached("IsFocused", typeof(bool?), typeof(FocusExtension), new FrameworkPropertyMetadata(IsFocusedChanged){BindsTwoWayByDefault = true});

    public static bool? GetIsFocused(DependencyObject element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        return (bool?)element.GetValue(IsFocusedProperty);
    }

    public static void SetIsFocused(DependencyObject element, bool? value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        element.SetValue(IsFocusedProperty, value);
    }

    private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var fe = (FrameworkElement)d;

        if (e.OldValue == null)
        {
            fe.GotFocus += FrameworkElement_GotFocus;
            fe.LostFocus += FrameworkElement_LostFocus;
        }

        if (!fe.IsVisible)
        {
            fe.IsVisibleChanged += new DependencyPropertyChangedEventHandler(fe_IsVisibleChanged);
        }

        if (e.NewValue != null && (bool)e.NewValue)
        {
            fe.Focus();
        }
    }

    private static void fe_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var fe = (FrameworkElement)sender;
        if (fe.IsVisible && (bool)fe.GetValue(IsFocusedProperty))
        {
            fe.IsVisibleChanged -= fe_IsVisibleChanged;
            fe.Focus();
        }
    }

    private static void FrameworkElement_GotFocus(object sender, RoutedEventArgs e)
    {
        ((FrameworkElement)sender).SetValue(IsFocusedProperty, true);
    }

    private static void FrameworkElement_LostFocus(object sender, RoutedEventArgs e)
    {
        ((FrameworkElement)sender).SetValue(IsFocusedProperty, false);
    }
}

La raison pour laquelle j'ai ajouté les références de visibilité étaient les onglets. Apparemment, si vous utilisiez la propriété attachée sur un autre onglet que l'onglet initialement visible, la propriété attachée ne fonctionnait pas tant que vous ne mettiez pas manuellement le contrôle au point.

L'autre obstacle était de créer un moyen plus élégant de réinitialiser la propriété sous-jacente à false lorsqu'elle perdait le focus. C'est là qu'interviennent les événements de perte de focus.

<TextBox            
    Text="{Binding Description}"
    FocusExtension.IsFocused="{Binding IsFocused}"/>

S'il y a une meilleure façon de gérer la question de la visibilité, faites-le moi savoir.

Note : Merci à Apfelkuacha pour la suggestion de mettre le BindsTwoWayByDefault dans la DependencyProperty. Je l'avais fait il y a longtemps dans mon propre code, mais je n'ai jamais mis à jour ce post. Le Mode=TwoWay n'est plus nécessaire dans le code WPF en raison de ce changement.

11 votes

Cela fonctionne bien pour moi, sauf que j'ai besoin d'ajouter une vérification "if (e.Source == e.OriginalSource)" dans le GotFocus/LostFocus, sinon cela s'empile (littéralement) lorsqu'il est utilisé sur mon UserControl, qui redirige le focus vers le composant interne. J'ai supprimé les contrôles de Visible, en acceptant le fait que cela fonctionne exactement comme la méthode .Focus(). Si la méthode .Focus() ne fonctionne pas, la liaison ne devrait pas fonctionner - et c'est correct pour mon scénario.

0 votes

VOUS ÊTES UN GÉNIE ABSOLU. J'ai passé une journée entière là-dessus. Merci ! J'ai essayé de trouver un moyen de mettre au point un élément et de le cacher lorsqu'il n'est pas au point. Mais cela fout vraiment en l'air les propriétés de dépendance. Cette solution a fonctionné pour moi.

1 votes

Je l'utilise dans WF 4.5. Sur IsFocusedChanged, j'ai un scénario (une activité est rechargée) où e.NewValue est nulle et lève une exception, il faut donc d'abord vérifier cela. Tout fonctionne bien avec cette modification mineure.

34voto

wizpert Points 850

Je pense que le meilleur moyen est de garder le principe MVVM propre, donc en gros, vous devez utiliser la classe Messenger fournie avec MVVM Light et voici comment l'utiliser :

dans votre viewmodel(exampleViewModel.cs):écrivez ce qui suit

 Messenger.Default.Send<string>("focus", "DoFocus");

Maintenant dans votre View.cs (pas le XAML, le view.xaml.cs) écrivez ce qui suit dans le constructeur

 public MyView()
        {
            InitializeComponent();

            Messenger.Default.Register<string>(this, "DoFocus", doFocus);
        }
        public void doFocus(string msg)
        {
            if (msg == "focus")
                this.txtcode.Focus();
        }

Cette méthode fonctionne très bien, avec moins de code et en respectant les normes MVVM.

9 votes

Si vous voulez garder le principe MVVM propre, vous n'écrirez pas de code dans votre code arrière en premier lieu. Je pense que l'approche des propriétés attachées est beaucoup plus propre. Elle n'introduit pas non plus beaucoup de chaînes magiques dans votre modèle de vue.

40 votes

El Nino : D'où te vient l'idée qu'il ne doit rien y avoir dans le code-behind de ta vue ? Tout ce qui est lié à l'interface utilisateur devrait être dans le code-behind de la vue. La mise au point des éléments de l'interface utilisateur devrait Définitivement être dans le code-behind de la vue. Laissez le modèle de vue déterminer quand envoyer le message ; laissez la vue déterminer ce qu'elle doit faire avec le message. Ce C'est ce que fait M-V-VM : séparer les préoccupations du modèle de données, de la logique métier et de l'interface utilisateur.

0 votes

Sur la base de cette suggestion, j'ai implémenté mon propre ViewCommandManager qui gère l'invocation de commandes dans les vues connectées. C'est essentiellement l'autre direction des commandes régulières, pour les cas où un ViewModel a besoin de faire une action dans sa ou ses vues. Il utilise la réflexion comme les commandes liées aux données et les WeakReferences pour éviter les fuites de mémoire. dev.unclassified.de/source/viewcommand (également sur CodeProject)

17voto

Leo Vildosola Points 101

Aucune d'entre elles n'a fonctionné exactement pour moi, mais pour le bénéfice des autres, voici ce que j'ai fini par écrire en me basant sur une partie du code déjà fourni ici.

L'utilisation serait la suivante :

<TextBox ... h:FocusBehavior.IsFocused="True"/>

Et la mise en œuvre serait la suivante :

/// <summary>
/// Behavior allowing to put focus on element from the view model in a MVVM implementation.
/// </summary>
public static class FocusBehavior
{
    #region Dependency Properties
    /// <summary>
    /// <c>IsFocused</c> dependency property.
    /// </summary>
    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached("IsFocused", typeof(bool?),
            typeof(FocusBehavior), new FrameworkPropertyMetadata(IsFocusedChanged));
    /// <summary>
    /// Gets the <c>IsFocused</c> property value.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>Value of the <c>IsFocused</c> property or <c>null</c> if not set.</returns>
    public static bool? GetIsFocused(DependencyObject element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        return (bool?)element.GetValue(IsFocusedProperty);
    }
    /// <summary>
    /// Sets the <c>IsFocused</c> property value.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    public static void SetIsFocused(DependencyObject element, bool? value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        element.SetValue(IsFocusedProperty, value);
    }
    #endregion Dependency Properties

    #region Event Handlers
    /// <summary>
    /// Determines whether the value of the dependency property <c>IsFocused</c> has change.
    /// </summary>
    /// <param name="d">The dependency object.</param>
    /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
    private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Ensure it is a FrameworkElement instance.
        var fe = d as FrameworkElement;
        if (fe != null && e.OldValue == null && e.NewValue != null && (bool)e.NewValue)
        {
            // Attach to the Loaded event to set the focus there. If we do it here it will
            // be overridden by the view rendering the framework element.
            fe.Loaded += FrameworkElementLoaded;
        }
    }
    /// <summary>
    /// Sets the focus when the framework element is loaded and ready to receive input.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
    private static void FrameworkElementLoaded(object sender, RoutedEventArgs e)
    {
        // Ensure it is a FrameworkElement instance.
        var fe = sender as FrameworkElement;
        if (fe != null)
        {
            // Remove the event handler registration.
            fe.Loaded -= FrameworkElementLoaded;
            // Set the focus to the given framework element.
            fe.Focus();
            // Determine if it is a text box like element.
            var tb = fe as TextBoxBase;
            if (tb != null)
            {
                // Select all text to be ready for replacement.
                tb.SelectAll();
            }
        }
    }
    #endregion Event Handlers
}

3voto

Shawn Points 21

Le problème est qu'une fois que la fonction IsUserNameFocused est définie sur true, elle ne sera jamais false. Ceci résout le problème en gérant le GotFocus et le LostFocus pour le FrameworkElement.

J'ai eu des problèmes avec le formatage du code source, alors voici un enlace

1 votes

J'ai changé "object fe = (FrameworkElement)d ;" en "FrameworkElement fe = (FrameworkElement)d ;" pour que l'intellisense fonctionne.

0 votes

Ça ne résout toujours pas le problème. L'élément reste concentré chaque fois que j'y reviens.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X