Bind TextBox saat menekan tombol Enter

108

Pengikatan data default TextBoxadalah TwoWaydan memasukkan teks ke properti hanya ketika TextBoxkehilangan fokusnya.

Apakah ada cara XAML yang mudah untuk membuat penyatuan data terjadi ketika saya menekan Entertombol di TextBox?. Saya tahu ini cukup mudah dilakukan dalam kode di belakang, tetapi bayangkan jika ini TextBoxada di dalam beberapa kompleks DataTemplate.

Joy Jobi
sumber

Jawaban:

137

Anda dapat menjadikan diri Anda sendiri pendekatan XAML murni dengan membuat perilaku terlampir .

Sesuatu seperti ini:

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

Kemudian di XAML Anda, Anda mengatur InputBindingsManager.UpdatePropertySourceWhenEnterPressedPropertyproperti ke properti yang ingin Anda perbarui ketika Entertombol ditekan. Seperti ini

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

(Anda hanya perlu memastikan untuk menyertakan referensi xmlns clr-namespace untuk "b" di elemen root dari file XAML Anda yang menunjuk ke namespace mana Anda meletakkan InputBindingsManager di dalamnya).

Samuel Jack
sumber
11
Dalam upaya untuk menyelamatkan pembaca di masa depan beberapa menit dari mengutak-atik, saya baru saja menggunakan ini dalam proyek saya, dan sampel XAML yang diberikan di atas tidak berfungsi dengan benar. Sumber diperbarui pada setiap perubahan karakter. Mengubah "UpdateSourceTrigger = PropertyChanged" menjadi "UpdateSourceTrigger = Explicit" memperbaiki masalah. Sekarang semuanya bekerja sesuai keinginan.
ihake
1
@ihake: Saya pikir perubahan yang Anda rekomendasikan juga akan mencegah pembaruan pada fokus yang hilang
VoteCoffee
4
UpdateSourceTrigger = PropertyChanged mungkin tidak nyaman saat mengetik bilangan real. Misalnya "3." menyebabkan masalah saat terikat pada pelampung. Saya merekomendasikan untuk tidak menentukan UpdateSourceTrigger (atau pengaturan ke LostFocus) selain perilaku terlampir. Ini memberikan yang terbaik dari kedua dunia.
David Hollinshead
2
Kerja bagus! Tiny nit-pick: Saat UpdatePropertySourceWhenEnterPressedperubahan dari nilai valid ke nilai valid berbeda, Anda berhenti berlangganan & berlangganan kembali PreviewKeyDownacara jika tidak perlu. Sebaliknya, yang Anda perlukan hanyalah memeriksa apakah e.NewValueada nullatau tidak. Jika tidak null, maka berlangganan; jika tidak, nullmaka berhenti berlangganan.
Nicholas Miller
1
Saya suka solusi ini, terima kasih telah mempostingnya! Bagi siapa pun yang perlu melampirkan perilaku ini ke banyak Kotak Teks di aplikasi Anda, saya memposting ekstensi untuk jawaban ini tentang bagaimana Anda dapat mencapainya dengan sangat mudah. Lihat posting di sini
Nik
50

Beginilah cara saya memecahkan masalah ini. Saya membuat penangan acara khusus yang masuk ke kode di belakang:

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

Lalu saya baru saja menambahkan ini sebagai pengendali acara KeyUp di XAML:

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

Penangan kejadian menggunakan senderreferensinya agar pengikatannya sendiri diperbarui. Karena event handler berdiri sendiri maka itu harus bekerja dalam DataTemplate yang kompleks. Penangan kejadian yang satu ini sekarang dapat ditambahkan ke semua kotak teks yang membutuhkan fitur ini.

Ben
sumber
8
Sesuatu memberi tahu saya bahwa ini adalah postingan yang diremehkan. Banyak jawaban yang populer karena "XAML-y". Tapi yang satu ini tampaknya menghemat ruang di banyak kesempatan.
j riv
3
+1 Ini juga memungkinkan Anda untuk meninggalkan UpdateSourceTrigger sendiri jika Anda sudah dengan susah payah mendapatkan binding TextBox Anda untuk berperilaku seperti yang Anda inginkan (dengan gaya, validasi, twoway, dll.), Tetapi saat ini tidak menerima input setelah menekan Enter .
Jamin
2
Ini adalah pendekatan terbersih yang saya temukan, dan menurut saya harus ditandai sebagai jawabannya. Menambahkan pemeriksaan null tambahan pada pengirim, pemeriksaan pada Key.Return (tombol Enter pada keyboard saya mengembalikan Key.Return), dan Keyboard.ClearFocus () untuk menghapus fokus dari TextBox setelah memperbarui properti sumber. Saya telah mengedit jawaban yang menunggu tinjauan sejawat.
Oystein
2
Setuju dengan komentar di atas. Tapi bagi saya acara KeyDown lebih tepat
Allender
1
Saya menggunakan KeyBindingXAML untuk mengaktifkan metode ini karena UI saya memiliki kontrol "Default" yang menangkap tombol enter. Anda harus menangkapnya di dalam TextBox ini untuk menghentikannya menyebarkan pohon UI ke kontrol "Default".
Pria CAD
46

Saya tidak percaya bahwa ada cara "XAML murni" untuk melakukan apa yang Anda gambarkan. Anda bisa menyiapkan pengikatan agar diperbarui setiap kali teks dalam TextBox berubah (bukan saat TextBox kehilangan fokus) dengan menyetel properti UpdateSourceTrigger , seperti ini:

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

Jika Anda menyetel UpdateSourceTrigger ke "Explicit" dan kemudian menangani event PreviewKeyDown dari TextBox (mencari tombol Enter) maka Anda dapat mencapai apa yang Anda inginkan, tetapi akan membutuhkan kode di belakang. Mungkin semacam properti terlampir (mirip dengan properti EnterKeyTraversal saya ) akan bekerja untuk Anda.

Matt Hamilton
sumber
3
Jawaban ini mungkin lebih sederhana daripada yang ditandai sebagai jawaban, tetapi memiliki beberapa keterbatasan. Gambar Anda ingin melakukan beberapa jenis pemeriksaan di set-fungsi properti terikat (memeriksa apakah input valid). Anda tidak ingin memanggil fungsi pemeriksaan itu setelah setiap tombol yang ditekan pengguna, bukan (terutama jika fungsi tersebut membutuhkan waktu).?
sth_Weird
25

Anda dapat dengan mudah membuat kontrol Anda sendiri yang diwarisi dari TextBox dan menggunakannya kembali di seluruh proyek Anda.

Sesuatu yang mirip dengan ini seharusnya berfungsi:

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

Mungkin ada cara untuk menyiasati langkah ini, tetapi sebaliknya Anda harus mengikat seperti ini (menggunakan Eksplisit):

<custom:SubmitTextBox
    Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />
Ryan Versaw
sumber
9
Di WPF / Silverlight Anda tidak boleh menggunakan warisan - itu mengacaukan gaya dan tidak sefleksibel Perilaku Terlampir. Misalnya dengan Attached Behaviors, Anda dapat memiliki Watermark dan UpdateOnEnter pada kotak teks yang sama.
Mikhail
14

Jika Anda menggabungkan solusi Ben dan ausadmin, Anda akan mendapatkan solusi yang sangat ramah 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>

... yang berarti Anda meneruskan TextBoxdirinya sebagai parameter ke Command.

Ini mengarah pada Commandtampilan Anda seperti ini (jika Anda menggunakan DelegateCommandimplementasi -style di VM Anda):

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

CommandImplementasi ini dapat digunakan untuk semua TextBoxdan yang terbaik dari semua tanpa kode di belakang kode meskipun Anda mungkin ingin meletakkan ini di kelasnya sendiri sehingga tidak ada ketergantungan pada System.Windows.ControlsVM Anda. Itu tergantung pada seberapa ketat pedoman kode Anda.

toadflakz.dll
sumber
Bagus. Sangat bersih. Jika multibinding terlibat, Anda mungkin perlu menggunakan BindingOperations.GetBindingExpressionBase (tBox, prop); dan kemudian mengikat.UpdateTarget ();
VoteCoffee
4

Berikut adalah pendekatan yang bagi saya tampaknya cukup mudah, dan lebih mudah daripada menambahkan AttachedBehaviour (yang juga merupakan solusi yang valid). Kami menggunakan UpdateSourceTrigger default (LostFocus for TextBox), dan kemudian menambahkan InputBinding ke Enter Key, terikat ke perintah.

Xaml adalah sebagai berikut

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

Kemudian metode Command adalah

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

Dan TextBox terikat ke Properti

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

Sejauh ini tampaknya berfungsi dengan baik dan menangkap acara Enter Key di TextBox.

ausadmin
sumber
4

Ini bukan jawaban untuk pertanyaan awal, melainkan perpanjangan dari jawaban yang diterima oleh @Samuel Jack. Saya melakukan hal berikut dalam aplikasi saya sendiri, dan kagum dengan keanggunan solusi Samuel. Ini sangat bersih, dan sangat dapat digunakan kembali, karena dapat digunakan pada kontrol apa pun, bukan hanya TextBox. Saya pikir ini harus dibagikan dengan komunitas.

Jika Anda memiliki Jendela dengan seribu TextBoxesyang semuanya perlu memperbarui Sumber Binding pada Enter, Anda dapat melampirkan perilaku ini ke semuanya dengan memasukkan XAML di bawah ini ke dalam Anda Window Resourcesdaripada melampirkannya ke setiap Kotak Teks. Pertama, Anda harus menerapkan perilaku terlampir sesuai posting Samuel , tentu saja.

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

Anda selalu dapat membatasi cakupan, jika perlu, dengan meletakkan Style ke dalam Sumber daya salah satu elemen anak Window (yaitu a Grid) yang berisi TextBoxes target.

Nik
sumber
2

Jika Anda menggunakan MultiBinding dengan TextBox Anda, Anda perlu menggunakan BindingOperations.GetMultiBindingExpressionmetode alih-alih 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();
     }
}
akjoshi
sumber
1

Ini bekerja untuk saya:

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

Menurut saya pribadi, memiliki Ekstensi Markup adalah pendekatan yang lebih bersih.

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>
metoyou
sumber
0

Solusi lain (tidak menggunakan xaml tapi masih cukup bersih menurut saya).

class ReturnKeyTextBox : TextBox
{
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (e.Key == Key.Return)
            GetBindingExpression(TextProperty).UpdateSource();
    }
}
Hauk
sumber