ผูกกล่องข้อความเมื่อกดปุ่ม Enter


109

การเชื่อมโยงฐานข้อมูลเริ่มต้นTextBoxคือTwoWayและส่งข้อความไปยังคุณสมบัติเฉพาะเมื่อTextBoxสูญเสียโฟกัส

มีวิธี XAML ง่าย ๆ ในการทำให้การเชื่อมต่อฐานข้อมูลเกิดขึ้นเมื่อฉันกดEnterปุ่มบนTextBox? ฉันรู้ว่ามันค่อนข้างง่ายที่จะทำในโค้ดข้างหลัง แต่ลองนึกดูว่าTextBoxมันซับซ้อนDataTemplateไหม

คำตอบ:


139

คุณสามารถทำให้ตัวเองวิธีการ 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"/>

(คุณต้องแน่ใจว่าได้รวมการอ้างอิง xmlns clr-namespace สำหรับ "b" ไว้ในองค์ประกอบรากของไฟล์ XAML ของคุณที่ชี้ไปที่เนมสเปซที่คุณใส่ InputBindingsManager)


11
ในความพยายามที่จะช่วยผู้อ่านในอนาคตไม่กี่นาทีในการเล่นซอฉันเพิ่งใช้สิ่งนี้ในโครงการของฉันและตัวอย่าง XAML ที่ให้ไว้ข้างต้นไม่ได้ผล แหล่งที่มาได้รับการอัปเดตทุกการเปลี่ยนแปลงอักขระ การเปลี่ยน "UpdateSourceTrigger = PropertyChanged" เป็น "UpdateSourceTrigger = Explicit" ช่วยแก้ปัญหาได้ ตอนนี้ทุกอย่างทำงานได้ตามต้องการ
ihake

1
@ihake: ฉันคิดว่าการเปลี่ยนแปลงที่คุณแนะนำจะป้องกันไม่ให้อัปเดตเมื่อโฟกัสหายไป
VoteCoffee

4
UpdateSourceTrigger = PropertyChanged อาจไม่สะดวกเมื่อพิมพ์ตัวเลขจริง ตัวอย่างเช่น "3. " ทำให้เกิดปัญหาเมื่อผูกไว้กับลอย ฉันไม่แนะนำให้ระบุ UpdateSourceTrigger (หรือตั้งค่าเป็น LostFocus) นอกเหนือจากลักษณะการทำงานที่แนบมา สิ่งนี้ให้สิ่งที่ดีที่สุดของทั้งสองโลก
David Hollinshead

2
งานดีมาก! Tiny nit-pick: เมื่อการUpdatePropertySourceWhenEnterPressedเปลี่ยนแปลงจากค่าที่ถูกต้องเป็นค่าอื่นที่ถูกต้องคุณจะยกเลิกการสมัครและสมัครรับข้อมูลอีกครั้งPreviewKeyDownโดยไม่จำเป็น แต่สิ่งที่คุณควรต้องมีคือตรวจสอบว่าe.NewValueเป็นnullหรือไม่ ถ้าไม่nullสมัคร; มิฉะนั้นหากnullยกเลิกการเป็นสมาชิก
Nicholas Miller

1
ฉันชอบโซลูชันนี้ขอบคุณสำหรับการโพสต์! สำหรับใครก็ตามที่ต้องการแนบพฤติกรรมนี้กับ TextBoxes จำนวนมากในแอปพลิเคชันของคุณฉันโพสต์ส่วนขยายสำหรับคำตอบนี้เกี่ยวกับวิธีที่คุณสามารถบรรลุได้อย่างง่ายดาย ดูโพสต์ที่นี่
Nik

51

นี่คือวิธีที่ฉันแก้ไขปัญหานี้ ฉันสร้างตัวจัดการเหตุการณ์พิเศษที่ใส่รหัสไว้ข้างหลัง:

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 คนเดียวได้ในกรณีที่คุณได้รับการเชื่อมโยงกล่องข้อความของคุณอย่างระมัดระวังเพื่อให้ทำงานในแบบที่คุณต้องการแล้ว (ด้วยสไตล์การตรวจสอบความถูกต้องสองวัน ฯลฯ ) แต่ในปัจจุบันจะไม่ได้รับอินพุตหลังจากกด Enter .
Jamin

2
นี่เป็นแนวทางที่สะอาดที่สุดที่ฉันพบและในความคิดของฉันควรถูกระบุว่าเป็นคำตอบ เพิ่มการตรวจสอบ null เพิ่มเติมเกี่ยวกับผู้ส่งการตรวจสอบ Key.Return (ปุ่ม Enter บนแป้นพิมพ์ของฉันส่งคืน Key.Return) และ Keyboard.ClearFocus () เพื่อลบโฟกัสออกจากกล่องข้อความหลังจากอัปเดตคุณสมบัติต้นทาง ฉันได้ทำการแก้ไขคำตอบที่รอการตรวจสอบโดยเพื่อน
Oystein

2
เห็นด้วยกับความเห็นข้างบน แต่สำหรับฉันแล้วงาน KeyDown เหมาะสมกว่า
Allender

1
ฉันใช้KeyBindingใน XAML เพื่อเริ่มการทำงานของวิธีนี้เนื่องจาก UI ของฉันมีการควบคุม "ค่าเริ่มต้น" ที่จับคีย์ Enter คุณต้องจับมันภายในกล่องข้อความนี้เพื่อหยุดการแพร่กระจายโครงสร้าง UI ไปยังตัวควบคุม "ค่าเริ่มต้น"
CAD bloke

46

ฉันไม่เชื่อว่ามีวิธี "XAML บริสุทธิ์" ที่จะทำตามที่คุณอธิบายไว้ คุณสามารถตั้งค่าการเชื่อมโยงเพื่อให้อัปเดตเมื่อใดก็ตามที่ข้อความในกล่องข้อความเปลี่ยนไป (แทนที่จะเป็นเมื่อกล่องข้อความสูญเสียโฟกัส) โดยการตั้งค่าคุณสมบัติUpdateSourceTriggerดังนี้:

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

หากคุณตั้งค่า UpdateSourceTrigger เป็น "Explicit" จากนั้นจัดการเหตุการณ์ PreviewKeyDown ของ TextBox (มองหาคีย์ Enter) คุณจะสามารถบรรลุสิ่งที่คุณต้องการได้ แต่จะต้องใช้โค้ดข้างหลัง บางทีคุณสมบัติที่แนบมาบางประเภท (คล้ายกับคุณสมบัติEnterKeyTraversalของฉัน) woudld ทำงานให้คุณ


3
คำตอบนี้อาจง่ายกว่าคำตอบที่ระบุว่าเป็นคำตอบ แต่มีข้อ จำกัด บางประการ รูปภาพที่คุณต้องการตรวจสอบบางประเภทในฟังก์ชัน set-function ของคุณสมบัติที่ถูกผูกไว้ (ตรวจสอบว่าอินพุตถูกต้องหรือไม่) คุณไม่ต้องการเรียกใช้ฟังก์ชั่นตรวจสอบหลังจากที่ผู้ใช้กดทุกปุ่มใช่หรือไม่ (โดยเฉพาะอย่างยิ่งเมื่อฟังก์ชันใช้เวลาสักครู่)
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();
            }
        }
    }
}

อาจมีวิธีแก้ไขขั้นตอนนี้ แต่มิฉะนั้นคุณควรผูกแบบนี้ (โดยใช้ Explicit):

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

9
ใน WPF / Silverlight คุณไม่ควรใช้การสืบทอด - มันทำให้รูปแบบยุ่งเหยิงและไม่ยืดหยุ่นเท่า Attached Behaviors ตัวอย่างเช่นกับ Attached Behaviors คุณสามารถมีทั้ง Watermark และ UpdateOnEnter ในกล่องข้อความเดียวกัน
Mikhail

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); จากนั้นมีผลผูกพัน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>

จากนั้นวิธีการคำสั่งคือ

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

และกล่องข้อความถูกผูกไว้กับคุณสมบัติ

 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ที่จำเป็นต้องอัปเดต Binding Source บน Enter คุณสามารถแนบลักษณะการทำงานนี้กับหน้าต่างทั้งหมดได้โดยรวม XAML ด้านล่างไว้ในของคุณWindow Resourcesแทนที่จะแนบไปกับกล่องข้อความแต่ละอัน ก่อนอื่นคุณต้องใช้พฤติกรรมที่แนบมาตามโพสต์ของซามูเอลแน่นอน

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

คุณสามารถ จำกัด ขอบเขตได้ตลอดเวลาหากจำเป็นโดยใส่สไตล์ลงในทรัพยากรของหนึ่งในองค์ประกอบลูกของหน้าต่าง (เช่น a Grid) ที่มีกล่องข้อความเป้าหมาย


2

ในกรณีที่คุณกำลังใช้ MultiBinding กับกล่องข้อความของคุณคุณจำเป็นต้องใช้วิธีการแทนBindingOperations.GetMultiBindingExpressionBindingOperations.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.