Question

Ok, j'ai un contrôle qui a une propriété IsEditing qui, pour l'amour de l'argument a un modèle par défaut qui est normalement un bloc de texte, mais quand IsEditing est vrai, il Swaps dans une zone de texte pour l'édition en place. Maintenant, quand le contrôle perd le focus, si elle est toujours en édition, il est censé abandonner le mode d'édition et de retour d'échange dans le modèle de TextBlock. Assez simple, droit?

Pensez le comportement de renommer un fichier dans l'Explorateur Windows ou sur votre bureau (qui est la même chose que je sais ...) C'est le comportement que nous voulons.

Le problème est que vous ne pouvez pas utiliser l'événement LostFocus parce que lorsque vous passez à une autre fenêtre (ou un élément qui est un FocusManager) LostFocus ne se déclenche pas puisque le contrôle a toujours le focus logique, de sorte que ne fonctionnera pas.

Si vous au lieu d'utiliser LostKeyboardFocus, alors que cela ne résout la question « autre FocusManager », vous avez maintenant une nouvelle: lorsque vous modifiez et que vous faites un clic droit sur la zone de texte pour afficher le menu contextuel, car le menu contextuel a maintenant le focus clavier, votre contrôle perd le focus du clavier, des gouttes sur le mode d'édition et ferme le menu contextuel, confondant l'utilisateur!

Maintenant, je l'ai essayé de placer un drapeau d'ignorer le LostKeyboardFocus juste avant le menu ouvre, puis en utilisant cette FIAG en cas de LostKeyboardFocus pour déterminer à coup de pied sur le mode d'édition ou non, mais si le menu est ouvert et je clique ailleurs dans l'application, puisque le contrôle lui-même n'a pas eu plus le focus clavier (le menu l'avait) le contrôle ne devient jamais un autre événement LostKeyboardFocus il reste en mode d'édition. (Je dois ajouter un chèque lorsque le menu ferme pour voir ce qui a le focus alors le coup manuellement sur EditMode si ce n'est pas le contrôle. Cela semble prometteur.)

Alors ... quelqu'un a une idée comment je peux le code avec succès ce comportement?

Mark

Était-ce utile?

La solution

Ok ... c'était "fun" comme dans Programmeur-fun. Une vraie douleur dans le keester à comprendre, mais avec un énorme sourire agréable sur mon visage que je l'ai fait. (Temps pour obtenir une IcyHot pour mon épaule étant donné que je suis moi-même tapotant si dur: P)

Enfin il est une chose, mais en plusieurs étapes est étonnamment simple une fois que vous avez compris tout. La version courte est que vous devez utiliser à la fois LostFocus et LostKeyboardFocus, pas l'un ou l'autre.

LostFocus est facile. Chaque fois que vous recevez cet événement, jeu IsEditing false. Fait et fait.

Menus contextuels et Lost focus clavier

LostKeyboardFocus est un peu plus délicat depuis le menu contextuel de votre contrôle peut tirer que sur le contrôle lui-même (par exemple lorsque le menu contextuel de votre commande ouvre, le contrôle est toujours mise au point, mais il perd le focus du clavier et donc, les feux de LostKeyboardFocus. )

Pour gérer ce comportement, vous substituez ContextMenuOpening (ou gérer l'événement) et définir un indicateur de niveau de classe indiquant le menu est ouverture. (Je l'utilise bool _ContextMenuIsOpening.) Puis, dans le remplacement de LostKeyboardFocus (ou un événement), vous vérifiez que le drapeau et si elle est définie, vous supprimiez et ne rien faire d'autre. Si ce n'est pas réglé cependant, que cela signifie quelque chose en plus l'ouverture du menu contextuel est à l'origine du contrôle de perdre le focus du clavier, donc dans ce cas, vous ne voulez définir IsEditing false.

Menus Contexte déjà ouvert

Maintenant, il y a un comportement étrange que si le menu contextuel pour un contrôle est ouvert, et donc le contrôle a déjà perdu le focus du clavier comme décrit ci-dessus, si vous cliquez ailleurs dans l'application, avant que le nouveau contrôle obtient le focus, votre contrôle obtient focus clavier premier, mais seulement pour une fraction de seconde, il donne instantanément au nouveau contrôle.

Cela fonctionne en fait à notre avantage ici car cela signifie que nous allons aussi obtenir un autre événement LostKeyboardFocus mais cette fois le drapeau _ContextMenuOpening sera défini sur false, et comme décrit ci-dessus, notre gestionnaire de LostKeyboardFocus mettra alors IsEditing false, qui est exactement ce que nous voulons. J'adore un heureux hasard!

avait l'accent a été tout simplement suite à la commande que vous avez cliqué sans premier réglage à l'arrière de mise au point au contrôle propriétaire du menu contextuel, alors nous aurions dû faire quelque chose comme l'accrochage de l'événement ContextMenuClosing et vérifier ce que le contrôle va obtenir se concentrer à côté, alors que nous avions seulement ensemble IsEditing false si le contrôle bientôt à être axé sur n'a pas été celui qui a donné naissance dans le menu contextuel, donc nous avons esquivé une balle au fond là-bas.

Caveat: Contexte par défaut Menus

Maintenant, il y a aussi la mise en garde que si vous utilisez quelque chose comme une zone de texte et n'ont pas explicitement définir votre propre menu contextuel, alors vous ne pas obtenir l'événement ContextMenuOpening, ce qui m'a surpris . Cela est facilement fixe cependant, en créant simplement un nouveau menu contextuel avec les mêmes commandes standard que le menu contextuel par défaut (par exemple couper, copier, coller, etc.) et l'affecter à la zone de texte. Il ressemble exactement la même chose, mais maintenant vous obtenez l'événement que vous devez définir le drapeau.

Cependant, même là, vous avez un problème que si vous créez un contrôle réutilisable tiers et l'utilisateur de ce contrôle veut avoir leur propre menu contextuel, vous pouvez accidentellement configurer le vôtre à une priorité plus élevée et vous ll passer outre leur!

Le chemin autour qui était depuis la zone de texte est en fait un élément dans le modèle de IsEditing pour mon contrôle, je simplement ajouté une nouvelle DP sur le contrôle externe appelé IsEditingContextMenu que je puis se lient à la zone de texte via un style TextBox interne, je ajouté un DataTrigger dans ce style qui vérifie la valeur de IsEditingContextMenu sur le contrôle externe et si elle est nulle, je mis le menu par défaut que je viens de créer ci-dessus, qui est stocké dans une ressource.

Voici le internastyle l pour la zone de texte (L'élément nommé « racine » représente le contrôle externe que l'utilisateur insère effectivement dans leur XAML) ...

<Style x:Key="InlineTextbox" TargetType="TextBox">

    <Setter Property="OverridesDefaultStyle" Value="True"/>
    <Setter Property="FocusVisualStyle"      Value="{x:Null}" />
    <Setter Property="ContextMenu"           Value="{Binding IsEditingContextMenu, ElementName=Root}" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBoxBase}">

                <Border Background="White" BorderBrush="LightGray" BorderThickness="1" CornerRadius="1">
                    <ScrollViewer x:Name="PART_ContentHost" />
                </Border>

            </ControlTemplate>
        </Setter.Value>
    </Setter>

    <Style.Triggers>
        <DataTrigger Binding="{Binding IsEditingContextMenu, RelativeSource={RelativeSource AncestorType=local:EditableTextBlock}}" Value="{x:Null}">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu>
                        <MenuItem Command="ApplicationCommands.Cut" />
                        <MenuItem Command="ApplicationCommands.Copy" />
                        <MenuItem Command="ApplicationCommands.Paste" />
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </DataTrigger>
    </Style.Triggers>

</Style>

Notez que vous devez définir le menu contextuel initial obligatoire dans le style, pas directement sur la zone de texte, ou bien par la valeur directement-set se supplantée DataTrigger est le style qui rend le déclenchement inutile et vous êtes de retour droit à un carré si la personne de la null 'utilisations pour le menu contextuel. (Si vous voulez supprimer le menu, vous utilisez pas « null » de toute façon. Vous mettre la valeur d'un menu vide comme Utiliser le défaut »null)

Alors maintenant, l'utilisateur peut utiliser la propriété ContextMenu régulière quand IsEditing est faux ... ils peuvent utiliser le IsEditingContextMenu quand IsEditing est vrai, et si elles ne spécifiait pas un IsEditingContextMenu, la valeur par défaut interne que nous avons défini est utilisé pour la zone de texte. Depuis le menu contextuel de la zone de texte ne peut jamais réellement nulle, son ContextMenuOpening toujours des incendies, et donc la logique de soutenir le fonctionnement de ce comportement.

Comme je l'ai dit ... la vraie douleur dans la boîte comprendre tout cela, mais putain si je n'ai pas vraiment cool sentiment d'accomplissement ici.

J'espère que cela aide les autres ici avec la même question. Ne hésitez pas à répondre moi ici ou PM avec des questions.

Mark

Autres conseils

Malheureusement, vous êtes à la recherche d'une solution simple à un problème complexe. Le problème posé est tout simplement d'avoir des contrôles d'interface utilisateur intelligente-engagement automatique qui nécessitent un minimum d'interaction et de « faire la bonne chose » lorsque vous « se détourner » de leur part.

La raison pour laquelle il est complexe parce que ce que la bonne chose est dépend du contexte de l'application. L'approche WPF prend est de vous donner le focus logique et les concepts de mise au point clavier et pour vous permettre de décider comment vous faire la bonne chose dans votre situation.

Que faire si le menu contextuel est ouvert? Que faut-il si le menu de l'application est ouverte? Que faire si l'accent est mis sur une autre application? Que faire si une fenêtre est ouverte appartenant au contrôle local? Que faire si l'utilisateur appuie sur Entrée pour fermer une boîte de dialogue? Toutes ces situations peuvent être traitées, mais ils sont tous aller si vous avez un commettras bouton ou l'utilisateur doit appuyer sur Entrée pour valider.

Vous avez trois choix:

  • Laissez le séjour de contrôle dans l'état d'édition quand il a le focus logique
  • Ajoutez un mécanisme explicite validation ou appliquer
  • gérer tous les cas de désordre qui se posent lorsque vous essayez de support auto-validation

Ce ne serait pas juste plus facile à:

    void txtBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        TextBox txtBox = (sender as TextBox);

        if (e.NewFocus is ContextMenu && (e.NewFocus as ContextMenu).PlacementTarget == txtBox)
        {
            return;
        }

        // Rest of code for existing edit mode here...
    }

Je ne suis pas sûr de la question du menu contextuel mais je tentais de faire quelque chose de similaire et a constaté que l'aide capture de la souris vous donne (à peu près) le comportement que vous êtes après:

voir la réponse ici: Comment une poignée de commande d'un clic de souris en dehors de ce contrôle?

Je ne sais pas, mais cela pourrait être utile. J'ai eu un problème similaire avec zone de liste déroulante éditable. Mon problème est que j'utilisait la méthode de remplacement OnLostFocus qui était pas appelé. Fix a été j'avais joint un rappel à l'événement LostFocus et cela a fonctionné tout va bien.

Je suis passé par ici sur ma recherche d'une solution pour un problème similaire. J'ai un ListBox qui perd le focus lorsque le ContextMenu ouvre, et je ne veux pas que cela se produise

Ma solution simple était de mettre Focusable à False, aussi bien pour le ContextMenu et ses MenuItems:

<ContextMenu x:Key="QueryResultsMenu" Focusable="False">
    <ContextMenu.Resources>
        <Style TargetType="MenuItem">
            <Setter Property="Focusable" Value="False"/>
        </Style>
    </ContextMenu.Resources>
    <MenuItem ... />
</ContextMenu>

Espérons que cela aide les demandeurs d'avenir ...

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