WPF:在自定义创建的用户控件中进行数据绑定

时间:2017-07-03 13:09:37

标签: c# wpf xaml data-binding

我创建了自己的WPF用户控件,这是一个包含自动完成建议的文本框。 XAML看起来像这样:

<UserControl x:Class="WpfApplication4.AutoCompleteTextBox"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:WpfApplication4"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Orientation="Vertical">
    <TextBox x:Name="textBox" />
    <ListBox x:Name="listBox" MaxHeight="100"/>
</StackPanel>

背后的代码如下:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication4
{
/// <summary>
/// A WPF component which has a textbox. If the user enters some text, a ListBox is displayed showing some suggestions which the user can follow or not.
/// </summary>
public partial class AutoCompleteTextBox : UserControl
{

    /// <summary>
    /// This event is invoked when the text of the textbox is changed.
    /// </summary>
    public event TextChangedEventHandler TextChanged;

    /// <summary>
    /// Needed for DataBinding of Suggestions
    /// </summary>
    public static DependencyProperty SuggestionsProperty;

    /// <summary>
    /// Needed for data binding of Text
    /// </summary>
    public static DependencyProperty TextProperty;

    /// <summary>
    /// A list of the suggestions which are displayed to the user.
    /// </summary>
    public List<string> Suggestions { get
        {
            return (List<string>) GetValue(SuggestionsProperty);
        }
        set
        {
            SetValue(SuggestionsProperty, value);
        }

    }

    /// <summary>
    /// True if showing of the suggestions is case-sensitive; false, if case-insensitive
    /// </summary>
    public bool CaseSensitive { get; set; }

    /// <summary>
    /// The text displayed inside the textbox
    /// </summary>
    public string Text
    {
        get
        {
            return textBox.Text;
            //return (string)GetValue(TextProperty);
        }
        set
        {
            SetValue(TextProperty, value);
            textBox.Text = value;
        }
    }

    /// <summary>
    /// Create a new AutoCompleteTextBox
    /// </summary>
    public AutoCompleteTextBox()
    {
        InitializeComponent();
        Suggestions = new List<string>();
        DataContext = this;
        listBox.Visibility = Visibility.Collapsed;
        CaseSensitive = true;
        textBox.TextChanged += ExternalTextEvent;
        textBox.TextChanged += textChanged;
        textBox.PreviewKeyDown += keyDown;
        textBox.LostFocus += lostFocus;
        textBox.GotFocus += gotFocus;
        listBox.SelectionChanged += selectionChanged;
    }

    private void ExternalTextEvent(object sender, TextChangedEventArgs e)
    {
        TextChanged?.Invoke(this, e);
    }

    static AutoCompleteTextBox()
    {
        SuggestionsProperty = DependencyProperty.Register(nameof(Suggestions), typeof(List<string>), typeof(AutoCompleteTextBox));
        TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(AutoCompleteTextBox));
    }

    private void gotFocus(object sender, RoutedEventArgs e)
    {
        updateListBox();
    }

    private void lostFocus(object sender, RoutedEventArgs e)
    {
        listBox.Visibility = Visibility.Collapsed;
    }

    private void keyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter || e.Key == Key.Return)
        {
            if (listBox.Visibility == Visibility.Visible)
            {
                listBox.Visibility = Visibility.Collapsed;
                textBox.TextChanged -= textChanged;
                textBox.Text = (string)listBox.Items[0];
                textBox.TextChanged += textChanged;
            }
        }
        if (e.Key == Key.Escape)
        {
            listBox.Visibility = Visibility.Collapsed;
        }
        //if(e.Key == Key.Down && listBox.Visibility == Visibility.Visible)
        //{
        //    textBox.LostFocus -= lostFocus;
        //    listBox.SelectedIndex = 0;
        //    textBox.LostFocus += lostFocus;
        //}
    }

    private void selectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (listBox.ItemsSource != null)
        {
            listBox.Visibility = Visibility.Collapsed;
            textBox.TextChanged -= textChanged;
            if (listBox.SelectedIndex != -1)
            {
                textBox.Text = listBox.SelectedItem.ToString();
            }
            textBox.TextChanged += textChanged;
        }
    }

    private void textChanged(object sender, TextChangedEventArgs e)
    {
        SetValue(TextProperty, textBox.Text);
        updateListBox();
    }

    private void updateListBox()
    {
        if (String.IsNullOrEmpty(textBox.Text) || Suggestions == null || Suggestions.Count == 0)
        {
            return;
        }
        List<string> autoList = new List<string>();
        foreach (string item in Suggestions)
        {
            if (CaseSensitive && item.StartsWith(textBox.Text))
            {
                autoList.Add(item);
            }
            if (!CaseSensitive && item.ToUpper().StartsWith(textBox.Text.ToUpper()))
            {
                autoList.Add(item);
            }
        }
        if (autoList.Count > 0)
        {
            listBox.ItemsSource = autoList;
            listBox.Visibility = Visibility.Visible;
        }
        else
        {
            listBox.Visibility = Visibility.Collapsed;
            listBox.ItemsSource = null;
        }
    }
}
}

如果我使用此用户控件,例如

<local:AutoCompleteTextBox x:Name="autocomplete" Suggestions="{Binding SomeList}" Text="{Binding SomeText,UpdateSourceTrigger=PropertyChanged}"/>

然后数据绑定对于建议列表非常有效,但它不适用于文本框的文本。我不知道自己做错了什么。有人可以帮忙吗?我将非常感激。

1 个答案:

答案 0 :(得分:0)

依赖项属性的CLR包装器的getter和setter应调用GetValueSetValue方法。如果您在设置属性时想要其他任何内容,则应该使用回调。

您可能还应该将Text属性的默认绑定模式设置为TwoWay

public string Text
{
    get
    {
        return (string)GetValue(TextProperty);
    }
    set
    {
        SetValue(TextProperty, value);
    }
}

static AutoCompleteTextBox()
{
    SuggestionsProperty = DependencyProperty.Register(nameof(Suggestions), typeof(List<string>), typeof(AutoCompleteTextBox));
    TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(AutoCompleteTextBox),
        new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnTextChanged)) { BindsTwoWayByDefault = true });
}

private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    AutoCompleteTextBox ctrl = d as AutoCompleteTextBox;
    ctrl.textBox.Text = e.NewValue as string;
}