Database WPF prima del salvataggio

Nella mia applicazione WPF, ho un numero di caselle di testo del database. L’ UpdateSourceTrigger per questi binding è LostFocus . L’object viene salvato utilizzando il menu File. Il problema che ho è che è ansible inserire un nuovo valore in un TextBox, selezionare Salva dal menu File e non persistere mai il nuovo valore (quello visibile nel TextBox) perché l’accesso al menu non rimuove lo stato attivo dal TextBox . Come posso risolvere questo? C’è un modo per forzare tutti i controlli in una pagina al database?

@ Palehorse: buon punto. Sfortunatamente, ho bisogno di usare LostFocus come UpdateSourceTrigger per supportare il tipo di convalida che voglio.

@dmo: ci avevo pensato. Sembra, tuttavia, come una soluzione davvero inelegante per un problema relativamente semplice. Inoltre, richiede che ci sia un controllo sulla pagina che è sempre visibile per ricevere il focus. La mia applicazione è a tabs, tuttavia, quindi nessun controllo di questo tipo si presenta facilmente.

@ Nidonocu: Il fatto che l’uso del menu non abbia spostato lo stato attivo dal TextBox mi ha confuso. Questo è, comunque, il comportamento che sto vedendo. Il seguente semplice esempio dimostra il mio problema:

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

Supponiamo di avere un TextBox in una finestra e una barra degli strumenti con un pulsante Salva in essa. Si supponga che la proprietà Text del TextBox sia associata a una proprietà su un object business e la proprietà UpdateSourceTrigger del binding sia impostata sul valore predefinito di LostFocus, il che significa che il valore associato viene reimpostato alla proprietà dell’object business quando il TextBox perde lo stato attivo dell’input. Inoltre, si supponga che il pulsante Salva della barra degli strumenti abbia la sua proprietà Comando impostata sul comando ApplicationCommands.Save.

In questa situazione, se modifichi il TextBox e fai clic sul pulsante Salva con il mouse, c’è un problema. Quando si fa clic su un pulsante in una barra degli strumenti, la casella di testo non perde la messa a fuoco. Poiché l’evento LostFocus di TextBox non viene triggersto, l’associazione di proprietà Text non aggiorna la proprietà di origine dell’object business.

Ovviamente non è necessario convalidare e salvare un object se il valore modificato più recentemente nell’interfaccia utente non è stato ancora inserito nell’object. Questo è il problema esatto in cui Karl aveva lavorato, scrivendo codice nella sua finestra che cercava manualmente un TextBox con focus e aggiornava la fonte del binding dei dati. La sua soluzione ha funzionato bene, ma mi ha fatto pensare a una soluzione generica che sarebbe stata utile anche al di fuori di questo particolare scenario. Inserisci CommandGroup …

Tratto dall’articolo CodeProject di Josh Smith su CommandGroup

Ho scoperto che la rimozione delle voci di menu che dipendono dall’ambito FocusScope del menu fa sì che la casella di testo perda messa a fuoco correttamente. Non penserei che questo si applica a TUTTI gli articoli in Menu, ma sicuramente per un’azione salvata o valida.

  

Supponendo che ci sia più di un controllo nella sequenza di tabulazioni, la seguente soluzione sembra essere completa e generale (solo copia e incolla) …

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

Questo è un trucco UGLY ma dovrebbe funzionare anche

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

Questo codice controlla se un TextBox ha lo stato attivo … Se viene trovato 1 … aggiorna la sorgente di associazione!

La soluzione semplice è aggiornare il codice Xaml come mostrato di seguito

    

Hai provato a impostare UpdateSourceTrigger su PropertyChanged? In alternativa, è ansible chiamare il metodo UpdateSOurce (), ma sembra un po ‘eccessivo e vanifica lo scopo del databinding di TwoWay.

Ho riscontrato questo problema e la soluzione migliore che ho trovato è stata la modifica del valore selezionabile del pulsante (o di qualsiasi altro componente come MenuItem) su true:

  

Il motivo per cui funziona, è perché obbliga il pulsante a focalizzarsi prima che invochi il comando e quindi rende il TextBox o qualsiasi altro object UIElement per perdere la messa a fuoco e aumentare l’evento di focus perso che richiama il binding da modificare.

Nel caso in cui si stia utilizzando il comando limitato (come indicato nel mio esempio), l’ottima soluzione di John Smith non si adatta molto bene poiché non è ansible associare StaticExtension a proprietà limitate (né DP).

Potresti mettere a fuoco altrove prima di salvare?

Puoi farlo chiamando focus () su un elemento dell’interfaccia utente.

Potresti concentrarti su qualunque elemento invochi il “salvataggio”. Se il trigger è LostFocus, devi spostare il focus da qualche parte. Save ha il vantaggio di non essere modificato e avrebbe senso per l’utente.

Nella ricerca di questo per rispondere, sono un po ‘confuso che il comportamento che stai vedendo sta accadendo, sicuramente l’atto di fare clic sul menu File o su cosa dovresti mettere a fuoco la casella di testo e impostarla sul menu?

Il modo più semplice è di mettere a fuoco da qualche parte .
È ansible ripristinare immediatamente la messa a fuoco, ma l’impostazione della messa a fuoco in qualsiasi punto attiverà l’evento LostFocus su qualsiasi tipo di controllo e lo aggiornerà:

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

Un altro modo sarebbe quello di ottenere l’elemento focalizzato, ottenere l’elemento di rilegatura dall’elemento focalizzato e triggersre manualmente l’aggiornamento. Un esempio per TextBox e ComboBox (dovresti aggiungere qualsiasi tipo di controllo che devi supportare):

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

Cosa ne pensi di questo? Credo di aver trovato un modo per renderlo un po ‘più generico usando la riflessione. Non mi piaceva davvero l’idea di mantenere una lista come alcuni degli altri esempi.

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

Vedi qualche problema con quello?

Da quando ho notato che questo problema è ancora un rompicapo da risolvere in un modo molto generico, ho provato varie soluzioni.

Alla fine uno che ha funzionato per me: ogni volta che c’è bisogno che le modifiche all’interfaccia utente devono essere convalidate e aggiornate alle sue fonti (controlla le modifiche dopo aver chiuso una finestra, eseguendo operazioni di salvataggio, …), chiamo una funzione di convalida che fa varie cose: – assicurati che un elemento focalizzato (come la casella di testo, la casella combinata, …) perda il focus che attiverà il comportamento dell’aggiornamento predefinito – convalida i controlli all’interno dell’albero di DependencyObject che è dato alla funzione di convalida – imposta lo stato attivo sul elemento originale focalizzato

La funzione stessa restituisce true se tutto è in ordine (la convalida ha esito positivo) -> la tua azione originale (chiudendo con richiesta di conferma opzionale, salvando, …) può continuare. Altrimenti la funzione restituirà false e la tua azione non potrà continuare perché ci sono errori di validazione su uno o più elementi (con l’aiuto di un ErrorTemplate generico sugli elementi).

Il codice (la funzionalità di convalida si basa sull’articolo Rilevazione degli errori di convalida WPF ):

 public static class Validator { private static Dictionary> gdicCachedDependencyProperties = new Dictionary>(); 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 GetAllDependencyProperties(DependencyObject DependencyObject) { Type ltype = DependencyObject.GetType(); if (gdicCachedDependencyProperties.ContainsKey(ltype.FullName)) return gdicCachedDependencyProperties[ltype.FullName]; List llstDependencyProperties = new List(); List 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; } } 

Sto usando BindingGroup.

XAML:

     ...  

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 } 

Dovrebbe funzionare.