保存前的WPF数据绑定

保存前的WPF数据绑定,wpf,data-binding,Wpf,Data Binding,在我的WPF应用程序中,我有许多数据绑定的文本框。这些绑定的UpdateSourceTrigger是LostFocus。使用“文件”菜单保存对象。我遇到的问题是,可以在文本框中输入新值,从“文件”菜单中选择“保存”,并且永远不会保留新值(文本框中可见的值),因为访问该菜单不会从文本框中移除焦点。我怎样才能解决这个问题?有没有办法强制页面中的所有控件进行数据绑定 @帕勒霍斯:说得好。不幸的是,为了支持我想要的验证类型,我需要使用LostFocus作为我的UpdateSourceTrigger @d

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

@帕勒霍斯:说得好。不幸的是,为了支持我想要的验证类型,我需要使用LostFocus作为我的UpdateSourceTrigger

@dmo:我早就想到了。然而,对于一个相对简单的问题来说,这似乎是一个非常不雅观的解决方案。此外,它还要求页面上有一些控件,这些控件始终可见以接收焦点。然而,我的应用程序是选项卡式的,因此没有这样的控件能够自动出现

@Nidonocu:使用菜单并没有将焦点从文本框中移开这一事实也让我感到困惑。然而,这就是我所看到的行为。下面的简单示例演示了我的问题:



您是否尝试过将UpdateSourceTrigger设置为PropertyChanged?或者,您也可以调用UpdateSOurce()方法,但这似乎有点过火,无法达到双向数据绑定的目的。

在保存之前,是否可以在其他地方设置焦点

可以通过对UI元素调用focus()来实现这一点

您可以关注调用“save”的任何元素。如果触发器失去焦点,则必须将焦点移到某个位置。Save的优点是它不被修改,对用户来说是有意义的

假设窗口中有一个文本框,工具栏上有一个保存按钮。假设TextBox的Text属性绑定到业务对象上的属性,并且绑定的UpdateSourceTrigger属性设置为LostFocus的默认值,这意味着当TextBox失去输入焦点时,绑定的值被推回业务对象属性。另外,假设工具栏的“保存”按钮的“命令”属性设置为ApplicationCommand.Save命令

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

显然,如果UI中最近编辑的值尚未推入对象,则不应验证和保存对象。这正是Karl解决的问题,他在窗口中编写代码,手动查找带有焦点的文本框并更新数据绑定的源。他的解决方案工作得很好,但它让我想到了一个通用的解决方案,它在这个特定场景之外也很有用。输入CommandGroup


摘自Josh Smith关于

的CodeProject文章,这是一个丑陋的黑客行为,但也应该有效

TextBox focusedTextBox = Keyboard.FocusedElement as TextBox;
if (focusedTextBox != null)
{
    focusedTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
此代码检查文本框是否具有焦点。。。如果找到1。。。更新绑定源

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

IInputElement x = System.Windows.Input.Keyboard.FocusedElement;
DummyField.Focus();
x.Focus();
另一种方法是获取聚焦元素,从聚焦元素获取绑定元素,并手动触发更新。TextBox和ComboBox示例(您需要添加任何需要支持的控件类型):


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

<Menu FocusManager.IsFocusScope="False" >

假设选项卡序列中有多个控件,则以下解决方案看起来是完整的、通用的(只需剪切和粘贴)


你觉得这个怎么样?我相信我已经找到了一种方法,使用反射使它更通用。我真的不喜欢像其他一些例子那样维护一个列表

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

看到这个问题了吗?

简单的解决方案是更新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> 

因为我注意到这个问题仍然是一个很难用非常通用的方法解决的问题,所以我尝试了各种解决方案

最终,我找到了一个适合我的方法: 每当需要验证UI更改并将其更新到其源时(在关闭窗口、执行保存操作等时检查更改),我都会调用一个验证函数,它可以执行各种操作: -确保焦点元素(如textbox、combobox等)失去焦点,这将触发默认的updatesource行为 -验证提供给验证函数的DependencyObject树中的任何控件 -将焦点设置回原始焦点元素

如果一切正常(验证成功),函数本身将返回true->您的原始操作(关闭时可选择询问确认、保存等)可以继续。否则,函数将返回false,您的操作将无法继续,因为一个或多个元素上存在验证错误(借助于元素上的通用ErrorTemplate)

代码(验证功能基于文章):

公共静态类验证程序
{
私有静态字典GDICCachedPendencyProperties=new Dictionary();
公共静态布尔值有效(DependencyObject父对象)
{
//移动焦点并重置它以更新绑定
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();
}
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);
        }
    }
}
    <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> 
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;
    }
}
<Button Focusable="True" Command="{Binding CustomSaveCommand}"/>
<R:RibbonWindow Closing="RibbonWindow_Closing" ...>

    <FrameworkElement.BindingGroup>
        <BindingGroup />
    </FrameworkElement.BindingGroup>

    ...
</R:RibbonWindow>
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
}