Domanda

Nella mia applicazione WPF, ho un certo numero di databound Caselle di testo.Il UpdateSourceTrigger per queste associazioni è LostFocus.L'oggetto viene salvato utilizzando il menu File.Il problema che ho è che è possibile immettere un nuovo valore in una casella di testo, selezionare Salva dal menu File, e mai persistono il nuovo valore (visibile nella casella di testo), perché per accedere al menu di non disattivare la casella di testo.Come posso risolvere questo problema?C'è qualche modo per forzare tutti i controlli in una pagina a databind?

@palehorse:Un buon punto.Purtroppo, ho bisogno di usare LostFocus come il mio UpdateSourceTrigger per sostenere il tipo di convalida voglio.

@dmo:Avevo pensato anche a questo.Sembra, tuttavia, come un davvero poco elegante per una soluzione relativamente semplice problema.Inoltre, si richiede che ci sia un certo controllo sulla pagina che è sempre visibile per ricevere la messa a fuoco.La mia domanda è a schede, tuttavia, in modo che nessun controllo prontamente si presenta.

@Nidonocu:Il fatto che utilizzando il menu non sposta il focus dalla casella di testo confuso me....Che è, tuttavia, il comportamento di sto vedendo.Il semplice esempio che segue viene illustrato il mio 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; }
    }
}
È stato utile?

Soluzione

Si supponga di disporre di una casella di testo in una finestra, e una Barra degli strumenti con un pulsante Salva in esso.Assumere la proprietà di Testo TextBox è associato a una proprietà di un oggetto di business, e l'associazione è UpdateSourceTrigger proprietà è impostata sul valore predefinito di LostFocus, il che significa che il valore associato è spinto indietro per il business oggetto di proprietà, quando la TextBox perde lo stato attivo per l'input.Si supponga, inoltre, che la Barra degli strumenti il pulsante Salva è la sua proprietà di Comando set per ApplicationCommands.Il comando salva.

In tale situazione, se si modifica la casella di testo e fare clic sul pulsante Salva con il mouse, c'è un problema.Quando si fa clic su un Pulsante di una Barra degli strumenti, la casella di testo non perdere la concentrazione.Dal momento che la TextBox evento LostFocus non il fuoco, la proprietà Text associazione non aggiornare l'origine delle proprietà dell'oggetto business.

Ovviamente non si deve convalidare e salvare un oggetto se il più recente valore modificato nell'interfaccia utente non è ancora stato inserito nell'oggetto.Questo è il problema esatto Karl aveva lavorato in giro, scrivendo il codice nella finestra che manualmente guardato per una casella di testo con attenzione e aggiornata fonte di associazione dati.La sua soluzione ha funzionato bene, ma mi ha fatto pensare a una soluzione generica che sarebbe utile anche al di fuori di questo particolare scenario.Inserire CommandGroup...

Preso da Josh Smith articolo su CodeProject CommandGroup

Altri suggerimenti

Ho trovato che la rimozione delle voci di menu che sono campo di applicazione dipendeva dal FocusScope del menu cause casella di testo per perdere la messa a fuoco correttamente.Io non credo che questo si applica a TUTTI gli elementi nel Menu, ma certamente per un salvataggio o confermare azione.

<Menu FocusManager.IsFocusScope="False" >

Supponendo che non vi è più di un controllo nella scheda di sequenza, la seguente soluzione sembra essere completa e generale (solo taglia-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 BRUTTO hack ma dovrebbe funzionare anche

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

Questo codice controlla se una casella di testo è a fuoco...Se 1 è trovato...aggiornamento dell'associazione fonte!

Soluzione più semplice è aggiornare il codice Xaml, come illustrato di seguito

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

Hai provato impostazione UpdateSourceTrigger per PropertyChanged?In alternativa, si può chiamare il UpdateSOurce() il metodo, ma che sembra un po ' eccessivo e sconfigge lo scopo di Reciproca databinding.

Ho eseguito in questo problema e la soluzione migliore che ho trovato è stato quello di cambiare il attivabile valore del pulsante (o qualsiasi altro componente, ad esempio un MenuItem) a true:

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

Il motivo per cui funziona è perché costringe il pulsante per ottenere la messa a fuoco prima di richiamare il comando e quindi rende la TextBox o qualsiasi altro UIElement per quella materia a perdere la messa a fuoco e aumentare lost focus evento che richiama il legame di essere cambiato.

Nel caso In cui si utilizza delimitata comando (come mi faceva notare nel mio esempio), John Smith, il grande soluzione non è adatta molto bene in quanto non riesco a collegare il StaticExtension in delimitata proprietà (né DP).

Si potrebbe impostare la messa a fuoco da qualche altra parte appena prima di salvare?

È possibile farlo chiamando focus() su un elemento dell'interfaccia utente.

Si potrebbe concentrarsi su qualunque elemento richiama il "salva".Se il trigger è Disattivato quindi è necessario spostare il focus da qualche parte.Salva ha il vantaggio che non è modificata e avrebbe senso per l'utente.

Nella ricerca di questa risposta, sono un po ' confuso, che il comportamento si sta vedendo sta accadendo, sicuramente l'atto di clic sul menu File o quello che si dovrebbe unfocus la casella di testo e impostare il menu?

Il modo più semplice è quello di impostare la messa a fuoco da qualche parte.
È possibile impostare la messa a fuoco indietro immediatamente, ma la messa a fuoco ovunque attiverà il LostFocus Evento qualsiasi tipo di controllo e farlo aggiornare la sua roba:

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

Un altro modo sarebbe quello di ottenere il concentrato elemento, ottenere l'elemento vincolante da la focalizzata elemento, e attivare manualmente l'aggiornamento.Un esempio per il controllo TextBox e ComboBox (si avrebbe bisogno di aggiungere qualsiasi tipo di controllo è necessario per il supporto):

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 pensate di questa?Credo di aver trovato un modo per rendere un po ' più generico utilizzo di riflessione.Io davvero non mi piace l'idea di mantenere una lista come alcuni 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);
        }
    }
}

Vedere eventuali problemi con quello?

Dal momento che ho notato che questo problema è ancora un dolore nel culo per risolvere in modo generico, ho provato varie soluzioni.

Finalmente uno che ha funzionato per me:Ogni volta che il bisogno c'è che le modifiche dell'interfaccia utente devono essere validati e aggiornati per le sue fonti (Verificare le modifiche all'chiude una finestra, eseguire le operazioni di Salvataggio, ...), io la chiamo una funzione di validazione che fa diverse cose:- assicurarsi che un particolare elemento (come textbox, combobox, ...), perde il suo focus, che attivano di default updatesource comportamento - convalidare i controlli all'interno della struttura di DependencyObject che è dato per la funzione di convalida - impostare la messa a fuoco originale focalizzata elemento

La funzione restituisce true se tutto è in ordine (la validazione è successo) -> originale azione (chiude con opzionale chiedendo conferma, saveing, ...) in grado di continuare.Altrimenti la funzione restituisce false e che la tua azione non può continuare, perché ci sono errori di validazione su uno o più elementi (con l'aiuto di un generico ErrorTemplate su elementi).

Il codice (funzionalità di validazione si basa sull'articolo La rilevazione di WPF Errori di Validazione):

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

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

Dovrebbe funzionare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top