WPF ComboBox + MVVM, запрос при выборе элемента

257
26 мая 2018, 02:40

В процессе работы над проектом возникла необходимость сделать ComboBox при выборе элемента которого выдавался бы запрос, Вроде - "А Вы уверены?", если нет возвращать старое значение. Вопрос что я делаю не так? FrameWork 4.0. Заранее спасибо.

Сейчас у меня получилось следующее:

<x:Array x:Key="UnitArray" Type="sys:UInt32" 
   xmlns:sys="clr-namespace:System;assembly=mscorlib">
     <sys:UInt32>0x0</sys:UInt32>
     <sys:UInt32>0xFFFFFFFF</sys:UInt32>
</x:Array>
<DataTemplate x:Key="ComboboxOfUnit" >
<StackPanel Orientation="Horizontal">                   
    <TextBlock  VerticalAlignment="Center" >
        <TextBlock.Style>
            <Style TargetType="TextBlock">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding}" Value="0x0">
                        <Setter Property="Text" Value="value1"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding}" Value="0xFFFFFFFF">
                        <Setter Property="Text" Value="value2"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
</StackPanel>
</DataTemplate>

в блоке ресурсов и

<ComboBox ItemsSource="{StaticResource ResourceKey=UnitArray}" 
      SelectedItem="{Binding UnitOf}" 
      ItemTemplate="{StaticResource ComboboxOfUnit}"/>

в основном теле xaml кода в модели имеем следующее

uint _prev_UnitOf;
public uint PrevUnitOf
{
    get { return _prev_UnitOf; }
    set
    {
        _prev_UnitOf = value;
        if (!_prev_UnitOf.Equals(_UnitOf))
        {
           // HasHasChangedArray[_UnitOf_] = true;
            //bfUnitOf = true;
            /HasChanged = true;
        }
        else
        {
            //HasHasChangedArray[_UnitOf_] = false;
            //bfUnitOf = false;
            //HasChanged = isChangedArray();
        }
        OnPropertyChanged(() => PrevUnitOf);
    }
}
uint _UnitOf;
public uint UnitOf
{
    get { return _UnitOf; }
    set
    {
        if (!_UnitOf.Equals(value))
            if (AppContext.Instance.ErrorReportMSG("Вы уверены?", "", Common.Interfaces.MsgType.Question))
            {
                _UnitOf = value;
                if (!_prev_UnitOf.Equals(_UnitOf))
                {
                    //HasHasChangedArray[_UnitOf_] = true;
                    //bfUnitOf = true;
                    //HasChanged = true;
                }
                else
                {
                    //HasHasChangedArray[_UnitOf_] = false;
                    //bfUnitOf = false;
                    //HasChanged = isChangedArray();
                }
            }
            else
            {
                _UnitOf = _prev_UnitOf;
                //bfUnitOf = false;
                //HasChanged = isChangedArray();
            }
        OnPropertyChanged(() => UnitOf);
    }
} 

где AppContext.Instance.ErrorReportMSG("Вы уверены?", "", Common.Interfaces.MsgType.Question) обертка над messagebox и OnPropertyChanged(() => UnitOf)

public void OnPropertyChanged(string propertyName)
{
    if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Метод генерации события изменения свойства объекта
/// </summary>
/// <typeparam name="T">Тип объекта</typeparam>
/// <param name="action">Лямбда функция доступа свойству</param>
protected void OnPropertyChanged<T>(Expression<Func<T>> action)
{
    var propertyName = GetPropertyName(action);
    OnPropertyChanged(propertyName);
}
/// <summary>
/// Метод получения имени параметра
/// </summary>
/// <typeparam name="T">Тип объекта</typeparam>
/// <param name="action">Лямбда функция доступа свойству</param>
/// <returns>Возвращает имя параметра</returns>
private static string GetPropertyName<T>(Expression<Func<T>> action)
{
    var expression = (MemberExpression)action.Body;
    var propertyName = expression.Member.Name;
    return propertyName;
}
Answer 1

По-началу вопрос мне показался достаточно простым, однако на практике я столкнулся с той же проблемой: ComboBox не хочет "отпрыгивать назад" в случае отмены изменения привязанного свойства SelectedItem. Решение нашлось путем гугления и экспериментов, вот ссылка 1 и ссылка 2. Наиболее полезной оказалась последняя.

Единственный недостаток в таком решении, по моему мнению, в том, что приходится использовать во ViewModel Dispatcher.BeginInvoke(), т.е. подключать using System.Windows; и using System.Windows.Threading;, что для ViewModel как бы не комильфо.

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

Такой XAML

<Canvas>
    <ComboBox Canvas.Left="30"
              Canvas.Top="40"
              Width="120"
              ItemsSource="{Binding Units}"
              SelectedIndex="{Binding SelectedUnit}" />
</Canvas>

А вот такая вьюмодель

public class MainViewModel : INotifyPropertyChanged
{
    private readonly IMainWindow _mainWindow;
    public event PropertyChangedEventHandler PropertyChanged;
    //ctor
    public MainViewModel(IMainWindow mainWindow)
    {
        _mainWindow = mainWindow;
    }
    public List<string> Units { get; set; } = new List<string> { "0x0", "0xFFFFFFFF" };
    private string _SelectedUnit;
    public string SelectedUnit
    {
        get => _SelectedUnit;
        set
        {
            // сохраняем старое значение
            var origValue = _SelectedUnit;
            //меняем значение в обычном порядке
            _SelectedUnit = value;
            //спрашиваем пользователя
            if (!_mainWindow.ShowYesNo("Изменить значение?"))
            {
                // возвращаем предыдущее значение, но только после того как
                // UI закончит свое обновление.
                Application.Current.Dispatcher.BeginInvoke(
                        new Action(() =>
                        {
                            _SelectedUnit = origValue;
                            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedUnit)));
                        }),
                        DispatcherPriority.ContextIdle,
                        null
                    );
                //и выходим
                return;
            }
            //Оповещаем как обычно изменение, сделанное до if (!_mainWindow.ShowYesNo("Изменить значение?"))
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedUnit)));
            //а здесь уже преобразуем изменившиеся значение
            //в необходимое uint
            //SetNewUnit(_SelectedUnit);
        }
    }
}

Иллюстрация работы

READ ALSO
Элементы формы в dll

Элементы формы в dll

Как использовать элементы формы в методах библиотеки и подключить SystemWindows

241
Sql joins in linq

Sql joins in linq

Есть замечательная схема SQL Joins, которая иллюстрирует различные типы соединений в SQL:

279
Как перегрузить оператор для класса с произвольным типом

Как перегрузить оператор для класса с произвольным типом

как я могу перегрузить оператор "+", чтобы можно было получить сумму двух узлов двоичного дерева поиска, учитывая то, что тип я у них сделал...

264