문제

내 WPF 응용 프로그램에는 여러 개의 데이터 바인딩된 TextBox가 있습니다.그만큼 UpdateSourceTrigger 이러한 바인딩의 경우 LostFocus.개체는 파일 메뉴를 사용하여 저장됩니다.문제는 TextBox에 새 값을 입력하고 파일 메뉴에서 저장을 선택한 다음 메뉴에 액세스해도 TextBox에서 포커스가 제거되지 않기 때문에 새 값(TextBox에 표시되는 값)을 유지하지 않을 수 있다는 것입니다. .이 문제를 어떻게 해결할 수 있나요?페이지의 모든 컨트롤을 강제로 데이터 바인딩하는 방법이 있습니까?

@창백말:좋은 지적.안타깝게도 원하는 유효성 검사 유형을 지원하려면 LostFocus를 UpdateSourceTrigger로 사용해야 합니다.

@dmo:나는 그것을 생각했었다.그러나 이는 상대적으로 간단한 문제에 대한 정말 우아하지 못한 해결책처럼 보입니다.또한 포커스를 받으려면 항상 표시되는 페이지 제어 기능이 필요합니다.그러나 내 응용 프로그램은 탭으로 표시되어 있으므로 그러한 제어 기능이 쉽게 나타나지 않습니다.

@니도노쿠:메뉴를 사용해도 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; }
    }
}
도움이 되었습니까?

해결책

창에 TextBox가 있고 그 안에 저장 버튼이 있는 ToolBar가 있다고 가정해 보세요.TextBox의 Text 속성이 비즈니스 개체의 속성에 바인딩되어 있고 바인딩의 UpdateSourceTrigger 속성이 LostFocus의 기본값으로 설정되어 있다고 가정합니다. 즉, TextBox가 입력 포커스를 잃을 때 바인딩된 값이 비즈니스 개체 속성으로 다시 푸시됩니다.또한 도구 모음의 저장 버튼에 해당 Command 속성이 ApplicationCommands.Save 명령으로 설정되어 있다고 가정합니다.

이런 상황에서 TextBox를 편집한 후 마우스로 저장 버튼을 클릭하면 문제가 발생합니다.ToolBar에서 버튼을 클릭하면 TextBox의 포커스가 사라지지 않습니다.TextBox의 LostFocus 이벤트가 실행되지 않으므로 Text 속성 바인딩은 비즈니스 개체의 소스 속성을 업데이트하지 않습니다.

분명히 UI에서 가장 최근에 편집한 값이 아직 개체에 푸시되지 않은 경우 개체의 유효성을 검사하고 저장해서는 안 됩니다.이는 Karl이 포커스가 있는 TextBox를 수동으로 찾고 데이터 바인딩 소스를 업데이트하는 코드를 창에 작성하여 해결한 정확한 문제입니다.그의 솔루션은 제대로 작동했지만 이 특정 시나리오 밖에서도 유용할 일반 솔루션에 대해 생각하게 되었습니다.CommandGroup 입력…

Josh Smith의 CodeProject 기사에서 발췌 CommandGroup

다른 팁

메뉴의 FocusScope에 의존하는 범위의 메뉴 항목을 제거하면 텍스트 상자의 포커스가 올바르게 손실되는 것으로 나타났습니다.나는 이것이 메뉴의 모든 항목에 적용된다고 생각하지 않지만 저장 또는 유효성 검사 작업에는 확실히 적용됩니다.

<Menu FocusManager.IsFocusScope="False" >

탭 순서에 컨트롤이 두 개 이상 있다고 가정하면 다음 솔루션은 완전하고 일반적인 것으로 보입니다(잘라내기 및 붙여넣기).

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

이것은 추악한 해킹이지만 작동해야합니다

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

이 코드는 TextBox에 포커스가 있는지 확인합니다.1이 발견되면...바인딩 소스를 업데이트하세요!

간단한 해결책은 아래와 같이 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> 

UpdateSourceTrigger를 PropertyChanged로 설정해 보셨나요?또는 UpdateSOurce() 메서드를 호출할 수도 있지만 이는 다소 과도한 것처럼 보이며 TwoWay 데이터 바인딩의 목적을 무효화합니다.

저는 이 문제에 직면했고 제가 찾은 최선의 해결책은 버튼(또는 MenuItem과 같은 다른 구성 요소)의 포커스 가능한 값을 true로 변경하는 것이었습니다.

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

작동하는 이유는 명령을 호출하기 전에 버튼에 초점을 맞춰서 TextBox를 만들기 때문입니다. 또는 해당 문제에 대한 다른 UIElement 포커스를 풀고 바인딩 변경을 호출하는 포커스 손실 이벤트를 발생시킵니다.

제한된 명령을 사용하는 경우(예제에서 지적한 것처럼) John Smith의 훌륭한 솔루션은 StaticExtension을 제한된 속성(DP도 아님)에 바인딩할 수 없기 때문에 잘 맞지 않습니다.

저장하기 직전에 초점을 다른 곳에 설정할 수 있나요?

UI 요소에서 focus()를 호출하여 이를 수행할 수 있습니다.

"저장"을 호출하는 요소에 집중할 수 있습니다.트리거가 LostFocus인 경우 포커스를 어딘가로 이동해야 합니다.저장은 수정되지 않으며 사용자에게 의미가 있다는 장점이 있습니다.

이에 대한 답을 찾기 위해 조사하면서 현재 보고 있는 동작이 파일 메뉴를 클릭하는 동작인지, 아니면 텍스트 상자의 초점을 해제하고 메뉴로 설정해야 하는지에 대해 약간 혼란스럽습니다.

가장 쉬운 방법은 어딘가에 초점을 맞춰라.
초점을 즉시 다시 설정할 수 있지만 초점을 아무데나 설정하면 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();

이것에 대해 어떻게 생각하세요?나는 리플렉션을 사용하여 좀 더 일반적으로 만드는 방법을 찾았다고 생각합니다.나는 다른 예처럼 목록을 유지한다는 아이디어를 정말 좋아하지 않았습니다.

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

문제가 있나요?

이 문제는 매우 일반적인 방법으로 해결하기에는 여전히 골치 아픈 문제라는 것을 알았기 때문에 다양한 솔루션을 시도했습니다.

결국 나에게 효과가 있었던 것 :UI 변경 사항의 유효성을 검사하고 소스에 업데이트해야 할 필요가 있을 때마다(창을 닫을 때 변경 사항 확인, 저장 작업 수행 등...) 다양한 작업을 수행하는 유효성 검사 함수를 호출합니다.- 집중 요소 (텍스트 박스, Combobox, ...)가 초점을 잃어 버리면 기본 업데이트 소스 동작을 트리거 할 수 있습니다. 요소

모든 것이 정상이면(검증 성공) 함수 자체가 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;
    }
}

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
}

작동해야합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top