在我的WPF应用程序中,我有许多数据绑定TextBoxes。这些绑定的UpdateSourceTrigger
为LostFocus
。使用“文件”菜单保存对象。我遇到的问题是可以在TextBox中输入一个新值,从File菜单中选择Save,并且永远不会保留新值(TextBox中可见的值)因为访问菜单不会从TextBox中删除焦点。我怎样才能解决这个问题?是否有某种方法可以强制页面中的所有控件进行数据绑定?
@palehorse:好点。不幸的是,我需要使用LostFocus作为我的UpdateSourceTrigger,以支持我想要的验证类型。
@dmo:我想到了这一点。然而,对于一个相对简单的问题,它似乎是一个非常不优雅的解决方案。此外,它要求页面上有一些控件,它始终可见以接收焦点。然而,我的应用程序是标签式的,因此没有这样的控件可以随时出现。
@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; }
}
}
答案 0 :(得分:23)
我发现从菜单的FocusScope中删除范围所依赖的菜单项会导致文本框正确失去焦点。我不认为这适用于Menu中的所有项目,但肯定适用于保存或验证操作。
<Menu FocusManager.IsFocusScope="False" >
答案 1 :(得分:14)
假设标签序列中有多个控件,以下解决方案似乎是完整且一般的(只是剪切和粘贴)......
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();
}
答案 2 :(得分:7)
这是一个UGLY黑客,但也应该工作
TextBox focusedTextBox = Keyboard.FocusedElement as TextBox;
if (focusedTextBox != null)
{
focusedTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
此代码检查TextBox是否具有焦点...如果找到1 ...更新绑定源!
答案 3 :(得分:6)
假设您在窗口中有一个TextBox,并且其中有一个带有Save按钮的ToolBar。假设TextBox的Text属性绑定到业务对象上的属性,并且绑定的UpdateSourceTrigger属性设置为LostFocus的默认值,这意味着当TextBox失去输入焦点时,绑定值将被推回到业务对象属性。另外,假设ToolBar的Save按钮的Command属性设置为ApplicationCommands.Save命令。
在这种情况下,如果您编辑TextBox并使用鼠标单击“保存”按钮,则会出现问题。单击工具栏中的按钮时,TextBox不会失去焦点。由于TextBox的LostFocus事件不会触发,因此Text属性绑定不会更新业务对象的source属性。
显然,如果UI中最近编辑的值尚未推送到对象中,则不应验证和保存对象。这是Karl解决的确切问题,通过在他的窗口中编写代码来手动查找具有焦点的TextBox并更新数据绑定的源。他的解决方案运行良好,但它让我想到了一个通用的解决方案,在这个特定场景之外也是有用的。输入CommandGroup ...
取自Josh Smith的CodeProject关于CommandGroup
的文章答案 4 :(得分:3)
简单的解决方案是更新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>
答案 5 :(得分:2)
您是否尝试过将UpdateSourceTrigger设置为PropertyChanged?或者,您可以调用UpdateSOurce()方法,但这看起来有点矫枉过正,并且无法实现TwoWay数据绑定的目的。
答案 6 :(得分:2)
我遇到过这个问题,我发现最好的解决方案是将按钮(或任何其他组件,如MenuItem)的可聚焦值更改为true:
<Button Focusable="True" Command="{Binding CustomSaveCommand}"/>
它起作用的原因是因为它在调用命令之前强制按钮被聚焦,因此使TextBox 或任何其他UIElement 失去焦点并引发丢失的焦点事件它调用要更改的绑定。
如果你使用有界命令(正如我在我的例子中指出的那样),John Smith的优秀解决方案不太适合,因为你无法将StaticExtension绑定到有界属性(也不是DP)。 / em>的
答案 7 :(得分:1)
您可以在保存之前将焦点设置在其他地方吗?
您可以通过在UI元素上调用focus()来完成此操作。
你可以专注于任何调用“保存”的元素。如果您的触发器是LostFocus,那么您必须将焦点移动到某处。保存的优势在于它不会被修改并且对用户有意义。
答案 8 :(得分:0)
在研究这个问题时,我有点困惑,你所看到的行为正在发生,肯定是单击文件菜单的行为,或者你应该将文本框重置为什么并将其设置到菜单?
答案 9 :(得分:0)
最简单的方法是将焦点设置在某处 您可以立即设置焦点,但将焦点设置在任何位置将触发任何类型的控件上的LostFocus-Event并使其更新其内容:
IInputElement x = System.Windows.Input.Keyboard.FocusedElement;
DummyField.Focus();
x.Focus();
另一种方法是获取聚焦元素,从聚焦元素获取绑定元素,并手动触发更新。 TextBox和ComboBox的示例(您需要添加需要支持的任何控件类型):
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();
答案 10 :(得分:0)
您如何看待这个?我相信我已经找到了一种方法,可以使用反射使其更通用。我真的不喜欢像其他一些例子一样维护列表的想法。
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);
}
}
}
看到有什么问题吗?
答案 11 :(得分:0)
由于我注意到这个问题仍然是一个非常通用的解决问题,我尝试了各种解决方案。
最终为我效劳的是: 每当需要UI更改必须验证并更新到其源(在关闭窗口时检查更改,执行保存操作,...)时,我调用验证函数执行各种操作: - 确保聚焦元素(如文本框,组合框,......)失去焦点,这将触发默认的更新源行为 - 验证DependencyObject树中给予验证函数的任何控件 - 将焦点设置回原始聚焦元素
如果一切正常(验证成功),函数本身返回true - &gt;您的原始操作(关闭可选的询问确认,保存,...)可以继续。否则该函数将返回false并且您的操作无法继续,因为一个或多个元素上存在验证错误(借助于元素上的通用ErrorTemplate)。
代码(验证功能基于文章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;
}
}
答案 12 :(得分:0)
我正在使用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
}
它应该有用。