way two que property data context wpf data-binding

two - WPF Databind antes de guardar



wpf binding types (13)

En mi aplicación WPF, tengo una cantidad de TextBoxes con datos. UpdateSourceTrigger para estas vinculaciones 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?

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


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. Ingrese CommandGroup ...

Tomado del artículo de Josh Smith sobre CodeProject sobre CommandGroup


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


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

Puede hacer esto llamando a focus () en un elemento UI.

Podrías enfocarte 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.


¿Qué piensas 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?


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ú?


Descubrí que quitar los elementos de menú que dependen del alcance del FocusScope del menú hace que el cuadro de texto pierda el foco 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" >


Desde que noté que este problema todavía es un problema para resolver de una manera muy genérica, probé varias soluciones.

Finalmente, uno que funcionó para mí: siempre que sea necesario que los cambios en la interfaz de usuario se validen y actualicen a sus fuentes (verifique los cambios al cerrar una ventana, realizar operaciones de guardado, ...), invoco una función de validación que hace varios cosas: - asegúrese de que un elemento enfocado (como cuadro de texto, cuadro combinado, ...) pierda su enfoque que activará el comportamiento de fuente de actualización predeterminado - valide cualquier control dentro del árbol de DependencyObject que se le da a la función de validación - vuelva a establecer el foco elemento original enfocado

La función en sí vuelve verdadera si todo está en orden (la validación es exitosa) -> su acción original (cerrando con confirmación de solicitud opcional, guardando, ...) 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 (la funcionalidad de validación se basa en el artículo Detección de errores de validación de 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; } }


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

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

Este código comprueba si un cuadro de texto tiene foco ... Si se encuentra 1 ... ¡actualice la fuente de enlace!


Estoy usando BindingGroup.

XAML:

<R:RibbonWindow Closing="RibbonWindow_Closing" ...> <FrameworkElement.BindingGroup> <BindingGroup /> </FrameworkElement.BindingGroup> ... </R:RibbonWindow>

DO#

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 }

Deberia de funcionar.


La forma más fácil es establecer el foco en algún lugar .
Puede volver a establecer el foco inmediatamente, pero establecer el foco en cualquier lugar activará el LostFocus-Event en cualquier tipo de control y lo actualizará:

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

Otra forma sería obtener el elemento enfocado, obtener el elemento vinculante del elemento enfocado y activar la actualización manualmente. Un ejemplo para TextBox y ComboBox (necesitaría agregar cualquier tipo de control que necesite admitir):

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


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


Me he encontrado con este problema y la mejor solución que encontré fue cambiar el valor enfocable del botón (o cualquier otro componente como MenuItem) a verdadero:

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

La razón por la que funciona es porque obliga al botón a enfocarse antes de invocar el comando y, por lo tanto, hace que el TextBox u otro UIElement pierda su foco y genere un evento de foco perdido que invoca el enlace que se va a cambiar.

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


Suponiendo que hay más de un control en la secuencia de pestañas, la siguiente solución parece ser completa y general (solo 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(); }