Frage

In meiner WPF-Anwendung habe ich eine Reihe datengebundener TextBoxen.Der UpdateSourceTrigger für diese Bindungen ist LostFocus.Das Objekt wird über das Menü Datei gespeichert.Das Problem, das ich habe, besteht darin, dass es möglich ist, einen neuen Wert in eine TextBox einzugeben, im Menü „Datei“ die Option „Speichern“ auszuwählen und den neuen Wert (den in der TextBox sichtbaren) niemals beizubehalten, da durch den Zugriff auf das Menü der Fokus nicht von der TextBox entfernt wird .Wie kann ich das beheben?Gibt es eine Möglichkeit, die Datenbindung aller Steuerelemente auf einer Seite zu erzwingen?

@palehorse:Guter Punkt.Leider muss ich LostFocus als meinen UpdateSourceTrigger verwenden, um die Art der Validierung zu unterstützen, die ich möchte.

@dmo:Daran hatte ich gedacht.Es scheint jedoch eine wirklich unelegante Lösung für ein relativ einfaches Problem zu sein.Außerdem ist es erforderlich, dass auf der Seite eine gewisse Kontrolle vorhanden ist, die immer sichtbar ist, um den Fokus zu erhalten.Meine Anwendung verfügt jedoch über Registerkarten, sodass eine solche Steuerung nicht ohne weiteres angezeigt wird.

@Nidonocu:Die Tatsache, dass die Verwendung des Menüs den Fokus nicht von der TextBox verlagerte, verwirrte mich ebenfalls.Das ist jedoch das Verhalten, das ich sehe.Das folgende einfache Beispiel verdeutlicht mein Problem:

<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; }
    }
}
War es hilfreich?

Lösung

  

Angenommen, Sie einen Text in einem Fenster haben, und eine Symbolleiste mit einer Schaltfläche Speicher darin. Angenommen, den Text-Eigenschaft des TextBox wird auf eine Eigenschaft auf einem Business-Objekt gebunden ist, und die verbindliche Update-Eigenschaft auf den Standardwert von Lost-Focus gesetzt, was bedeutet, dass der gebundene Wert zurück in der Business-Objekt-Eigenschaft gedrückt wird, wenn die TextBox Eingabefokus verliert. Es sei auch angenommen, dass die Symbolleiste der Save-Button seine Command-Eigenschaft hat gesetzt Befehl ApplicationCommands.Save.

     

In dieser Situation, wenn Sie die TextBox bearbeiten, und klicken Sie auf die Schaltfläche Speichern mit der Maus, ist es ein Problem. Wenn in einer Symbolleiste auf eine Schaltfläche klicken, wird der TextBox nicht Fokus verlieren. Da das Lost-Focus-Ereignis wird nicht die TextBox, die Bindung der Eigenschaft Text nicht die Quelle Eigenschaft des Business-Objekt aktualisieren.

     

Natürlich sollten Sie nicht überprüfen und ein Objekt speichern, wenn der zuletzt bearbeiteten Wert in der Benutzeroberfläche noch nicht in das Objekt gedrückt worden. Dies ist das genaue Problem Karl um gearbeitet hatte, von Code in seinem Fenster zu schreiben, das für eine TextBox mit Fokus und aktualisiert, um die Quelle der Datenbindung von Hand sieht. Seine Lösung funktionierte gut, aber es hat mich über eine generische Lösung zu denken, die auch außerhalb dieses speziellen Szenario nützlich wäre. Geben Sie Commandgroup ...

Genommen von Josh Smith Codeproject Artikel über Command

Andere Tipps

Ich fand, dass die Menüpunkte zu entfernen, die Rahmen sind aus dem FocusScope des Menüs hing das Textfeld verursacht korrekt zu verlieren konzentrieren. Ich würde nicht, dass dies für alle Elemente im Menü gilt, aber sicherlich für eine speichern oder validieren Aktion.

<Menu FocusManager.IsFocusScope="False" >

Unter der Annahme, daß es mehr als eine Kontrolle in der Tab-Sequenz ist, wird die folgende Lösung vollständig und allgemein sein (nur cut-and-paste) ...

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

Dies ist ein hässlicher Hack sollte aber auch funktionieren

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

Dieser Code prüft, ob ein TextBox den Fokus hat ... Wenn 1 gefunden wird ... aktualisieren, um die Bindungsquelle!

Einfache Lösung ist, den XAML-Code aktualisieren, wie unter

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

Haben Sie versucht, die Update zu Property Einstellung? Alternativ können Sie das Update () -Methode aufrufen, aber das scheint ein bisschen übertrieben und besiegt den Zweck TwoWay Datenbindung.

Ich habe in dieser Frage laufen und die beste Lösung, die ich gefunden habe, war die fokussierbare Wert der Taste (oder eine andere Komponente, wie MenuItem) auf true zu ändern:

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

Der Grund, es funktioniert, ist, weil es die Taste drückt konzentriert, bevor es den Befehl aufruft und macht daher die TextBox oder andere UIElement für diese Angelegenheit den Fokus zu verlieren und verloren Fokus Ereignis auslösen welche ruft die Bindung geändert werden.

Falls Sie beschränken Befehl verwenden (wie ich in meinem Beispiel zu zeigen wurde), große Lösung John Smith wird nicht sehr gut passen, da Sie nicht StaticExtension in begrenzten Eigenschaft binden können (noch DP).

Könnten Sie den Fokus woanders nur einstellen, bevor Sie speichern?

Sie können dies tun, indem Fokus () auf einem UI-Element aufrufen.

könnten Sie konzentrieren sich auf, was Element ruft die „Speichern“. Wenn Ihr Auslöser ist Lost-Focus dann muss man irgendwo den Fokus. Speichern Sie hat den Vorteil, dass sie nicht verändert und würde Sinn für den Benutzer machen.

In dieser Erforschung, sie zu beantworten, ich bin ein wenig verwirrt, dass das Verhalten Sie sehen, geschieht, sicherlich der Akt der das Menü Datei klicken, oder was haben, sollten Sie die Textbox unfocus und es in das Menü eingestellt?

Der einfachste Weg ist es, den Fokus irgendwo .
Sie können den Fokus zurück sofort, sondern löst die Fokuseinstellung überall das Lost-Focus-Ereignis auf jede Art von Kontrolle und machen es seine Sachen aktualisieren:

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

Eine weitere Möglichkeit wäre das fokussierte Element zu erhalten, das Bindeelement aus dem fokussierten Elemente zu erhalten, und löst das Update manuell. Ein Beispiel für TextBox und ComboBox (Sie jede Steuerungsart Sie unterstützen müssen hinzufügen müssten bis):

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

Was denken Sie darüber? Ich glaube, ich habe einen Weg gefunden, um es generische Verwendung von Reflexion etwas zu machen. Ich mag hatte die Idee, eine Liste wie einige der anderen Beispiele Aufrechterhaltung wirklich nicht.

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

Sehen Sie irgendwelche Probleme mit dem?

Da ich dieses Problem bemerkt ist immer noch ein Schmerz in dem Arsch auf eine sehr allgemeine Weise zu lösen, habe ich versucht, verschiedene Lösungen.

Schließlich eine, die für mich gearbeitet: Jedes Mal, wenn die Notwendigkeit besteht, dass Änderungen an der Benutzeroberfläche muss seine Quellen überprüft und aktualisiert werden (Check für Änderungen auf ein Fenster closeing, darstellende speichern Operationen, ...), nenne ich eine Validierungsfunktion, die verschiedene Dinge tut: - sicher ein fokussiertes Element machen (wie Textbox, Combobox, ...) verliert seinen Fokus, die Standard-Updateverhalten auslösen - Validierung alle Steuerelemente innerhalb des Baums des DependencyObject das die Validierungsfunktion gegeben ist - Set Fokus wieder auf das ursprüngliche fokussierte Element

Die Funktion selbst gibt true zurück, wenn alles in Ordnung ist (Validierung ist erfolgreich) -> Ihre ursprüngliche Aktion (closeing mit optional fragen Bestätigung, saveing, ...) fortgesetzt werden kann. Andernfalls wird die Funktion false zurück und die Aktion kann nicht fortgesetzt werden, weil es Validierungsfehler auf einem oder mehr Elementen (mit Hilfe eines generischen Errortemplate auf den Elementen) ist.

Der Code (Validierung Funktionalität wird auf den Artikel 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;
    }
}

Ich bin mit Binding.

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
}

Es sollte funktionieren.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top