当SelectedIndex在ComboBox上更改时,防止文本更改

时间:2015-01-06 11:25:54

标签: c# combobox wpf-controls

我正在UserControl创建ComboBox。我的目标是让用户能够写出卡片名称的一部分,并且在写入时,与字符串匹配的卡片如下所示,然后用户可以使用光标选择他想要的卡片

这就是我现在所拥有的:

XAML:

<UserControl x:Class="UrSimulator.View.UserControls.SearchMaxedCardComboBox"
             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:System="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d">
    <UserControl.Resources>
        <DataTemplate x:Key="MaxedCardTemplate">
            <!-- The template works so I've removed it to avoid clutter -->
        </DataTemplate>
    </UserControl.Resources>
        <ComboBox x:Name="SearchBox"
                  HorizontalAlignment="Left" VerticalAlignment="Top" Width="120" 
                  IsEditable="True" IsSynchronizedWithCurrentItem="False" IsTextSearchEnabled="False" 
                  ItemTemplate="{StaticResource MaxedCardTemplate}"
                  TextBoxBase.TextChanged="ComboBox_TextChanged" 
                  GotFocus="ComboBox_GotFocus" 
                  LostFocus="ComboBox_LostFocus"/>
</UserControl>

代码背后:

public partial class SearchMaxedCardComboBox : UserControl
{
    public InMemoryManager InMemoryManager { get; set; } // In Memory Database where cards are stored
    public CardBase SelectedCard { get; set; }

    private string DefaultText; 

    public SearchMaxedCardComboBox()
    {
        InitializeComponent();
        DefaultText = Properties.UIStrings.ui_calculator_search_card; // == Name (min 2 chars)
        SearchBox.Text = DefaultText;
    }

    private void ComboBox_GotFocus(object sender, RoutedEventArgs e)
    {
        ComboBox control = sender as ComboBox;
        control.Text = "";
        control.IsDropDownOpen = true;
    }
    private void ComboBox_LostFocus(object sender, RoutedEventArgs e)
    {
        ComboBox control = sender as ComboBox;
        control.IsDropDownOpen = false;

        if (SelectedCard == null)
            control.Text = DefaultText;
        else
            control.Text = SelectedCard.Name;
    }
    private void ComboBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        ComboBox control = sender as ComboBox;

        if (control.Text == DefaultText)
            return;

        Debug.Assert(InMemoryManager != null);            

        List<string> names;
        if (control.Text.Length < 2) // If a search happens with 1 char or an empty string it slows down too much
            names = new List<string>();
        else
            names = InMemoryManager.LookForCardNames(control.Text); // List<string> with the names

        List<CardBase> cards = new List<CardBase>();
        foreach (string name in names)
            cards.Add(InMemoryManager.GetCardBase(name));

        control.Items.Clear();
        foreach (CardBase card in cards)
            control.Items.Add(new MaxedCardBaseViewModel(card));
    }

    private void SearchBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ComboBox control = sender as ComboBox;
        if (control.SelectedItem != null)
            SelectedCard = ((MaxedCardBaseViewModel)control.SelectedItem).Card;
    }
}

问题:SelectedIndex发生变化时,ComboBox上的文字也会发生变化。然后,文本匹配所选项目(在这种情况下,它将成为项目的类名称),TextChanged再次启动,搜索完成,没有结果,项目列表结束为空。

如何在选择项目时避免文本被更改?


更新:我正在尝试这个commenter所说的内容,这让我意识到我的问题有点受到XY Problem的影响,但我还在努力来自问题的类似代码,并将尝试使用他链接的“自动完成组合框”代码。

我发现这个question本质上与我的相同,但WinFormsWPF没有OnSelectionChangeCommittedTextUpdate替代方法,因为它们与OnSelectionChangeTextChanged的工作方式不同。

1 个答案:

答案 0 :(得分:2)

使用Sinatr链接,我设法创建一个按照我的意愿工作的控件。它做了以下事情:

  • 基数为ComboBox,因此在单个控件上集成了TextBoxListBox
  • ItemsSource更改时,Text会更新,因此它只显示
  • 元素
  • 当用户通过键盘更改选择时,必须使用Enter或Tab
  • 进行确认
  • 仅“已确认”选项(Enter,Tab,Mouse)将修改SelectedCard
  • 仅在文本最少2个字符(默认情况下)
  • 时搜索

它还有一些怪癖:

  • 如果用户使用键盘更改索引(向上/向下),然后在外部单击,则会选择最后突出显示的项目。这不是一个大问题,而是一个烦恼,所以它不会成为一个优先事项。
  • 使用ItemsTemplate并且列表大到不适合时的视觉错误,当使用键盘滚动超过视图限制时,所选项目不会滚动到。

即使有这些极小的怪癖,我认为它已经解决了。

以下是代码:

<强> XAML:

<UserControl x:Class="MyApp.SearchCardControl"
             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:UrSimulator.View.UserControls"
             mc:Ignorable="d">
    <UserControl.Resources>
        <DataTemplate x:Key="CardTemplate">
        <!-- ... -->
        </DataTemplate>
    </UserControl.Resources>
    <Grid>
        <local:SearchComboBox HorizontalAlignment="Left" VerticalAlignment="Top" Width="Auto" 
            x:Name="SearchBox"
            ItemTemplate="{StaticResource CardTemplate}"
            IsEditable="True" 
            IsSynchronizedWithCurrentItem="False" 
            IsTextSearchEnabled="False" 
            StaysOpenOnEdit="True"
            ScrollViewer.CanContentScroll="False"
            TextBoxBase.TextChanged="TextBox_TextChanged" 
            SelectionChanged="SearchBox_SelectionChanged" 
            LostKeyboardFocus="SearchBox_LostKeyboardFocus" />
    </Grid>
</UserControl>

代码背后:

public partial class SearchCardControl : UserControl, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public static readonly DependencyProperty SelectedCardProperty =
        DependencyProperty.Register("SelectedCard", typeof(CardBase), typeof(SearchCardControl), new FrameworkPropertyMetadata(null));

    public InMemoryManager InMemoryManager { get; set; }
    public CardBase SelectedCard
    {
        get { return (CardBase)GetValue(SelectedCardProperty); }
        set
        {
            SetValue(SelectedCardProperty, value);
            this.Notify(PropertyChanged);
        }
    }

    private int _minimumSearchChars;
    public int MinimumSearchChars
    {
        get { return _minimumSearchChars; }
        set { if (value > 0) _minimumSearchChars = value; }
    }
    public string DefaultText { get { return String.Format(Properties.UIStrings.ui_calculator_search_card, MinimumSearchChars); } }

    public SearchCardControl()
    {
        InitializeComponent();
        SearchBox.SetTextWithoutSearching(DefaultText);
        MinimumSearchChars = 2;
    }

    private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        SearchComboBox control = (SearchComboBox)sender;
        if (control.IsSearchNeeded)
        {
            if (control.Text.Length >= MinimumSearchChars)
                control.ItemsSource = Search(control.Text);
            else
                control.ItemsSource = new List<object>();
        }
    }
    private void SearchBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        SearchComboBox control = (SearchComboBox)sender;
        if (control.SelectedItem == null)
            control.SetTextWithoutSearching(DefaultText);
        else
        {
            SelectedCard = (CardBase)control.SelectedItem;
            control.SetTextWithoutSearching(SelectedCard.Name);
        }
    }
    private void SearchBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        if (SelectedCard == null)
            SearchBox.SetTextWithoutSearching(DefaultText);
        else
            SearchBox.Text = SelectedCard.Name;
    }

    private List<CardBase> Search(string partialName)
    {
        // Whatever floats your boat
        // MyList.FindAll(x => x.FieldToCompareForExampleCardName.IndexOf(partialName, StringComparison.OrdinalIgnoreCase) >= 0);
        // Or you could implement a delegate here
    }
}

internal class SearchComboBox : ComboBox
{
    internal bool IsSearchNeeded = true;
    internal SelectionChangedEventArgs LastOnSelectionChangedArgs;

    internal void SetTextWithoutSearching(string text)
    {
        IsSearchNeeded = false;
        Text = text;
        IsSearchNeeded = true;
    }

    protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
    {
        if (IsSearchNeeded)
        {
            Text = "";
            IsDropDownOpen = true;
        }
        base.OnGotKeyboardFocus(e);
    }
    protected override void OnSelectionChanged(SelectionChangedEventArgs e)
    {  
        if (SelectedIndex != -1)
            LastOnSelectionChangedArgs = e;
    }
    protected override void OnDropDownClosed(EventArgs e)
    {
        if (LastOnSelectionChangedArgs != null)
            base.OnSelectionChanged(LastOnSelectionChangedArgs);
        base.OnDropDownClosed(e);
    }
    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        if (e.Key == Key.Tab || e.Key == Key.Enter)
        {
            IsDropDownOpen = false;
        }
        else if (e.Key == Key.Escape)
        {
            SelectedIndex = -1;
            IsDropDownOpen = false;
        }
        else
        {
            if (e.Key == Key.Down)
                this.IsDropDownOpen = true;
            base.OnPreviewKeyDown(e);
        }
    }
    protected override void OnKeyUp(KeyEventArgs e)
    {
        if (!(e.Key == Key.Up || e.Key == Key.Down || e.Key == Key.Tab || e.Key == Key.Enter))
            base.OnKeyUp(e);
    }
}