Подтвердить что ты не робот

WPF перед записью

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

Ответ 1

Предположим, что у вас есть TextBox в окне и ToolBar с кнопкой Save в нем. Предположим, что свойство TextBoxs Text привязано к свойству на бизнес-объекте, а для свойства bindings UpdateSourceTrigger установлено значение по умолчанию LostFocus, что означает, что связанное значение возвращается к свойству бизнес-объекта, когда TextBox теряет фокус ввода. Кроме того, предположим, что кнопка "Сохранить панель инструментов" имеет свойство "Команда", заданное командой "ApplicationCommands.Save".

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

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

Взято из статьи Josh Smiths CodeProject о CommandGroup

Ответ 2

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

<Menu FocusManager.IsFocusScope="False" >

Ответ 3

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

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

Ответ 4

Это взломанный UGLY, но должен также работать

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

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

Ответ 5

Простым решением является обновление кода 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> 

Ответ 6

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

Ответ 7

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

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

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

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

Ответ 8

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

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

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

Ответ 9

Изучая это, чтобы ответить на него, я немного смущен тем, что поведение, которое вы видите, происходит, конечно, действие щелчка по меню "Файл" или что вам нужно расфокусировать текстовое поле и установить его в меню?

Ответ 10

Самый простой способ - установить фокус где-нибудь. Вы можете сразу установить фокус, но настройка фокуса в любом месте вызовет событие LostFocus на любом типе управления и обновит его содержимое:

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

Ответ 11

Что вы думаете об этом? Я считаю, что я понял способ сделать его более универсальным, используя отражение. Мне действительно не нравилась идея сохранить список, как некоторые из других примеров.

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

Посмотрите на какие-либо проблемы с этим?

Ответ 12

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

В конце концов тот, который сработал у меня: Всякий раз, когда возникает необходимость в том, что изменения пользовательского интерфейса должны быть проверены и обновлены до его источников (проверьте изменения при закрытии окна, выполняя операции сохранения,...), я вызываю функцию проверки, которая выполняет различные действия: - убедитесь, что сфокусированный элемент (например, текстовое поле, combobox,...) теряет фокус, который будет вызывать поведение по умолчанию по умолчанию - проверять любые элементы управления в дереве объекта 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;
    }
}

Ответ 13

Я использую BindingGroup.

XAML:

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

Он должен работать.