保存之前的WPF数据绑定

在我的WPF应用程序中,我有一些数据绑定文本框。 这些绑定的UpdateSourceTriggerLostFocus 。 该对象使用“文件”菜单进行保存。 我遇到的问题是,可以在文本框中input一个新的值,从文件菜单中select保存,并且永远不会保留新值(在文本框中可见的值),因为访问菜单不会从文本框中移除焦点。 我怎样才能解决这个问题? 有没有办法强制一个页面中的所有控件进行数据绑定?

@palehorse:好点。 不幸的是,我需要使用LostFocus作为我的UpdateSourceTrigger,以支持我想要的validationtypes。

@dmo:我已经想到了。 然而,对于一个相对简单的问题来说,它似乎是一个非常不合理的解决scheme。 另外,它要求在页面上有一些控件总是可见的以获得焦点。 我的应用程序是选项卡,但是,所以没有这样的控制很容易出现。

@Nidonocu:使用菜单没有移动焦点从TextBox混淆了我的事实。 然而,这是我所看到的行为。 以下简单的例子演示了我的问题:

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

假设你在窗口中有一个TextBox,并在其中有一个Savebutton的ToolBar。 假定TextBox的Text属性绑定到业务对象上的一个属性,绑定的UpdateSourceTrigger属性被设置为LostFocus的默认值,这意味着当TextBox失去input焦点时,绑定值被推回到业务对象属性。 此外,假定工具栏的保存button的Command属性设置为ApplicationCommands.Save命令。

在这种情况下,如果编辑文本框并用鼠标单击保存button,则会出现问题。 当单击工具栏中的button时,文本框不会失去焦点。 由于TextBox的LostFocus事件不会触发,因此Text属性绑定不会更新业务对象的源属性。

显然你不应该validation和保存一个对象,如果用户界面中最近编辑的值尚未被推入对象。 这是Karl解决的确切问题,通过在他的窗口中编写代码来手动查找带有焦点的TextBox并更新数据绑定的来源。 他的解决scheme工作得很好,但是让我想到了一个通用的解决scheme,在这种特殊情况之外也是有用的。 进入CommandGroup …

取自Josh Smith的CodeProject关于CommandGroup的文章

我发现从菜单的FocusScope删除范围依赖的菜单项导致文本框正确地失去焦点。 我不认为这适用于菜单中的所有项目,但肯定是保存或validation操作。

 <Menu FocusManager.IsFocusScope="False" > 

假设在标签序列中有多个控件,下面的解决scheme看起来是完整的和一般的(只是剪切和粘贴)…

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

这是一个丑陋的黑客,但也应该工作

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

此代码检查文本框是否有焦点…如果find1 …更新绑定源!

简单的解决scheme是更新Xaml代码,如下所示

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

你有没有尝试将UpdateSourceTrigger设置为PropertyChanged? 或者,你可以调用UpdateSOurce()方法,但是这似乎有点矫枉过正,并且破坏了TwoWay数据绑定的目的。

我遇到了这个问题,我发现的最好的解决scheme是将button(或任何其他组件,如MenuItem)的可调焦值更改为true:

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

它工作的原因是因为它强制button在调用命令之前得到集中,因此使TextBox 或任何其他UIElement松散焦点并引发失去焦点事件调用绑定被改变。

如果你正在使用有界的命令(正如我在我的例子中指出的那样),John Smith的很好的解决scheme将不太适合,因为你不能将StaticExtension绑定到有界属性(也就是DP)。

你可以在保存之前把焦点放在别的地方吗?

你可以通过在UI元素上调用focus()来做到这一点。

你可以专注于任何元素调用“保存”。 如果你的触发器是LostFocus,那么你必须把焦点移到某个地方。 保存的好处是不会被修改,对用户是有意义的。

在研究这个答案时,我有点困惑,你正在看到的行为正在发生,当然是点击文件菜单的行为,或者你应该把文本框放在什么位置并将其设置到菜单上?

最简单的方法是将焦点设置在某个地方
您可以立即设置焦点,但是将焦点设置在任何位置都会触发任何types控件上的LostFocus事件,并更新其内容:

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

另一种方法是获取焦点元素,从焦点元素获取绑定元素,并手动触发更新。 TextBox和ComboBox的一个例子(您需要添加任何需要支持的控件types):

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

你怎么看待这件事? 我相信我已经想出了一种通过反思使其更具通用性的方法。 我真的不喜欢维护一个像其他例子一样的列表的想法。

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

看到有什么问题呢?

既然我注意到这个问题仍然是一个非常通用的解决scheme,我尝试了各种解决scheme。

最后一个解决scheme是:只要需要在那里,UI的变化必须validation和更新到其源(检查closures一个窗口,执行保存操作,…)的变化,我称之为validationfunction,它做各种事情: – 确保焦点元素(如文本框,combobox,…)失去焦点,这将触发默认的更新源行为 – validation赋予validation函数的DependencyObject树中的任何控件 – 将焦点设置回原始的重点元素

如果一切正常(validation成功),函数本身将返回true – >您的原始操作(closures可选的询问确认,保存,…)可以继续。 否则,该函数将返回false,并且由于在一个或多个元素(在元素上的通用ErrorTemplate的帮助下)存在validation错误,您的操作无法继续。

代码(validationfunction基于文章“ 检测WPFvalidation错误” ):

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

我正在使用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 } 

它应该工作。