Enterキーを押したときにTextBoxをバインドする


108

デフォルトのデータバインディングTextBoxはでTwoWayありTextBox、フォーカスを失った場合にのみ、テキストをプロパティにコミットします。

?のEnterキーを押したときにデータバインディングを実行する簡単なXAML方法はありますTextBoxか?コードビハインドでこれを行うのは非常に簡単ですが、これTextBoxが複雑な内部にあるかどうか想像してみてくださいDataTemplate

回答:


137

添付ビヘイビアーを作成することで、純粋なXAMLアプローチを作成できます

このようなもの:

public static class InputBindingsManager
{

    public static readonly DependencyProperty UpdatePropertySourceWhenEnterPressedProperty = DependencyProperty.RegisterAttached(
            "UpdatePropertySourceWhenEnterPressed", typeof(DependencyProperty), typeof(InputBindingsManager), new PropertyMetadata(null, OnUpdatePropertySourceWhenEnterPressedPropertyChanged));

    static InputBindingsManager()
    {

    }

    public static void SetUpdatePropertySourceWhenEnterPressed(DependencyObject dp, DependencyProperty value)
    {
        dp.SetValue(UpdatePropertySourceWhenEnterPressedProperty, value);
    }

    public static DependencyProperty GetUpdatePropertySourceWhenEnterPressed(DependencyObject dp)
    {
        return (DependencyProperty)dp.GetValue(UpdatePropertySourceWhenEnterPressedProperty);
    }

    private static void OnUpdatePropertySourceWhenEnterPressedPropertyChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = dp as UIElement;

        if (element == null)
        {
            return;
        }

        if (e.OldValue != null)
        {
            element.PreviewKeyDown -= HandlePreviewKeyDown;
        }

        if (e.NewValue != null)
        {
            element.PreviewKeyDown += new KeyEventHandler(HandlePreviewKeyDown);
        }
    }

    static void HandlePreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            DoUpdateSource(e.Source);
        }
    }

    static void DoUpdateSource(object source)
    {
        DependencyProperty property =
            GetUpdatePropertySourceWhenEnterPressed(source as DependencyObject);

        if (property == null)
        {
            return;
        }

        UIElement elt = source as UIElement;

        if (elt == null)
        {
            return;
        }

        BindingExpression binding = BindingOperations.GetBindingExpression(elt, property);

        if (binding != null)
        {
            binding.UpdateSource();
        }
    }
}

次に、XAMLでInputBindingsManager.UpdatePropertySourceWhenEnterPressedPropertyEnterキーが押されたときに更新するプロパティにプロパティを設定します。このような

<TextBox Name="itemNameTextBox"
         Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}"
         b:InputBindingsManager.UpdatePropertySourceWhenEnterPressed="TextBox.Text"/>

(XAMLファイルのルート要素に "b"のxmlns clr-namespace参照を含めて、InputBindingsManagerを配置した名前空間を指すようにする必要があります)。


11
将来の読者を数分いじる手間を省くために、これをプロジェクトで使用したところ、上記で提供されているサンプルXAMLは正しく機能しませんでした。ソースは、キャラクターが変更されるたびに更新されました。「UpdateSourceTrigger = PropertyChanged」を「UpdateSourceTrigger = Explicit」に変更すると、問題が修正されました。これで、すべてが希望どおりに機能します。
2014年

1
@ihake:推奨される変更により、フォーカスが失われた場合の更新も妨げられると思います
VoteCoffee 2014年

4
UpdateSourceTrigger = PropertyChangedは、実数を入力するときに不便な場合があります。たとえば、「3」。フロートにバインドすると問題が発生します。添付の動作に加えて、UpdateSourceTrigger(またはLostFocusへの設定)を指定しないことをお勧めします。これにより、両方の長所が得られます。
David Hollinshead、

2
素晴らしい仕事です!ちっちゃな一瞬:UpdatePropertySourceWhenEnterPressed有効な値から別の有効な値に変更すると、PreviewKeyDownイベントの購読を取り消して、不要に再購読します。代わりに、あなたが必要とする必要があるすべては、かどうかを確認することであるe.NewValueですnullか。そうでない場合はnull、購読してください。それ以外の場合nullは、購読を解除します。
Nicholas Miller

1
投稿してくれてありがとう、このソリューションが大好きです!この動作をアプリケーションの多数のTextBoxにアタッチする必要がある人のために、これを簡単に実現する方法について、この回答の拡張機能を投稿しました。こちらの投稿をご覧ください
Nik

50

これが私がこの問題を解決した方法です。コードビハインドに入る特別なイベントハンドラーを作成しました。

private void TextBox_KeyEnterUpdate(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Enter)
    {
        TextBox tBox = (TextBox)sender;
        DependencyProperty prop = TextBox.TextProperty;

        BindingExpression binding = BindingOperations.GetBindingExpression(tBox, prop);
        if (binding != null) { binding.UpdateSource(); }
    }
}

次に、これをXAMLのKeyUpイベントハンドラーとして追加しました。

<TextBox Text="{Binding TextValue1}" KeyUp="TextBox_KeyEnterUpdate" />
<TextBox Text="{Binding TextValue2}" KeyUp="TextBox_KeyEnterUpdate" />

イベントハンドラーはそのsender参照を使用して、独自のバインディングを更新します。イベントハンドラーは自己完結型であるため、複雑なDataTemplateで機能するはずです。この1つのイベントハンドラーは、この機能を必要とするすべてのテキストボックスに追加できます。


8
これは過小評価されている投稿であると私に言います。「XAML-y」であるため、多くの回答が人気です。しかし、これはほとんどの場合スペースを節約するように見えます。
j riv

3
+1これにより、TextBoxバインディングがすでに希望どおりに動作するように(スタイル、検証、双方向など)すでに問題が発生している場合でも、UpdateSourceTriggerをそのままにしておくことができますが、現在はEnterキーを押した後に入力を受信しません。 。
Jamin

2
これは私が見つけた最もクリーンなアプローチであり、私の意見では答えとしてマークする必要があります。送信者に関する追加のnullチェック、Key.Return(キーボードのEnterキーがKey.Returnを返す)のチェック、およびソースプロパティの更新後にTextBoxからフォーカスを削除するためのKeyboard.ClearFocus()を追加しました。査読待ちの回答を編集しました。
オイスタイン

2
上記のコメントに同意してください。しかし、私にとってはKeyDownイベントの方が適切でした
Allender

1
KeyBindingXAMLでこのメソッドを起動するために使用しました。これは、UIにEnterキーをキャッチする「デフォルト」コントロールがあるためです。このTextBox内でキャッチして、UIツリーを「デフォルト」コントロールに伝播しないようにする必要があります。
CADは

46

私はあなたが説明していることを行うための「純粋なXAML」方法があるとは信じていません。次のようにUpdateSourceTriggerプロパティを設定することで、TextBoxのテキストが変更されるたびに(TextBoxがフォーカスを失ったときではなく)更新されるようにバインディングを設定できます。

<TextBox Name="itemNameTextBox"
    Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}" />

UpdateSourceTriggerを "Explicit"に設定し、TextBoxのPreviewKeyDownイベント(Enterキーを探す)を処理した場合、希望どおりの結果を得ることができますが、コードビハインドが必要になります。おそらく、ある種の添付プロパティ(私のEnterKeyTraversalプロパティに似ています)が役に立ちます。


3
この回答は、回答としてマークされている回答よりも単純な場合がありますが、いくつかの制限があります。バインドされたプロパティのset関数で何らかのチェックを行う画像(入力が有効かどうかのチェック)。ユーザーがキーを押すたびにそのチェック関数を呼び出したくない場合(特に、関数に時間がかかる場合はそうではありません)。
sth_Weird 2015

25

TextBoxから継承する独自のコントロールを簡単に作成し、プロジェクト全体で再利用できます。

これに似たものがうまくいくはずです:

public class SubmitTextBox : TextBox
{
    public SubmitTextBox()
        : base()
    {
        PreviewKeyDown += new KeyEventHandler(SubmitTextBox_PreviewKeyDown);
    }

    void SubmitTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            BindingExpression be = GetBindingExpression(TextBox.TextProperty);
            if (be != null)
            {
                be.UpdateSource();
            }
        }
    }
}

この手順を回避する方法はあるかもしれませんが、それ以外の場合は(Explicitを使用して)このようにバインドする必要があります。

<custom:SubmitTextBox
    Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />

9
WPF / Silverlightでは、継承を使用しないでください。継承はスタイルを混乱させ、アタッチされた動作ほど柔軟性がありません。たとえば、アタッチされた動作では、同じテキストボックスにウォーターマークとUpdateOnEnterの両方を設定できます。
ミハイル

14

Benとausadminの両方のソリューションを組み合わせると、非常にMVVMフレンドリーなソリューションになります。

<TextBox Text="{Binding Txt1, Mode=TwoWay, UpdateSourceTrigger=Explicit}">
    <TextBox.InputBindings>
        <KeyBinding Gesture="Enter" 
                    Command="{Binding UpdateTextBoxBindingOnEnterCommand}"
                    CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}" />
    </TextBox.InputBindings>
</TextBox>

...つまり、TextBoxそれ自体をパラメータとしてに渡しますCommand

これにより、次のCommandようDelegateCommandになります(VMで-スタイルの実装を使用している場合)。

    public bool CanExecuteUpdateTextBoxBindingOnEnterCommand(object parameter)
    {
        return true;
    }

    public void ExecuteUpdateTextBoxBindingOnEnterCommand(object parameter)
    {
        TextBox tBox = parameter as TextBox;
        if (tBox != null)
        {
            DependencyProperty prop = TextBox.TextProperty;
            BindingExpression binding = BindingOperations.GetBindingExpression(tBox, prop);
            if (binding != null) 
                binding.UpdateSource();
        }
    }

このCommand実装はTextBox、分離コード内のすべてのコードのすべてに使用できますが、これを独自のクラスに配置しSystem.Windows.Controlsて、VMに依存しないようにすることもできます。それはあなたのコードガイドラインがどれほど厳しいかに依存します。


いいね。非常にきれいな。マルチバインディングが関係する場合は、BindingOperations.GetBindingExpressionBase(tBox、prop);を使用する必要がある場合があります。そしてbinding.UpdateTarget();
VoteCoffee 2014年

4

これは私には非常に簡単で、AttachedBehaviourを追加する方が簡単なアプローチです(これも有効なソリューションです)。デフォルトのUpdateSourceTrigger(TextBoxのLostFocus)を使用し、コマンドにバインドされたEnterBindingをEnterキーに追加します。

xamlは次のとおりです

       <TextBox Grid.Row="0" Text="{Binding Txt1}" Height="30" Width="150">
        <TextBox.InputBindings>
            <KeyBinding Gesture="Enter" 
                        Command="{Binding UpdateText1Command}"
                        CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}},Path=Text}" />
        </TextBox.InputBindings>
    </TextBox>

次に、コマンドメソッドは

Private Function CanExecuteUpdateText1(ByVal param As Object) As Boolean
    Return True
End Function
Private Sub ExecuteUpdateText1(ByVal param As Object)

    If TypeOf param Is String Then
        Txt1 = CType(param, String)
    End If
End Sub

そして、TextBoxはプロパティにバインドされています

 Public Property Txt1 As String
    Get
        Return _txt1
    End Get
    Set(value As String)
        _txt1 = value
        OnPropertyChanged("Txt1")
    End Set
End Property

これまでのところ、これはうまく機能しているようで、TextBoxのEnter Keyイベントをキャッチします。


4

これは元の質問への回答ではなく、@ Samuel Jackによって受け入れられた回答の拡張です。私は自分のアプリケーションで次のことを行い、サミュエルのソリューションの優雅さに畏敬の念を抱きました。だけでなく、あらゆるコントロールで使用できるため、非常にクリーンで再利用可能TextBoxです。これはコミュニティと共有すべきだと思いました。

TextBoxesEnterでバインドソースをすべて更新する必要がある1,000のウィンドウがある場合Window Resources、各TextBoxにアタッチするのではなく、以下のXAMLをに含めることで、すべてのウィンドウにこの動作をアタッチできます。最初に、もちろん、サミュエルの投稿に従って、添付の動作を実装する必要があります。

<Window.Resources>
    <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
        <Style.Setters>
            <Setter Property="b:InputBindingsManager.UpdatePropertySourceWhenEnterPressed" Value="TextBox.Text"/>
        </Style.Setters>
    </Style>
</Window.Resources>

必要に応じGridて、ターゲットのTextBoxを含むウィンドウの子要素の1つ(つまり)のリソースにスタイルを配置することにより、いつでもスコープを制限できます。


2

TextBoxでMultiBindingを使用している場合は、BindingOperations.GetMultiBindingExpressionではなくmethod を使用する必要がありますBindingOperations.GetBindingExpression

// Get the correct binding expression based on type of binding
//(simple binding or multi binding.
BindingExpressionBase binding = 
  BindingOperations.GetBindingExpression(element, prop);
if (binding == null)
{
    binding = BindingOperations.GetMultiBindingExpression(element, prop);
}

if (binding != null)
{
     object value = element.GetValue(prop);
     if (string.IsNullOrEmpty(value.ToString()) == true)
     {
         binding.UpdateTarget();
     }
     else
     {
          binding.UpdateSource();
     }
}

1

これは私にとってはうまくいきます:

        <TextBox                 
            Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
            <TextBox.InputBindings>
                <KeyBinding Key="Return" 
                            Command="{Binding Ok}"/>
            </TextBox.InputBindings>
        </TextBox>


0

個人的には、マークアップ拡張機能を使用する方がよりクリーンなアプローチだと思います。

public class UpdatePropertySourceWhenEnterPressedExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return new DelegateCommand<TextBox>(textbox => textbox.GetBindingExpression(TextBox.TextProperty).UpdateSource());
    }
}


<TextBox x:Name="TextBox"
             Text="{Binding Text}">
        <TextBox.InputBindings>
            <KeyBinding Key="Enter"
                        Command="{markupExtensions:UpdatePropertySourceWhenEnterPressed}" 
                        CommandParameter="{Binding ElementName=TextBox}"/>
        </TextBox.InputBindings>
</TextBox>

0

別の解決策(xamlを使用しないが、それでもかなりクリーンだと思います)。

class ReturnKeyTextBox : TextBox
{
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (e.Key == Key.Return)
            GetBindingExpression(TextProperty).UpdateSource();
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.