38

私のWPFアプリケーションには、いくつかのデータバインドされたTextBoxがあります。これらUpdateSourceTriggerのバインディングのはLostFocusです。オブジェクトは、[ファイル]メニューを使用して保存されます。私が抱えている問題は、TextBoxに新しい値を入力し、[ファイル]メニューから[保存]を選択し、メニューにアクセスしてもTextBoxからフォーカスが削除されないため、新しい値(TextBoxに表示される値)を保持できないことです。 。どうすればこれを修正できますか?ページ内のすべてのコントロールを強制的にデータバインドする方法はありますか?

@palehorse:良い点です。残念ながら、必要なタイプの検証をサポートするために、UpdateSourceTriggerとしてLostFocusを使用する必要があります。

@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 があり、その中に [保存] ボタンがあるツールバーがあるとします。TextBox の Text プロパティがビジネス オブジェクトのプロパティにバインドされ、バインディングの UpdateSourceTrigger プロパティがデフォルト値の LostFocus に設定されているとします。つまり、TextBox が入力フォーカスを失うと、バインドされた値がビジネス オブジェクト プロパティにプッシュ バックされます。また、ツールバーの [保存] ボタンの Command プロパティが ApplicationCommands.Save コマンドに設定されているとします。

その場合、TextBox を編集して [保存] ボタンをマウスでクリックすると問題が発生します。ToolBar の Button をクリックしても、TextBox はフォーカスを失いません。TextBox の LostFocus イベントは発生しないため、Text プロパティ バインディングはビジネス オブジェクトのソース プロパティを更新しません。

UI で最後に編集された値がまだオブジェクトにプッシュされていない場合は、オブジェクトを検証して保存しないでください。これは、Karl がフォーカスのある TextBox を手動で検索し、データ バインディングのソースを更新するコードをウィンドウに記述することで回避した正確な問題です。彼の解決策はうまくいきましたが、この特定のシナリオ以外でも役立つ一般的な解決策について考えさせられました。コマンドグループを入力してください…</p>

CommandGroup に関する Josh Smith の 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 のフォーカスが失われ、変更されるバインディングを呼び出す Lost focus イベントが発生するためです。

制限付きコマンドを使用している場合 (私の例で指摘していたように)、StaticExtension を制限付きプロパティ (または DP) にバインドできないため、John Smith の優れたソリューションはうまく適合しません。

于 2012-03-23T13:33:44.907 に答える
1

この問題は、非常に一般的な方法で解決するのが依然として面倒であることに気付いたので、さまざまな解決策を試しました。

最終的に私にとってはうまくいきました:UIの変更を検証してそのソースに更新する必要があるときはいつでも(ウィンドウを閉じるとき、保存操作を実行するときに変更を確認するなど)、さまざまな処理を行う検証関数を呼び出します- フォーカスされた要素 (テキストボックス、コンボボックスなど) がデフォルトの updatesource 動作をトリガーするフォーカスを失うことを確認します - 検証関数に渡される DependencyObject のツリー内のすべてのコントロールを検証します - フォーカスをオリジナルのフォーカス要素

関数自体は、すべてが正常である場合 (検証が成功した場合) に true を返します -> 元のアクション (オプションで確認を求めて閉じる、保存するなど) を続行できます。そうしないと、関数は false を返し、アクションを続行できません。1 つ以上の要素に検証エラーがあるためです (要素の一般的な 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 に答える
1

保存する直前にフォーカスを別の場所に設定していただけませんか?

これを行うには、UI 要素で focus() を呼び出します。

「保存」を呼び出す要素に集中できます。トリガーが LostFocus の場合、フォーカスをどこかに移動する必要があります。保存には、変更されず、ユーザーにとって意味があるという利点があります。

于 2008-09-11T21:09:10.247 に答える
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

最も簡単な方法は、フォーカスをどこかに設定することです。
フォーカスをすぐに戻すことができますが、フォーカスを任意の場所に設定すると、任意のタイプのコントロールで LostFocus-Event がトリガーされ、その内容が更新されます。

IInputElement x = System.Windows.Input.Keyboard.FocusedElement;
DummyField.Focus();
x.Focus();

もう 1 つの方法は、フォーカスされた要素を取得し、フォーカスされた要素からバインド要素を取得し、手動で更新をトリガーすることです。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 に答える