Запретить ComboBox устанавливать для selectedValue значение NULL, если его нет в связанном списке - PullRequest
4 голосов
/ 16 марта 2012

Я не совсем уверен, как справиться с этой проблемой.Я использую кучу комбинированных списков с выпадающими списками значений, которые мы позволяем пользователю также установить свойство.(т.е. Валюты = "USD, CAD, EUR").

Время от времени, когда мы загружаем данные, мы обнаруживаем, что в нашем списке нет такой валюты, как "AUD".В этом случае мы все еще хотим, чтобы в комбинированном ящике отображалось загруженное значение, и текущая выбранная валюта должна оставаться «AUD», если только пользователь не решит ее изменить, и в этом случае единственными опциями будут все еще «USD, CAD, EUR».

Моя проблема в том, что, как только элемент управления становится видимым, ComboBox вызывает сеттер для моего свойства SelectedCurrency и устанавливает его в null, предположительно, потому что текущего значения «AUD» нет в его списке,Как я могу отключить это поведение, не позволяя пользователю вводить все, что они хотят, в поле Валюта?

Ответы [ 6 ]

2 голосов
/ 16 марта 2012

Установите 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 при сохранении в базе данных

1 голос
/ 21 марта 2012

Это, кажется, довольно распространенная проблема. Представьте, что у вас есть список поиска в базе данных, возможно, список сотрудников. Стол сотрудника имеет флаг «работает здесь». Другая таблица ссылается на список поиска сотрудников. Когда человек покидает компанию, вы хотите, чтобы в его представлениях отображалось имя старого сотрудника, но не разрешалось назначать старого сотрудника в будущем.

Вот мое решение аналогичной проблемы с валютой:

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); }
    }
}
0 голосов
/ 21 марта 2012

Вот мое решение этой проблемы:

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}" />

Вероятно, для словаря есть более хороший способ раскрыть ItemSource, чтобы привязки не были такими уродливыми, но как только я заставил его работать, я слишком устал от этой проблемы, чтобы уточнить ее дальше.

отдельные словари, объявленные так:

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
}
0 голосов
/ 16 марта 2012

Он не хочет, чтобы пользователи могли печатать, поэтому IsEditable, кажется, не стоит на столе.

Я бы просто добавил новое значение AUD в список товаров как

 ComboBoxItem Content="AUD" Visibility="Collapsed"

Тогда Text = "AUD" будет работать в коде, но не из выпадающего списка. Чтобы быть фантазией, можно создать конвертер для ItemsSource, который привязывается к текстовому блоку и добавляет его автоматически свернутым

0 голосов
/ 16 марта 2012

Если IsEditable = false, то ComboBox не поддерживает значение, отсутствующее в списке.

Если вы хотите, чтобы действие пользователя добавляло значение, но не редактировало это значение или какие-либо существующие значения, тогда одним из подходов может быть помещение нового значения в TextBlock (не редактируемое) и кнопки, чтобы позволить им добавить это значение. Если они выбирают какое-либо значение из выпадающего списка, то скрывают TextBlock и Button.

Другой подход заключается в добавлении значения в список с более сложной логикой, которая заключается в том, что если выбрано любое другое значение, то это предварительное значение удаляется. И предварительное значение не сохраняется, пока оно не выбрано.

0 голосов
/ 16 марта 2012

Вы должны установить IsEditable для ComboBox в true и связать свойство Text вместо свойства SelectedValue.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...