Pregunta

Ok, tengo un control que tiene una propiedad de iseditación que, por argumento, tiene una plantilla predeterminada que normalmente es un bloque de texto, pero cuando Isediting es verdadero, cambia en un cuadro de texto para la edición en el lugar. Ahora, cuando el control pierde el enfoque, si todavía se está editando, se supone que abandona el modo de edición y volverá a cambiar en la plantilla TextBlock. Bastante directo, ¿verdad?

Piense en el comportamiento de cambiar el nombre de un archivo en Windows Explorer o en su escritorio (que es lo mismo que sé ...) Ese es el comportamiento que queremos.

El problema es que no puede usar el evento LostFocus porque cuando cambia a otra ventana (o elemento que es un FocusManager) Lostfocus no se dispara ya que el control todavía tiene un enfoque lógico, por lo que eso no funcionará.

Si en su lugar usa LostKeyboardfocus, mientras eso resuelve el problema de 'Otro FocusManager', ahora tiene uno nuevo: cuando está editando y hace clic con el botón derecho en el cuadro de texto para mostrar el menú contextual, porque el menú contextual ahora tiene teclado Enfoque, su control pierde el enfoque del teclado, cae del modo de edición y cierra el menú contextual, ¡confundiendo al usuario!

Ahora he intentado configurar una bandera para ignorar el perdedor de keykboardfocus justo antes de que se abra el menú, luego usando ese fiag en el evento LostKeyboardfocus para determinarlo para salir del modo de edición o no, pero si el menú está abierto y hago clic en otro lugar en el Aplicación, dado que el control en sí no tenía el enfoque del teclado (el menú lo tenía), el control nunca obtiene otro evento LostKeyboardfocus, por lo que permanece en modo de edición. (Es posible que tenga que agregar un cheque cuando el menú se cierra para ver qué tiene enfoque y luego expulsarlo manualmente de EditMode si no es el control. Eso parece prometedor).

Entonces ... ¿alguien tiene alguna idea de cómo puedo codificar con éxito este comportamiento?

Marca

¿Fue útil?

Solución

Ok ... esto fue "divertido" como en el programador-fun. Un verdadero dolor en lo más destacado para darse cuenta, pero con una gran sonrisa en mi rostro que hice. (¡Es hora de obtener un poco de helado para mi hombro teniendo en cuenta que lo estoy dando palmaditas tan duro!: P)

De todos modos, es una cosa de varios pasos, pero es sorprendentemente simple una vez que descubres todo. La versión corta es que necesitas usar ambas cosas LostFocus y LostKeyboardFocus, no uno u otro.

LostFocus es fácil. Siempre que reciba ese evento, establezca IsEditing a falso. Hecho y hecho.

Menús con contexto y enfoque de teclado perdido

LostKeyboardFocus es un poco más complicado ya que el menú contextual para su control puede disparar eso en el control en sí (es decir, cuando se abre el menú contextual para su control, el control todavía tiene enfoque pero pierde el enfoque del teclado y, por lo tanto, LostKeyboardFocus incendios.)

Para manejar este comportamiento, anulas ContextMenuOpening (o maneje el evento) y establezca una bandera de nivel de clase que indique que el menú se está abriendo. (Yo suelo bool _ContextMenuIsOpening.) Entonces en el LostKeyboardFocus Anule (o evento), verifica esa bandera y si está configurada, simplemente lo borra y no hace nada más. Sin embargo, si no está configurado, eso significa que algo además de la apertura del menú contextual está causando que el control pierda el enfoque del teclado, por lo que en ese caso desea establecer IsEditing a falso.

Menús de contexto ya abiertos

Ahora hay un comportamiento extraño de que si el menú contextual para un control está abierto y, por lo tanto, el control ya ha perdido el enfoque del teclado como se describe anteriormente, si hace clic en otra parte de la aplicación, antes de que el nuevo control se enfoque, su control obtiene el enfoque del teclado primero , pero solo por una fracción de segundo, luego lo produce instantáneamente al nuevo control.

Esto realmente funciona para nuestra ventaja aquí, ya que esto significa que también obtendremos otro LostKeyboardFocus evento, pero esta vez el indicador _contextMenuOpening se establecerá en falso, y al igual que se describió anteriormente, nuestro LostKeyboardFocus el controlador se establecerá IsEditing a falso, que es exactamente lo que queremos. ¡Amo la casualidad!

Ahora se había cambiado el enfoque simplemente al control en el que hizo clic sin haber primero configurado el enfoque en el control que posee el menú contextual, entonces tendríamos que hacer algo como enganchar el ContextMenuClosing evento y verificación de qué control se enfocará a continuación, entonces solo habíamos establecido IsEditing Para falso si el control que pronto se centrará no era el que generaba el menú contextual, por lo que básicamente esquivamos una bala allí.

Advertencia: menús de contexto predeterminado

Ahora también está la advertencia de que si está utilizando algo como un cuadro de texto y no ha establecido explícitamente su propio menú contextual, entonces usted no consigue el ContextMenuOpening evento, que me sorprendió. Sin embargo, eso se soluciona fácilmente, simplemente creando un nuevo menú contextual con los mismos comandos estándar que el menú contextual predeterminado (por ejemplo, cortar, copiar, pegar, etc.) y asignándolo al cuadro de texto. Se ve exactamente igual, pero ahora obtienes el evento que necesitas para configurar la bandera.

Sin embargo, incluso allí tiene un problema como si estuviera creando un control de terceros reutilizable y el usuario de ese control quiere tener su propio menú contextual, puede establecer accidentalmente el suyo en una precedencia más alta y anulará el suyo !

El camino fue ya que el cuadro de texto es en realidad un elemento en el IsEditing plantilla para mi control, simplemente agregué un nuevo DP en el control exterior llamado IsEditingContextMenu que luego encuentro al cuadro de texto a través de un interno TextBox estilo, luego agregué un DataTrigger en ese estilo que verifica el valor de IsEditingContextMenu En el control exterior y si es nulo, configuré el menú predeterminado que acabo de crear anteriormente, que se almacena en un recurso.

Aquí está el estilo interno para el cuadro de texto (el elemento llamado 'raíz' representa el control exterior que el usuario realmente inserta en su 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>

Tenga en cuenta que debe establecer el enlace del menú contextual inicial en el estilo, no directamente en el cuadro de texto o de lo contrario el DataTrigger del estilo es reemplazado por el valor de conjunto directo que hace que el activador sea inútil y esté de vuelta al cuadrado si la persona usa 'NULL' para el menú contextual. (Si desea suprimir el menú, no usaría 'NULL' de todos modos. Lo configuraría en un menú vacío como NULL significa 'Usar el predeterminado'))

Entonces, ahora el usuario puede usar el regular ContextMenu propiedad cuando IsEditing es falso ... pueden usar el IsEditingContextMenu Cuando el isedito es verdadero, y si no especificaron un IsEditingContextMenu, el valor predeterminado interno que definimos se usa para el cuadro de texto. Dado que el menú contextual del cuadro de texto nunca puede ser nulo, es ContextMenuOpening Siempre se dispara y, por lo tanto, la lógica para apoyar este comportamiento funciona.

Como dije ... Dolor real en la lata para resolver todo esto, pero maldición si no tengo una sensación de logro realmente genial aquí.

Espero que esto ayude a otros aquí con el mismo problema. No dude en responder aquí o envíeme un PM con preguntas.

Marca

Otros consejos

Desafortunadamente, está buscando una solución simple para un problema complejo. El problema establecido simplemente es tener controles inteligentes de interfaz de usuario automatizando automáticamente que requieren un mínimo de interacción y "hacer lo correcto" cuando "cambie" de ellos.

La razón por la que es complejo es porque lo correcto es depende del contexto de la aplicación. El enfoque que se adopta WPF es darle a los conceptos lógicos de enfoque y enfoque de teclado y permitirle decidir cómo hacer lo correcto por usted en su situación.

¿Qué pasa si se abre el menú contextual? ¿Qué debería pasar si se abre el menú de la aplicación? ¿Qué pasa si el enfoque se cambia a otra aplicación? ¿Qué pasa si se abre una ventana emergente que pertenece al control local? ¿Qué pasa si el usuario presiona Intro para cerrar un diálogo? Todas estas situaciones se pueden manejar, pero todas desaparecen si tiene un botón de confirmación o el usuario debe presionar Enter para comprometerse.

Entonces tienes tres opciones:

  • Deje que el control permanezca en el estado de edición cuando tenga el enfoque lógico
  • Agregue un mecanismo explícito de confirmación o aplique
  • Maneje todos los casos desordenados que surgen cuando intenta admitir autocomisores

¿No sería más fácil:

    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...
    }

No estoy seguro del problema del menú contextual, pero estaba tratando de hacer algo similar y descubrí que usar la captura del mouse le da (casi) el comportamiento que busca:

Vea la respuesta aquí: ¿Cómo puede un control manejar un clic del mouse fuera de ese control?

No estoy seguro, pero esto podría ser útil. Tuve un problema similar con la caja combinada editable. Mi problema era que estaba usando el método de anulación de Onlostfocus que no se llamaba. Fix fue que había adjunto una devolución de llamada al evento Lostfocus y funcionó bien.

Pasé por aquí en mi búsqueda de una solución para un problema similar: tengo un ListBox que pierde el enfoque cuando el ContextMenu abre, y no quiero que eso suceda.

Mi solución simple era establecer Focusable a False, ambos para el ContextMenu y es MenuItems:

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

Espero que esto ayude a futuros buscadores ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top