Bind TextBox khi nhấn phím Enter


108

Databinding mặc định TextBoxTwoWayvà nó chỉ chuyển văn bản đến thuộc tính khi TextBoxbị mất tiêu điểm.

Có cách nào XAML dễ dàng để làm cho việc ghi dữ liệu xảy ra khi tôi nhấn Enterphím trên TextBoxkhông ?. Tôi biết nó khá dễ thực hiện trong đoạn mã phía sau, nhưng hãy tưởng tượng nếu điều này TextBoxnằm trong một số phức tạp DataTemplate.

Câu trả lời:


137

Bạn có thể tạo cho mình một cách tiếp cận XAML thuần túy bằng cách tạo một hành vi đính kèm .

Một cái gì đó như thế này:

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

Sau đó, trong XAML của bạn, bạn đặt thuộc InputBindingsManager.UpdatePropertySourceWhenEnterPressedPropertytính thành thuộc tính bạn muốn cập nhật khi Enternhấn phím. Như thế này

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

(Bạn chỉ cần đảm bảo bao gồm tham chiếu không gian tên xmlns clr cho "b" trong phần tử gốc của tệp XAML của bạn trỏ đến không gian tên mà bạn đã đặt InputBindingsManager vào).


11
Trong một nỗ lực để tiết kiệm cho người đọc trong tương lai một vài phút loay hoay, tôi chỉ sử dụng nó trong dự án của mình và mẫu XAML được cung cấp ở trên không hoạt động bình thường. Nguồn đã được cập nhật vào mỗi lần thay đổi ký tự. Thay đổi "UpdateSourceTrigger = PropertyChanged" thành "UpdateSourceTrigger = Explicit" đã khắc phục sự cố. Bây giờ tất cả hoạt động như mong muốn.
ihake

1
@ihake: Tôi nghĩ rằng thay đổi được đề xuất của bạn cũng sẽ ngăn chặn việc cập nhật về tiêu điểm bị mất
VoteCoffee

4
UpdateSourceTrigger = PropertyChanged có thể gây bất tiện khi nhập một số thực. Ví dụ "3." gây ra sự cố khi liên kết với một phao. Tôi khuyên bạn không nên chỉ định UpdateSourceTrigger (hoặc cài đặt thành LostFocus) ngoài hành vi đính kèm. Điều này mang lại điều tốt nhất cho cả hai thế giới.
David Hollinshead,

2
Làm tốt lắm! Nhặt nhẹ: Khi các UpdatePropertySourceWhenEnterPressedthay đổi từ giá trị hợp lệ sang giá trị hợp lệ khác, bạn đang hủy đăng ký & đăng ký lại PreviewKeyDownsự kiện một cách không cần thiết. Thay vào đó, tất cả các bạn nên cần phải là để kiểm tra hay không e.NewValuenullhay không. Nếu không null, sau đó đăng ký; ngược lại, nếu null, sau đó hủy đăng ký.
Nicholas Miller

1
Tôi thích giải pháp này, cảm ơn vì đã đăng nó! Đối với bất kỳ ai cần đính kèm hành vi này vào nhiều TextBox trong ứng dụng của bạn, tôi đã đăng phần mở rộng cho câu trả lời này về cách bạn có thể đạt được điều đó rất dễ dàng. Xem bài đăng ở đây
Nik Ngày

50

Đây là cách tôi giải quyết vấn đề này. Tôi đã tạo một trình xử lý sự kiện đặc biệt đi vào mã đằng sau:

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

Sau đó, tôi chỉ thêm điều này làm trình xử lý sự kiện KeyUp trong XAML:

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

Trình xử lý sự kiện sử dụng sendertham chiếu của nó để khiến ràng buộc của chính nó được cập nhật. Vì trình xử lý sự kiện là độc lập nên nó sẽ hoạt động trong một DataTemplate phức tạp. Trình xử lý một sự kiện này hiện có thể được thêm vào tất cả các hộp văn bản cần tính năng này.


8
Một cái gì đó cho tôi biết đây là một bài viết được đánh giá thấp. Rất nhiều câu trả lời phổ biến vì chúng là "XAML-y". Nhưng cái này dường như tiết kiệm không gian trong hầu hết các trường hợp.
j riv

3
+1 Điều này cũng cho phép bạn để lại UpdateSourceTrigger một mình trong trường hợp bạn đã cẩn thận để các liên kết TextBox của mình đã hoạt động theo cách bạn muốn (với các kiểu, xác thực, hai ngày, v.v.), nhưng hiện tại sẽ không nhận được đầu vào sau khi nhấn Enter .
Jamin

2
Đây là cách tiếp cận sạch nhất mà tôi đã tìm thấy và theo ý kiến ​​của tôi nên được đánh dấu là câu trả lời. Đã thêm kiểm tra rỗng bổ sung cho người gửi, kiểm tra Key.Return (Phím Enter trên bàn phím của tôi trả về Key.Return) và Keyboard.ClearFocus () để xóa tiêu điểm khỏi TextBox sau khi cập nhật thuộc tính nguồn. Tôi đã thực hiện các chỉnh sửa cho câu trả lời đang chờ đồng nghiệp xem xét.
Oystein

2
Đồng ý với ý kiến ​​trên. Nhưng đối với tôi sự kiện KeyDown thích hợp hơn
Allender

1
Tôi đã sử dụng KeyBindingtrong XAML để kích hoạt phương pháp này vì giao diện người dùng của tôi có điều khiển "Mặc định" bắt phím enter. Bạn cần nắm bắt nó trong TextBox này để ngăn nó lan truyền lên cây giao diện người dùng đến điều khiển "Mặc định".
CAD bloke

46

Tôi không tin rằng có bất kỳ cách "XAML thuần túy" nào để làm những gì bạn đang mô tả. Bạn có thể thiết lập một liên kết để nó cập nhật bất cứ khi nào văn bản trong TextBox thay đổi (thay vì khi TextBox mất tiêu điểm) bằng cách đặt thuộc tính UpdateSourceTrigger , như sau:

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

Nếu bạn đặt UpdateSourceTrigger thành "Explicit" và sau đó xử lý sự kiện PreviewKeyDown của TextBox (đang tìm phím Enter) thì bạn có thể đạt được những gì mình muốn, nhưng nó sẽ yêu cầu mã phía sau. Có lẽ một số loại thuộc tính đính kèm (tương tự như thuộc tính EnterKeyTraversal của tôi ) sẽ phù hợp với bạn.


3
Câu trả lời này có thể đơn giản hơn câu được đánh dấu là câu trả lời, nhưng nó có một số hạn chế. Hình ảnh bạn muốn thực hiện một số loại kiểm tra trong set-function của thuộc tính ràng buộc (kiểm tra xem đầu vào có hợp lệ không). Bạn không muốn gọi chức năng kiểm tra đó sau mỗi lần người dùng nhấn phím phải không (đặc biệt là khi chức năng này mất một thời gian).?
sth_Weird

25

Bạn có thể dễ dàng tạo điều khiển của riêng mình kế thừa từ TextBox và sử dụng lại nó trong suốt dự án của bạn.

Một cái gì đó tương tự như thế này sẽ hoạt động:

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

Có thể có một cách để thực hiện bước này, nhưng nếu không, bạn nên liên kết như thế này (sử dụng Explicit):

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

9
Trong WPF / Silverlight, bạn không bao giờ nên sử dụng kế thừa - nó làm rối tung các kiểu và không linh hoạt như Hành vi đính kèm. Ví dụ: với Hành vi đính kèm, bạn có thể có cả Watermark và UpdateOnEnter trên cùng một hộp văn bản.
Mikhail

14

Nếu bạn kết hợp cả hai giải pháp của Ben và ausadmin, bạn sẽ có một giải pháp rất thân thiện với 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>

... có nghĩa là bạn đang chuyển TextBoxchính nó làm tham số cho Command.

Điều này dẫn đến giao diện của bạn Commandnhư thế này (nếu bạn đang sử dụng DelegateCommandtriển khai kiểu trong máy ảo của mình):

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

Việc Commandtriển khai này có thể được sử dụng cho bất kỳ mã nào TextBoxvà tốt nhất là không có mã nào trong mã phía sau mặc dù bạn có thể muốn đặt nó trong lớp riêng của nó để không có phụ thuộc nào System.Windows.Controlstrong máy ảo của bạn. Nó phụ thuộc vào mức độ nghiêm ngặt của nguyên tắc mã của bạn.


Đẹp. Rất sạch sẽ. Nếu có liên kết đa liên kết, bạn có thể cần sử dụng BindingOperations.GetBindingExpressionBase (tBox, prop); và sau đó ràng buộc.UpdateTarget ();
VoteCoffee

4

Đây là một cách tiếp cận mà đối với tôi có vẻ khá đơn giản và dễ dàng hơn là thêm AttachedBehaviour (cũng là một giải pháp hợp lệ). Chúng tôi sử dụng UpdateSourceTrigger mặc định (LostFocus cho TextBox), và sau đó thêm một InputBinding vào phím Enter, được liên kết với một lệnh.

Xaml như sau

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

Sau đó, các phương thức Lệnh là

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

Và TextBox liên kết với Thuộc tính

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

Cho đến nay, điều này dường như hoạt động tốt và bắt kịp sự kiện Enter Key trong TextBox.


4

Đây không phải là câu trả lời cho câu hỏi ban đầu, mà là phần mở rộng của câu trả lời được chấp nhận bởi @Samuel Jack. Tôi đã làm những điều sau đây trong ứng dụng của chính mình, và rất kinh ngạc trước sự sang trọng của giải pháp của Samuel. Nó rất sạch, và rất có thể tái sử dụng, vì nó có thể được sử dụng trên bất kỳ điều khiển nào, không chỉ TextBox. Tôi nghĩ điều này nên được chia sẻ với cộng đồng.

Nếu bạn có một Cửa sổ với một nghìn TextBoxestất cả đều yêu cầu cập nhật Nguồn liên kết khi Enter, bạn có thể đính kèm hành vi này cho tất cả chúng bằng cách đưa XAML bên dưới vào của bạn Window Resourcesthay vì gắn nó vào từng TextBox. Tất nhiên, trước tiên, bạn phải thực hiện hành vi đính kèm theo bài đăng của Samuel .

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

Bạn luôn có thể giới hạn phạm vi, nếu cần, bằng cách đưa Kiểu vào Tài nguyên của một trong các phần tử con của Cửa sổ (tức là a Grid) có chứa các Hộp văn bản đích.


2

Trong trường hợp bạn đang sử dụng MultiBinding với TextBox của mình, bạn cần sử dụng BindingOperations.GetMultiBindingExpressionphương pháp thay thế 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

Điều này phù hợp với tôi:

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


0

Cá nhân tôi nghĩ rằng có một Tiện ích mở rộng đánh dấu là một cách tiếp cận rõ ràng hơn.

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

Một giải pháp khác (không sử dụng xaml nhưng tôi nghĩ vẫn khá sạch sẽ).

class ReturnKeyTextBox : TextBox
{
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (e.Key == Key.Return)
            GetBindingExpression(TextProperty).UpdateSource();
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.