38

在我的 WPF 应用程序中,我有许多数据绑定文本框。这些UpdateSourceTrigger绑定的LostFocus. 使用文件菜单保存对象。我遇到的问题是可以在 TextBox 中输入一个新值,从“文件”菜单中选择“保存”,并且永远不要保留新值(在 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; }
    }
}
4

12 回答 12

25

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

<Menu FocusManager.IsFocusScope="False" >
于 2009-11-19T16:59:16.347 回答
15

假设选项卡序列中有多个控件,则以下解决方案似乎是完整且通用的(只是剪切和粘贴)...

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();
}
于 2011-01-18T13:44:57.397 回答
8

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

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

此代码检查 TextBox 是否具有焦点...如果找到 1...更新绑定源!

于 2008-09-12T07:32:13.087 回答
6

假设您在一个窗口中有一个 TextBox 和一个带有 Save 按钮的 ToolBar。假设 TextBox 的 Text 属性绑定到业务对象上的属性,并且绑定的 UpdateSourceTrigger 属性设置为默认值 LostFocus,这意味着当 TextBox 失去输入焦点时,绑定的值会被推回业务对象属性。此外,假设 ToolBar 的 Save 按钮的 Command 属性设置为 ApplicationCommands.Save command。

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

显然,如果 UI 中最近编辑的值尚未推送到对象中,则不应验证和保存对象。这正是 Karl 解决的问题,他在窗口中编写代码,手动查找具有焦点的 TextBox 并更新数据绑定的源。他的解决方案运行良好,但它让我想到了一个通用解决方案,该解决方案在这个特定场景之外也很有用。进入 CommandGroup…</p>

摘自 Josh Smith 关于CommandGroup的 CodeProject 文章

于 2008-09-12T07:22:48.540 回答
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> 
于 2011-09-22T17:24:23.457 回答
2

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

于 2008-09-11T20:14:05.933 回答
2

我遇到了这个问题,我发现的最佳解决方案是将按钮(或任何其他组件,如 MenuItem)的可聚焦值更改为 true:

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

它起作用的原因是因为它强制按钮在调用命令之前获得焦点,因此使 TextBox或任何其他 UIElement失去焦点并引发调用绑定被更改的丢失焦点事件。

如果您使用有界命令(正如我在示例中所指出的那样),John Smith 的出色解决方案将不太适合,因为您无法将 StaticExtension 绑定到有界属性(也不是 DP)。

于 2012-03-23T13:33:44.907 回答
1

您可以在保存之前将焦点设置在其他地方吗?

您可以通过在 UI 元素上调用 focus() 来完成此操作。

您可以专注于调用“保存”的任何元素。如果您的触发器是 LostFocus,那么您必须将焦点移到某处。Save 的优点是它不会被修改并且对用户有意义。

于 2008-09-11T21:09:10.247 回答
1

由于我注意到这个问题仍然很难以非常通用的方式解决,因此我尝试了各种解决方案。

最终为我解决了一个问题:每当需要验证 UI 更改并将其更新到其源时(在关闭窗口、执行保存操作时检查更改...),我调用一个验证函数来执行各种操作事情: - 确保焦点元素(如文本框,组合框,...)失去其焦点,这将触发默认的 updatesource 行为 - 验证 DependencyObject 树中的任何控件,该控件提供给验证函数 - 将焦点设置回原始焦点元素

如果一切正常(验证成功),该函数本身返回 true -> 您的原始操作(通过可选的询问确认、保存等关闭)可以继续。否则,该函数将返回 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;
    }
}
于 2011-12-30T14:12:35.703 回答
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();
于 2008-10-23T13:35:59.110 回答
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);
        }
    }
}

看看有什么问题吗?

于 2011-06-01T00:47:03.703 回答
0

我正在使用绑定组。

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
}

它应该工作。

于 2014-11-13T06:27:24.643 回答