2008-09-11 12 views
35

En mi aplicación WPF, tengo una cantidad de TextBoxes enlazados a datos. El UpdateSourceTrigger para estas fijaciones es LostFocus. El objeto se guarda utilizando el menú Archivo. El problema que tengo es que es posible ingresar un nuevo valor en un TextBox, seleccionar Guardar en el menú Archivo y nunca insistir en el nuevo valor (el visible en el TextBox) porque al acceder al menú no se elimina el foco del TextBox . ¿Cómo puedo arreglar esto? ¿Hay alguna manera de forzar todos los controles en una página para que se vinculen?WPF Databind Before Saving

@palehorse: Buen punto. Lamentablemente, necesito usar LostFocus como UpdateSourceTrigger para admitir el tipo de validación que deseo.

@dmo: Había pensado en eso. Parece, sin embargo, como una solución realmente poco elegante para un problema relativamente simple. Además, requiere que haya algún control en la página que siempre esté visible para recibir el foco. Sin embargo, mi aplicación tiene pestañas, por lo que este control no se presenta fácilmente.

@Nidonocu: El hecho de que usar el menú no movió el foco del TextBox me confundió también. Ese es, sin embargo, el comportamiento que estoy viendo. El siguiente ejemplo demuestra mi problema:

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

Respuesta

6

Supongamos que tiene un TextBox en una ventana y una barra de herramientas con un botón Guardar en él. Suponga que la propiedad Texto de TextBox está vinculada a una propiedad en un objeto comercial, y la propiedad UpdateSourceTrigger del enlace se establece en el valor predeterminado de LostFocus, lo que significa que el valor vinculado se retrotrae a la propiedad del objeto comercial cuando el TextBox pierde el foco de entrada. Además, suponga que el botón Guardar de la barra de herramientas tiene su propiedad Comando establecida en el comando Comandos de aplicación.Guardar.

En esa situación, si edita el TextBox y hace clic en el botón Guardar con el mouse, hay un problema. Al hacer clic en un botón en una barra de herramientas, TextBox no pierde el foco. Como el evento LostFocus de TextBox no se activa, el enlace de propiedad Text no actualiza la propiedad de origen del objeto comercial.

Obviamente, no debe validar y guardar un objeto si el valor editado más recientemente en la interfaz de usuario aún no se ha insertado en el objeto. Este es el problema exacto por el que Karl había trabajado, escribiendo código en su ventana que buscaba manualmente un TextBox con foco y actualizaba el origen del enlace de datos. Su solución funcionó bien, pero me hizo pensar en una solución genérica que también sería útil fuera de este escenario en particular.Introduzca CommandGroup ...

Tomado del artículo CodeProject de Josh Smith sobre CommandGroup

+4

Esta solución solo funciona para TextBox. ¿Hay alguna manera de hacerlo funcionar para cualquier control? –

2

Ha intentado establecer el UpdateSourceTrigger a PropertyChanged? Alternativamente, puede llamar al método UpdateSOurce(), pero eso parece un poco excesivo y frustra el propósito del enlace de datos TwoWay.

1

¿Podría establecer el foco en otro lugar justo antes de guardar?

Puede hacerlo llamando a focus() en un elemento de la interfaz de usuario.

Puede centrarse en cualquier elemento que invoque el "guardar". Si su desencadenador es LostFocus, debe mover el foco a alguna parte. Save tiene la ventaja de que no se modifica y tiene sentido para el usuario.

0

Al investigar esto para responderlo, estoy un poco confundido de que el comportamiento que está viendo está sucediendo, seguramente al hacer clic en el menú Archivo o lo que debe desenfocar el cuadro de texto y establecerlo en el menú?

7

Este es un hack feo, pero también debería funcionar

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

comprueba este código si un cuadro de texto tiene el foco ... Si 1 se encuentra ... actualizar la fuente de enlace!

+2

+1, esa es la solución que estoy usando en este momento. Para que sea un poco menos feo, recomiendo comprobar la expresión de enlace para 'null' antes de llamar a' UpdateSource' (ya que un TextBox independiente podría tener el foco actualmente). – Heinzi

0

La manera más fácil es establecer el foco en algún lugar.
Puede ajustar el foco de nuevo inmediatamente, pero establecer el foco en cualquier lugar activará el LostFocus-Evento en ningún tipo de control y hacer que actualizar sus cosas:

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

Otra forma sería la de obtener el centrado Elemento, obtenga el elemento de enlace del elemento enfocado y active la actualización manualmente. Un ejemplo de cuadro de texto y cuadro combinado (que tendría que añadir ningún tipo de control que necesita para apoyar):

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(); 
22

He encontrado que la eliminación de los elementos del menú que alcance dependía de la FocusScope del menú hace que el cuadro de texto que perder centrarse correctamente. No creo que esto se aplique a TODOS los elementos en el Menú, pero sin duda para guardar o validar una acción.

<Menu FocusManager.IsFocusScope="False" > 
+0

Esto resuelve el problema original (el mismo que estaba teniendo). Mi setter ahora recibe una llamada cuando se presionan los botones de menú y puedo dejar LostFocus como UpdateSourceTrigger. – Bob

+2

Para mí, parece ser la mejor solución presentada. Gracias. –

+0

oh wow ... respondí esto hace mucho tiempo ~ me alegro de poder ayudar :) – BigBlondeViking

14

Suponiendo que hay más de un control en el orden de tabulación, la siguiente solución parece ser completa y general (acaba de cortar y pegar) ...

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(); 
} 
+0

¿En qué método se debe agregar este fragmento de código? De esta manera parece ser una forma forzada, no importa si el otro control desea aceptar el foco o no, este código fuerza al control seleccionado actual a tomar el enfoque. – Cary

+0

Utilicé este recorte de código en mi método de ejecución del botón Guardar. Luego, otros cuadros de texto se perdieronFlujos y desencadenaron la fuente de actualización enlazada con esa propiedad – dush88c

0

¿Qué haces ¿piensa sobre esto? Creo que he descubierto una manera de hacerlo un poco más genérico usando la reflexión. Realmente no me gusta la idea de mantener una lista como algunos de los otros ejemplos.

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); 
     } 
    } 
} 

¿Ves algún problema con eso?

3

solución simple es actualizar el código XAML como se muestra a continuación

<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> 
0

Dado que he notado este problema sigue siendo un dolor en el culo para resolver en una forma muy genérica, he intentado varias soluciones.

Eventualmente uno que funcionó para mí: Cuando sea necesario que los cambios de la interfaz de usuario se validen y actualicen a sus fuentes (verifique los cambios al cerrar una ventana, realizar operaciones de guardado, ...), llamo a función de validación que hace varias cosas: - asegúrese de que un elemento enfocado (como cuadro de texto, cuadro combinado, ...) pierde su foco que desencadenará el comportamiento de fuente de actualizaciones predeterminado - validar los controles dentro del árbol del objeto DependencyObject que se otorga a la validación función - establecer el foco de nuevo en el elemento centrado original

La función en sí devuelve verdadera si todo está en orden (la validación es exitosa) -> su acción original (cerrar con la confirmación de solicitud opcional, guardar, ...) puede continuar. De lo contrario, la función devolverá false y su acción no podrá continuar porque hay errores de validación en uno o más elementos (con la ayuda de una ErrorTemplate genérica en los elementos).

El código (funcionalidad de validación se basa en el artículo Detecting WPF Validation Errors):

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; 
    } 
} 
2

me he encontrado con este problema y la mejor solución que he encontrado era cambiar el valor enfocable del botón (o cualquier otro componente, tal como MenuItem) true:

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

la razón funciona, es porque obliga al botón para conseguir centrado antes de invocar el comando y por lo tanto hace que el cuadro de texto o cualquier otro UIElement para el caso para perder su enfoque y elevar el evento de foco perdido que invoca el enlace que se va a cambiar.

En caso de que esté utilizando un comando limitado (como estaba señalando en mi ejemplo), la gran solución de John Smith no encajará muy bien ya que no puede unir StaticExtension en propiedad limitada (ni DP).

0

Estoy usando 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 
} 

Se debe trabajar.

Cuestiones relacionadas