【问题标题】:Prevent ComboBox from setting the selectedValue to null when it's not in the bound list当 ComboBox 不在绑定列表中时,防止 ComboBox 将 selectedValue 设置为 null
【发布时间】:2012-03-16 13:38:46
【问题描述】:

我不太确定如何处理这个问题。我正在使用一堆带有下拉列表的组合框,我们也允许用户设置属性。 (即货币 =“美元、加元、欧元”)。

有时,当我们加载数据时,我们会发现货币不在我们的列表中,例如“AUD”。在这种情况下,我们仍然希望组合框显示加载的值,并且当前选择的货币应该保持为“AUD”,除非用户选择更改它,在这种情况下,他们的唯一选项仍然是“USD, CAD, EUR”。

我的问题是,一旦控件变得可见,ComboBox 就会调用我的 SelectedCurrency 属性上的设置器并将其设置为null,大概是因为当前值“AUD”不在它的列表中。如何在不让用户在“货币”字段中输入任何他们想要的内容的情况下禁用此行为?

标签: c# wpf xaml .net-4.0


【解决方案1】:

IsEditable="True"IsReadOnly="True" 和您的SelectedItem 设置为您想要保存所选项目的任何对象

<ComboBox ItemsSource="{Binding SomeCollection}"
          Text="{Binding CurrentValue}"
          SelectedItem="{Binding SelectedItem}"
          IsEditable="True"
          IsReadOnly="True">

IsEditable 允许Text 属性显示不在列表中的值

IsReadOnly 使得Text 属性不可编辑

SelectedItem 存储所选项目。在用户选择列表中的项目之前,它将是null,因此在您的SaveCommand 中,如果SelectedItem == null 然后在保存到数据库时使用CurrentValue 而不是SelectedItem

【讨论】:

  • 如果 IsReadOnly=True,他们就不能再从下拉列表中选择值了吗?
  • @Alain 一开始我也是这么想的,但是做了一个快速测试,它仍然允许用户从下拉列表中选择一个项目并正确更改 SelectedItem,所以它出现了 IsReadOnly 标志仅在 IsEditable="True" 时影响 Text 属性
【解决方案2】:

这似乎是一个相当普遍的问题。想象一下,您在数据库中有一个查找列表,可能是一个员工列表。员工表有一个“在这里工作”标志。另一个表引用员工查找列表。当一个人离开公司时,您希望您的视图显示老员工的姓名,但不允许将来分配老员工。

这是我对类似货币问题的解决方案:

Xaml

<Page.DataContext>
    <Samples:ComboBoxWithObsoleteItemsViewModel/>
</Page.DataContext>
<Grid>
    <ComboBox Height="23" ItemsSource="{Binding Items}" 
              SelectedItem="{Binding SelectedItem}"/>
</Grid>

C#

// ViewModelBase and Set() are from MVVM Light Toolkit
public class ComboBoxWithObsoleteItemsViewModel : ViewModelBase
{
    private readonly string _originalCurrency;
    private ObservableCollection<string> _items;
    private readonly bool _removeOriginalWhenNotSelected;
    private string _selectedItem;

    public ComboBoxWithObsoleteItemsViewModel()
    {
        // This value might be passed in to the VM as a parameter
        // or obtained from a data service
        _originalCurrency = "AUD";

        // This list is hard-coded or obtained from your data service
        var collection = new ObservableCollection<string> {"USD", "CAD", "EUR"};

        // If the value to display isn't in the list, then add it
        if (!collection.Contains(_originalCurrency))
        {
            // Record the fact that we may need to remove this
            // value from the list later.
            _removeOriginalWhenNotSelected = true;
            collection.Add(_originalCurrency);
        }

        Items = collection;

        SelectedItem = _originalCurrency;
    }

    public string SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            // Remove the original value from the list if necessary
            if(_removeOriginalWhenNotSelected && value != _originalCurrency)
            {
                Items.Remove(_originalCurrency);
            }

            Set(()=>SelectedItem, ref _selectedItem, value);
        }
    }

    public ObservableCollection<string> Items
    {
        get { return _items; }
        private set { Set(()=>Items, ref _items, value); }
    }
}

【讨论】:

  • 我不得不做一些非常相似的事情。我必须创建一个特殊的绑定字典来维护它自己的源项目列表副本,并将缺少的索引添加到集合中。还必须进行一些静态缓存,这样您就不需要为最终绑定到同一集合的每个数据模板保留重复的列表视图。
【解决方案3】:

您应该将 ComboBox 的 IsEditable 设置为 true 并绑定 Text 属性而不是 SelectedValue 属性。

【讨论】:

    【解决方案4】:

    如果 IsEditable = false 则 ComboBox 不支持不在列表中的值。

    如果您希望用户操作添加一个值但不编辑该值或任何现有值,那么一种方法可能是将新值放入 TextBlock(不可编辑)和一个 Button 以让他们添加该值。如果他们从组合框中选择任何值,则隐藏 TextBlock 和 Button。

    另一种方法是将值添加到具有更复杂逻辑的列表中,如果选择了任何其他值,则删除该暂定值。并且在被选中之前,暂定值不会被持久化。

    【讨论】:

      【解决方案5】:

      他不想成为能够打字的用户,所以 IsEditable 似乎不在讨论范围内。

      我要做的只是将新值 AUD 添加到项目列表中

       ComboBoxItem Content="AUD" Visibility="Collapsed"
      

      然后 Text="AUD" 将在代码中工作,但不能从下拉列表中工作。 想象一下,可以为 ItemsSource 制作一个转换器,它绑定到 TEXT 框并自动将其添加到折叠

      【讨论】:

        【解决方案6】:

        这是我对这个问题的解决方案:

        XAML 如下所示:

        <DataTemplate>
            <local:CCYDictionary Key="{TemplateBinding Content}">
                <local:CCYDictionary.ContentTemplate>
                    <DataTemplate>
                        <ComboBox Style="{StaticResource ComboBoxCellStyle}"
                                  SelectedValuePath="CCYName" 
                                  DisplayMemberPath="CCYName"
                                  TextSearch.TextPath="CCYName"
                                  ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:CCYDictionary}}, Path=ListItems}"
                                  SelectedValue="{Binding}" />
                    </DataTemplate>
                </local:CCYDictionary.ContentTemplate>
            </local:CCYDictionary>
        </DataTemplate>
        
        <!-- For Completion's sake, here's the style and the datacolumn using it -->
        <Style x:Key="ComboBoxCellStyle" TargetType="ComboBox">
            <Setter Property="IsEditable" Value="False"/>
            <Setter Property="IsTextSearchEnabled" Value="True"/>
            <!-- ...other unrelated stuff (this combobox was was a cell template for a datagrid) -->
        </Style>
        <Column FieldName="CCYcode" Title="Currency" DataTemplate="{StaticResource CCYEditor}" />
        

        字典可能有更好的方法来公开 ItemsSource,这样 Bindings 就不会那么难看,但是一旦我开始工作,我就厌倦了这个问题,无法进一步完善它。

        这样声明的个别字典:

        public class CCYDictionary : DataTableDictionary<CCYDictionary>
        {
            protected override DataTable table { get { return ((App)App.Current).ApplicationData.CCY; } }
            protected override string indexKeyField { get { return "CCY"; } }
            public CCYDictionary() { }
        }
        public class BCPerilDictionary : DataTableDictionary<BCPerilDictionary>
        {
            protected override DataTable table { get { return ((App)App.Current).ApplicationData.PerilCrossReference; } }
            protected override string indexKeyField { get { return "BCEventGroupID"; } }
            public BCPerilDictionary() { }
        }
        //etc...
        

        基类如下:

        public abstract class DataTableDictionary<T> : ContentPresenter where T : DataTableDictionary<T>
        {
            #region Dependency Properties
            public static readonly DependencyProperty KeyProperty = DependencyProperty.Register("Key", typeof(object), typeof(DataTableDictionary<T>), new PropertyMetadata(null, new PropertyChangedCallback(OnKeyChanged)));
            public static readonly DependencyProperty RowProperty = DependencyProperty.Register("Row", typeof(DataRowView), typeof(DataTableDictionary<T>), new PropertyMetadata(null, new PropertyChangedCallback(OnRowChanged)));
            public static readonly DependencyProperty ListItemsProperty = DependencyProperty.Register("ListItems", typeof(DataView), typeof(DataTableDictionary<T>), new PropertyMetadata(null));
            public static readonly DependencyProperty IndexedViewProperty = DependencyProperty.Register("IndexedView", typeof(DataView), typeof(DataTableDictionary<T>), new PropertyMetadata(null));
            #endregion Dependency Properties
        
            #region Private Members
            private static DataTable _SourceList = null;
            private static DataView _ListItems = null;
            private static DataView _IndexedView = null;
            private static readonly Binding BindingToRow;
            private static bool cachedViews = false;
            private bool m_isBeingChanged;
            #endregion Private Members
        
            #region Virtual Properties
            protected abstract DataTable table { get; }
            protected abstract string indexKeyField { get; }
            #endregion Virtual Properties
        
            #region Public Properties
            public DataView ListItems
            {
                get { return this.GetValue(ListItemsProperty) as DataView; }
                set { this.SetValue(ListItemsProperty, value); }
            }
            public DataView IndexedView
            {
                get { return this.GetValue(IndexedViewProperty) as DataView; }
                set { this.SetValue(IndexedViewProperty, value); }
            }
            public DataRowView Row
            {
                get { return this.GetValue(RowProperty) as DataRowView; }
                set { this.SetValue(RowProperty, value); }
            }
            public object Key
            {
                get { return this.GetValue(KeyProperty); }
                set { this.SetValue(KeyProperty, value); }
            }
            #endregion Public Properties
        
            #region Constructors
            static DataTableDictionary()
            {
                DataTableDictionary<T>.BindingToRow = new Binding();
                DataTableDictionary<T>.BindingToRow.Mode = BindingMode.OneWay;
                DataTableDictionary<T>.BindingToRow.Path = new PropertyPath(DataTableDictionary<T>.RowProperty);
                DataTableDictionary<T>.BindingToRow.RelativeSource = new RelativeSource(RelativeSourceMode.Self);
            }
        
            public DataTableDictionary()
            {
                ConstructDictionary();
                this.SetBinding(DataTableDictionary<T>.ContentProperty, DataTableDictionary<T>.BindingToRow);
            }
            #endregion Constructors
        
            #region Private Methods
            private bool ConstructDictionary()
            {            
                if( cachedViews == false )
                {
                    _SourceList = table;
                    if( _SourceList == null )
                    {   //The application isn't loaded yet, we'll have to defer constructing this dictionary until it's used.
                        return false;
                    }
                    _SourceList = _SourceList.Copy(); //Copy the table so if the base table is modified externally we aren't affected.
                    _ListItems = _SourceList.DefaultView;
                    _IndexedView = CreateIndexedView(_SourceList, indexKeyField);
                    cachedViews = true;
                }
                ListItems = _ListItems;
                IndexedView = _IndexedView;
                return true;
            }
        
            private DataView CreateIndexedView(DataTable table, string indexKey)
            {
                // Create a data view sorted by ID ( keyField ) to quickly find a row.
                DataView dataView = new DataView(table);
                dataView.Sort = indexKey;
                dataView.ApplyDefaultSort = true;
                return dataView;
            }
            #endregion Private Methods
        
            #region Static Event Handlers
            private static void OnKeyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
            {
                // When the Key changes, try to find the data row that has the new key.
                // If it is not found, return null.
                DataTableDictionary<T> dataTableDictionary = sender as DataTableDictionary<T>;
        
                if( dataTableDictionary.m_isBeingChanged ) return; //Avoid Reentry
                dataTableDictionary.m_isBeingChanged = true;
        
                try
                {
                    if( dataTableDictionary.IndexedView == null ) //We had to defer loading.
                        if( !dataTableDictionary.ConstructDictionary() )
                            return; //throw new Exception("Dataview is null. Check to make sure that all Reference tables are loaded.");
        
                    DataRowView[] result = _IndexedView.FindRows(dataTableDictionary.Key);
                    DataRowView dataRow = result.Length > 0 ? result[0] : null;
        
                    //Sometimes a null key is valid - but sometimes it's just xceed being dumb - so we only skip the following step if it wasn't xceed.
                    if( dataRow == null && dataTableDictionary.Key != null )
                    {
                        //The entry was not in the DataView, so we will add it to the underlying table so that it is not nullified. Treaty validation will take care of notifying the user.
                        DataRow newRow = _SourceList.NewRow();
                        //DataRowView newRow = _IndexedView.AddNew();
                        int keyIndex = _SourceList.Columns.IndexOf(dataTableDictionary.indexKeyField);
                        for( int i = 0; i < _SourceList.Columns.Count; i++ )
                        {
                            if( i == keyIndex )
                            {
                                newRow[i] = dataTableDictionary.Key;
                            }
                            else if( _SourceList.Columns[i].DataType == typeof(String) )
                            {
                                newRow[i] = "(Unrecognized Code: '" + (dataTableDictionary.Key == null ? "NULL" : dataTableDictionary.Key) + "')";
                            }
                        }
                        newRow.EndEdit();
                        _SourceList.Rows.InsertAt(newRow, 0);
                        dataRow = _IndexedView.FindRows(dataTableDictionary.Key)[0];
                    }
        
                    dataTableDictionary.Row = dataRow;
                }
                catch (Exception ex)
                {
                    throw new Exception("Unknow error in DataTableDictionary.OnKeyChanged.", ex);
                }
                finally
                {
                    dataTableDictionary.m_isBeingChanged = false;
                }
            }
        
            private static void OnRowChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
            {
                // When the Key changes, try to find the data row that has the new key.
                // If it is not found, return null.
                DataTableDictionary<T> dataTableDictionary = sender as DataTableDictionary<T>;
        
                if( dataTableDictionary.m_isBeingChanged ) return; //Avoid Reentry
                dataTableDictionary.m_isBeingChanged = true;
        
                try
                {
                    if( dataTableDictionary.Row == null )
                    {
                        dataTableDictionary.Key = null;
                    }
                    else
                    {
                        dataTableDictionary.Key = dataTableDictionary.Row[dataTableDictionary.indexKeyField];
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Unknow error in DataTableDictionary.OnRowChanged.", ex);
                }
                finally
                {
                    dataTableDictionary.m_isBeingChanged = false;
                }
            }
            #endregion Static Event Handlers
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-06-23
          • 2011-04-21
          • 1970-01-01
          • 2018-05-24
          • 2021-07-07
          • 2018-01-09
          • 1970-01-01
          相关资源
          最近更新 更多