WPF Databind перед сохранением - PullRequest
36 голосов
/ 12 сентября 2008

В моем приложении 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; }
    }
}

Ответы [ 13 ]

23 голосов
/ 19 ноября 2009

Я обнаружил, что удаление пунктов меню, область действия которых зависит от FocusScope меню, приводит к тому, что текстовое поле теряет фокус. Я не думаю, что это относится ко ВСЕМ пунктам в Меню, но, безусловно, для действия сохранения или проверки.

<Menu FocusManager.IsFocusScope="False" >
14 голосов
/ 18 января 2011

Предполагая, что в последовательности вкладок имеется более одного элемента управления, следующее решение выглядит полным и общим (просто вырезать и вставить) ...

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();
}
7 голосов
/ 12 сентября 2008

Это уродливый хак, но также должен работать

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

Этот код проверяет, имеет ли TextBox фокус ... Если 1 найден ... обновите источник привязки!

6 голосов
/ 12 сентября 2008

Предположим, у вас есть TextBox в окне и панель инструментов с кнопкой Сохранить в нем. Предположим, что свойство Text TextBox связано со свойством бизнес-объекта, а для свойства UpdateSourceTrigger привязки установлено значение по умолчанию LostFocus, что означает, что привязанное значение передается обратно в свойство бизнес-объекта, когда TextBox теряет фокус ввода. Также предположим, что кнопка «Сохранить» на панели инструментов имеет свойство Command, установленное на команду ApplicationCommands.Save.

В этой ситуации, если вы редактируете TextBox и нажимаете кнопку «Сохранить» с помощью мыши, возникает проблема. При нажатии на кнопку на панели инструментов TextBox не теряет фокус. Поскольку событие TextBox LostFocus не запускается, привязка свойства Text не обновляет свойство источника бизнес-объекта.

Очевидно, что вы не должны проверять и сохранять объект, если последнее измененное значение в пользовательском интерфейсе еще не было помещено в объект. Это именно та проблема, над которой Карл работал, написав в своем окне код, который вручную искал TextBox с фокусом и обновил источник привязки данных. Его решение работало нормально, но оно заставило меня задуматься о универсальном решении, которое также было бы полезно вне этого конкретного сценария. Введите CommandGroup ...

Взято из статьи CodeProject Джоша Смита о CommandGroup

3 голосов
/ 22 сентября 2011

Простое решение - обновить код 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> 
2 голосов
/ 23 марта 2012

Я столкнулся с этой проблемой, и лучшее решение, которое я нашел, состояло в том, чтобы изменить фокусируемое значение кнопки (или любого другого компонента, такого как MenuItem) на true:

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

Причина, по которой это работает, заключается в том, что она заставляет кнопку фокусироваться, прежде чем она вызовет команду, и поэтому заставляет TextBox или любой другой элемент UIEle в этом отношении потерять фокус и вызвать событие потери фокуса, вызывает привязку, подлежащую изменению.

В случае, если вы используете ограниченную команду (как я указывал в моем примере), отличное решение Джона Смита не подойдет очень хорошо, поскольку вы не можете связать StaticExtension с ограниченным свойством (или DP).

2 голосов
/ 12 сентября 2008

Вы пытались установить UpdateSourceTrigger в PropertyChanged? В качестве альтернативы вы могли бы вызвать метод UpdateSOurce (), но это выглядит немного излишним и лишает цели связывания данных TwoWay.

1 голос
/ 12 сентября 2008

Не могли бы вы установить фокус где-то еще непосредственно перед сохранением?

Вы можете сделать это, вызвав focus () для элемента пользовательского интерфейса.

Вы можете сосредоточиться на любом элементе, который вызывает «сохранить». Если ваш триггер LostFocus, то вам нужно куда-то переместить фокус. Преимущество сохранения в том, что оно не изменено и имеет смысл для пользователя.

0 голосов
/ 13 ноября 2014

Я использую 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
}

Это должно работать.

0 голосов
/ 30 декабря 2011

Поскольку я заметил, что эту проблему все еще трудно решить в очень общей форме, я попробовал различные решения.

В конце концов, тот, который сработал для меня: Всякий раз, когда есть необходимость, чтобы изменения пользовательского интерфейса были проверены и обновлены до его источников (проверка изменений при закрытии окна, выполнении операций сохранения, ...), я вызываю функцию проверки, которая выполняет различные действия: - убедитесь, что сфокусированный элемент (например, текстовое поле, выпадающий список, ...) теряет фокус, что вызывает поведение источника обновлений по умолчанию - проверить все элементы управления в дереве объекта DependencyObject, который передается в функцию проверки - вернуть фокус на исходный фокусированный элемент

Сама функция возвращает true, если все в порядке (проверка прошла успешно) -> ваше первоначальное действие (закрытие с необязательным запросом подтверждения, сохранение, ...) может продолжаться. В противном случае функция вернет значение false, и ваше действие не может быть продолжено, поскольку имеются ошибки валидации для одного или нескольких элементов (с помощью универсального шаблона ErrorTemplate для элементов).

Код (функциональность проверки основана на статье Обнаружение ошибок проверки WPF ):

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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...