【问题标题】:Set SelectedValue to 0 when garbage is entered in ComboBox在 ComboBox 中输入垃圾时将 SelectedValue 设置为 0
【发布时间】:2016-06-01 13:14:42
【问题描述】:

我有一个简单的 ComboBox,如下所示:

<ComboBox ItemsSource="{DynamicResource ItemsCompColl}" 
          TextSearch.TextPath="ItemName"
          SelectedValue="{Binding ItemId, UpdateSourceTrigger=PropertyChanged, 
                                  ValidatesOnDataErrors=True}" SelectedValuePath="ItemId"
          Grid.IsSharedSizeScope="True">

    ........................

</ComboBox>

效果很好。现在,我使用绑定在 SelectedValue 中的 ItemId 属性来检查用户是否从组合框中选择了适当的项目。

问题:

当用户从 ComboBox 中选择一个值时,ItemId 属性设置为 ComboBox 中选定项的 Id。之后,如果用户转到下一个 Control 并返回 ComboBox 并向 ComboBox 输入一些垃圾值,则 ComboBox 的 ItemId 不会改变,我的意思是它不会重置为“0”。因此,我的验证失败,用户成功输入了垃圾值。

【问题讨论】:

  • 为什么不禁止手动输入并要求从组合框的值列表中进行选择?
  • @user469104 ComboBox 有很多项。如果不允许手动输入,则用户无法搜索项目。
  • 设置 IsEditable=true 不适合你?
  • @Pikoh 是的,我设置了 IsEditable=true 并且工作正常,但问题是由问题中提出的。
  • 我做了一个快速示例,但我不明白。就我而言,每次在组合框中按下新键时,都会触发 selectedvalue 设置器...

标签: c# wpf combobox


【解决方案1】:

好的,所以当ComboBox 的可编辑TextBox 中存在任何验证错误时,您希望将SelectedValue 设置为0。您需要检查Text 的验证结果,如果验证失败,则将SelectedValue 重置为0。

这是一个适合您的工作示例:

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <ComboBox ItemsSource="{Binding ComboboxItems}" 
                      IsEditable="True" DisplayMemberPath="ItemName"
                      Text="{Binding SelectedName, ValidatesOnDataErrors=True}"
                      SelectedValue="{Binding SelectedID, UpdateSourceTrigger=PropertyChanged}" 
                      SelectedValuePath="ItemId"
                      Grid.IsSharedSizeScope="True">                
            </ComboBox>
            <TextBox Text="{Binding SelectedID,UpdateSourceTrigger=PropertyChanged}"/>
        </StackPanel>
    </Grid>
</Window> 

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;    
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        MyViewModel mvm;
        public MainWindow()
        {
            InitializeComponent();
            mvm = new MyViewModel()
            {
                ComboboxItems = new ObservableCollection<ComboItem>() 
                { 
                    new ComboItem{ItemName="item1",ItemId=1},
                    new ComboItem{ItemName="item2",ItemId=2},
                    new ComboItem{ItemName="item3",ItemId=3}
                },
            };
            this.DataContext = mvm;
        }
    }
    public class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void RaisePropertyChanged(String propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
    }


    public class ComboItem : ObservableObject
    {
        private string _itemname;
        private int _itemid;
        public string ItemName
        {
            get
            {
                return _itemname;
            }
            set
            {
                _itemname = value;
                RaisePropertyChanged("ItemName");
            }
        }

        public int ItemId
        {
            get { return _itemid; }
            set
            {
                _itemid = value;
                RaisePropertyChanged("ItemId");
            }
        }
    }

    public class MyViewModel : ObservableObject, IDataErrorInfo
    {
        private int _selectedid;
        private string _selectedname;
        public ObservableCollection<ComboItem> ComboboxItems
        {
            get;
            set;
        }


        public int SelectedID
        {
            get { return _selectedid; }
            set
            {
                if (_selectedid != value)
                {
                    _selectedid = value;
                    RaisePropertyChanged("SelectedID");

                }
            }
        }
        public string SelectedName
        {
            get { return _selectedname; }
            set
            {
                if (_selectedname != value)
                {
                    _selectedname = value;
                    RaisePropertyChanged("SelectedName");

                }
            }
        }   

        public string Error
        {
            get { return this[SelectedName]; }
        }

        public string this[string columnName]
        {
            get {

                switch (columnName)
                {
                    case "SelectedName":
                        {
                            if (SelectedName!=null && ComboboxItems.Count(x => x.ItemName == SelectedName) == 0)
                            {
                                //reset selected value to 0
                                this.SelectedID = 0;
                                return "Invalid selection";

                            }
                            break;
                        }
                }
                return null;
            }
        }
    }
}

结果:

当用户输入有效文本(例如item1)时,下面的Textbox显示SelectedValue的正确ItemId,当用户输入无效文本时,所选值将重置为0。

P.S:当ComboBox中输入垃圾时,会一直显示验证错误提示(如上图红色边框),如果将SelectedItem绑定到某个属性上,则为null。所以如果有错误,你不应该关心SelectedValue,这就是为什么我说我不能在 cmets 中重现错误。

【讨论】:

  • Bolu 看到我在 OP 问题中的评论。要重现该问题,请在示例项目中放置一个 TextBox。在组合中选择一个值,单击文本框并返回组合。它选择所有文本,单击一次以取消选择它,然后写任何内容。你会看到SelectedValue的设置器没有被解雇
  • @Pikoh,感谢您提供的信息。我的答案是解决这个问题。
  • 我花了一整天的时间来解决这个问题,最后我依靠你的解决方案。这非常方便。非常感谢@Bolu ........
【解决方案2】:

你真的让我明白了,我从来没有意识到这个问题的存在。我找到了一个解决方案,它可以在您不关心清除焦点组合的情况下使用。可能有更好的方法,但没有我能想到的。也许有人有其他解决方案。

首先,在您的项目中添加对Windows.System.Interactivity 的引用,并将其添加到您的 XAML:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

然后,将此代码添加到组合框:

<ComboBox ItemsSource="{DynamicResource ItemsCompColl}" 
      TextSearch.TextPath="ItemName" x:Name="cbItems"
      SelectedValue="{Binding ItemId, UpdateSourceTrigger=PropertyChanged, 
                              ValidatesOnDataErrors=True}" SelectedValuePath="ItemId"
      Grid.IsSharedSizeScope="True">
      <i:Interaction.Triggers>
         <i:EventTrigger EventName="GotMouseCapture">
             <i:InvokeCommandAction Command="{Binding ClearCombo}" 
               CommandParameter="{Binding ElementName=cbItems}"/>
         </i:EventTrigger>
      </i:Interaction.Triggers>
</ComboBox>

最后,让我们在 ouw View Model 中创建命令:

RelayCommand<System.Windows.Controls.ComboBox> _clearCombo;
public ICommand ClearCombo
{
    get
    {
        if (_clearCombo == null)
        {
            _clearCombo = new RelayCommand<System.Windows.Controls.ComboBox>(this.ClearComboCommandExecuted,
            param => this.ClearComboCommandCanExecute());

        }
        return _clearCombo;
    }
}

private bool ClearComboCommandCanExecute()
{
    return true;
}

private void ClearComboCommandExecuted(System.Windows.Controls.ComboBox cb)
{
    cb.Text = "";
}

希望这对您的问题有所帮助。

编辑

好的,在@XAMlMAX 评论之后,我认为他是对的,这可以在代码后面轻松完成,并且在 MVVM 模式中可能会更好。只需将事件处理程序添加到组合框即可捕获GotMouseCapture

<ComboBox ItemsSource="{DynamicResource ItemsCompColl}" 
      TextSearch.TextPath="ItemName" x:Name="cbItems"
      SelectedValue="{Binding ItemId, UpdateSourceTrigger=PropertyChanged, 
                              ValidatesOnDataErrors=True}" SelectedValuePath="ItemId"
      Grid.IsSharedSizeScope="True" 
      GotMouseCapture="cbItems_GotMouseCapture" >

然后在视图后面的代码中:

private void cbItems_GotMouseCapture(object sender, MouseEventArgs e)
{
   ((ComboBox)sender).Text = "";
}

编辑 2

好吧,解决它的最后一个丑陋的想法。我一点也不喜欢它,但也许它可以解决你的问题。

首先,你必须订阅TextBoxBase.TextChanged事件:

<ComboBox ItemsSource="{DynamicResource ItemsCompColl}" 
      TextSearch.TextPath="ItemName" x:Name="cbItems"
      SelectedValue="{Binding ItemId, UpdateSourceTrigger=PropertyChanged, 
                              ValidatesOnDataErrors=True}" SelectedValuePath="ItemId"
      Grid.IsSharedSizeScope="True" 
      TextBoxBase.TextChanged="cbItems_TextChanged" >

然后在后面的代码中添加这段代码:

private void cbItems_TextChanged(object sender, TextChangedEventArgs e)
{
    string text = ((ComboBox)sender).Text;
   ((YourViewModel)this.DataContext).ItemId= text;
}

这样,您可以确保每当ComboBox 更改其文本时,您都会收到有关它的通知。这真的是可怕的代码,但我已经没有想法了......

【讨论】:

  • 干得好@Pikoh!!!!但是我要对你说的是,如果你把ComboBox的文字清除了,那就有问题了。让我给你解释一下。当用户正在编辑一个项目并想通过按 enter 或 tab 键从第一个字段转到最后一个字段时,他将通过组合框,他必须再次选择组合框中的数据。
  • 嗯……没想到。也许明天如果我有时间会想到一个解决方案。也许不是使用gotfocus,而是使用另一个事件,例如click...
  • 好消息,您已修复此问题,但 ViewModel 没有理由在其中包含 UI 元素!您打破了关注点分离的 MvvM 规则,最好的方法是在代码隐藏中。除了找到解决方案做得很好。
  • @XAMlMAX 好吧,viemodel 内部没有 UI 元素,它只是获取一个引用。我同意它可能不是纯 MVVM,但我认为例如对它进行单元测试不会有问题。无论如何,您可以使用视图背后的代码中的代码给我们一个更好的解决方案。我很想看看。
  • @Vishal 尝试我编辑的答案...只需将事件名称 GotFocus 更改为 GotMouseCapture。这样它就不会删除使用选项卡在组合中输入的值。
猜你喜欢
  • 2018-05-24
  • 1970-01-01
  • 1970-01-01
  • 2018-01-09
  • 2017-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-01
相关资源
最近更新 更多