Прив’яжіть TextBox при натисканні клавіші Enter


108

Типовим прив'язкою даних TextBoxє, TwoWayі він передає текст у властивість лише тоді, коли TextBoxвтратив фокус.

Чи є якийсь простий спосіб XAML зробити прив'язку даних, коли я натискаю Enterклавішу на 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.UpdatePropertySourceWhenEnterPressedPropertyвластивість до тієї, яку потрібно оновити, коли Enterнатискається клавіша. Подобається це

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

(Вам просто потрібно переконатися, що в кореневому елементі вашого файлу XAML увімкнено посилання на xmlns clr-namespace для "b", яке вказує на те, в яке простору імен ви ставите InputBindingsManager).


11
Намагаючись заощадити майбутніх читачів на декількох хвилинах вигадки, я просто використав це у своєму проекті, і зразок XAML, наданий вище, не працював правильно. Джерело оновлювалось при кожній зміні символів. Зміна "UpdateSourceTrigger = PropertyChanged" на "UpdateSourceTrigger = Явний" виправила проблему. Тепер це все працює за бажанням.
ihake

1
@ihake: Я думаю, що рекомендована вами зміна також не
дасть змоги

4
UpdateSourceTrigger = PropertyChanged може бути незручним при введенні реального числа. Наприклад "3" викликає проблему при прив'язці до поплавця. Я рекомендую не вказувати UpdateSourceTrigger (або встановити програму LostFocus) на додаток до поведінки, що додається. Це дає найкраще з обох світів.
Девід Холлінгед

2
Відмінна робота! Крихітний вибір ниток: Коли ви UpdatePropertySourceWhenEnterPressedзмінюєте дійсне значення на інше дійсне значення, ви відміняєте підписку та повторно підписуєтесь на PreviewKeyDownподію. Натомість все, що вам потрібно, - це перевірити, e.NewValueє nullчи ні. Якщо ні null, то підпишіться; в іншому випадку, якщо null, тоді скасуйте підписку.
Ніколас Міллер

1
Я люблю це рішення, дякую за публікацію! Для всіх, кому потрібно приєднати таку поведінку до численних TextBoxes у вашій програмі, я опублікував розширення до цієї відповіді про те, як можна дуже легко досягти цього. Дивіться пост тут
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(); }
    }
}

Потім я просто додав це як обробник подій KeyUp у XAML:

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

Обробник події використовує свою senderпосилання, щоб викликати оновлення власного прив'язки. Оскільки обробник подій є автономним, то він повинен працювати у складному DataTemplate. Цей обробник подій тепер може бути доданий до всіх текстових полів, які потребують цієї функції.


8
Щось мені підказує, що це недооцінений пост. Дуже багато відповідей популярні, оскільки вони "XAML-y". Але цей, здається, економить місце в більшості випадків.
j riv

3
+1 Це також дозволяє залишити в спокої UpdateSourceTrigger у випадку, якщо ви вже старанно отримали свої прив’язки TextBox, щоб вони вже поводилися так, як вам хочеться (зі стилями, валідацією, двостороннім і т.д.), але наразі просто не отримаєте введення після натискання клавіші Enter .
Джамін

2
Це найчистіший підхід, який я знайшов, і, на мою думку, його слід позначити як відповідь. Додано додаткову перевірку нуля на відправника, чек на Key.Return (клавіша Enter на моїй клавіатурі повертає Key.Return) та Keyboard.ClearFocus (), щоб видалити фокус з TextBox після оновлення властивості джерела. Я внесла зміни до відповіді, яка чекає експертної оцінки.
Ойштейн

2
Погодьтеся з коментарями вище. Але для мене KeyDown подія була більш доречно
Аллендер

1
Я використовував KeyBindingу XAML для запуску цього методу, оскільки мій інтерфейс має керування "За замовчуванням", яке вловлює ключ введення. Вам потрібно зафіксувати його в цьому TextBox, щоб зупинити його розповсюдження дерева інтерфейсу користувача до елемента управління за замовчуванням.
CAD

46

Я не вірю, що існує якийсь "чистий XAML" спосіб зробити те, що ви описуєте. Ви можете встановити прив'язку так, щоб вона оновлювалася щоразу, коли текст у TextBox змінюється (а не коли TextBox втрачає фокус), встановивши властивість UpdateSourceTrigger , як це:

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

Якщо ви встановите UpdateSourceTrigger на "Явне", а потім обробляєте подію PreviewKeyDown TextBox (шукаючи клавішу Enter), ви можете досягти того, що ви хочете, але це вимагатиме відставання коду. Можливо , якесь - то вкладене властивість (подібно моя EnterKeyTraversal власності) woudld роботи для вас.


3
Ця відповідь може бути простішою, ніж відповідь, позначена як відповідь, але вона має деякі обмеження. Зображення, яке ви хочете провести певну перевірку в заданій функції зв'язаного властивості (перевірити, чи є вхідний). Ви не хочете викликати цю функцію перевірки після кожної клавіші, яку користувач натискав, чи не особливо (коли це не потребує певного часу).
sth_Weird

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

Можливо, є спосіб обійти цей крок, але в іншому випадку слід зв’язати так (використовуючи явне):

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

9
У WPF / Silverlight ви ніколи не повинні використовувати спадщину - вона змішує стилі та не така гнучка, як приєднана поведінка. Наприклад, за допомогою програми Attached Behaviors ви можете мати як Watermark, так і 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-style-реалізацію у своїй машині управління):

    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у вашій машині не було залежностей . Це залежить від того, наскільки суворі ваші правила щодо коду.


Приємно. Дуже чисто. Якщо задіяно багатозв'язування, можливо, вам доведеться використовувати BindingOperations.GetBindingExpressionBase (tBox, prop); а потім прив'язка.UpdateTarget ();
VoteCoffee

4

Ось такий підхід, який мені здається досить простим, і простіше, ніж додавання AttachedBehaviour (що також є правильним рішенням). Ми використовуємо UpdateSourceTrigger за замовчуванням (LostFocus для TextBox), а потім додаємо InputBinding до клавіші 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>

Тоді методи Command є

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

Наразі це, здається, працює добре і вловлює подію Enter Key у TextBox.


4

Це не відповідь на початкове запитання, а скоріше розширення прийнятої відповіді від @Samuel Jack. Я зробив наступне у власному застосуванні і був у захваті від елегантності рішення Самуїла. Він дуже чистий і дуже багаторазовий, оскільки його можна використовувати на будь-якому контролі, не тільки на TextBox. Я подумав, що цим слід поділитися з громадою.

Якщо у вас є Вікно з тисячею, TextBoxesяке потребує оновлення джерела прив'язки під час введення, ви можете приєднати цю поведінку до всіх них, включивши XAML нижче у свій, Window Resourcesа не приєднуючи його до кожного TextBox. Спершу ви , звичайно, повинні реалізувати прикріплену поведінку відповідно до поста Самуїла .

<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), який містить цільові TextBoxes.


2

Якщо ви використовуєте MultiBinding зі своїм TextBox, вам потрібно використовувати BindingOperations.GetMultiBindingExpressionметод замість 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();
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.