MVVM: привязка переключателей к модели просмотра?

EDIT: проблема была исправлена ​​в .NET 4.0.

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

Вот мой вопрос: есть ли простой и надежный способ привязки переключателей с помощью MVVM? Благодарю.

Дополнительная информация: Свойство IsChecked не работает по двум причинам:

  1. Когда выбрана кнопка, свойства IsChecked других кнопок в группе не становятся равными false .

  2. Когда выбрана кнопка, ее собственное свойство IsChecked не устанавливается после первого выбора кнопки. Я предполагаю, что привязка будет разбита WPF при первом щелчке.

Демо-проект: Вот код и разметка для простой демонстрации, которая воспроизводит проблему. Создайте проект WPF и замените разметку в Window1.xaml следующим образом:

       

Замените код в Window1.xaml.cs следующим кодом (взломом), который устанавливает модель представления:

 using System.Windows; namespace WpfApplication1 { ///  /// Interaction logic for Window1.xaml ///  public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { this.DataContext = new Window1ViewModel(); } } } 

Теперь добавьте следующий код в проект как Window1ViewModel.cs :

 using System.Windows; namespace WpfApplication1 { public class Window1ViewModel { private bool p_ButtonAIsChecked; ///  /// Summary ///  public bool ButtonAIsChecked { get { return p_ButtonAIsChecked; } set { p_ButtonAIsChecked = value; MessageBox.Show(string.Format("Button A is checked: {0}", value)); } } private bool p_ButtonBIsChecked; ///  /// Summary ///  public bool ButtonBIsChecked { get { return p_ButtonBIsChecked; } set { p_ButtonBIsChecked = value; MessageBox.Show(string.Format("Button B is checked: {0}", value)); } } } } 

Чтобы воспроизвести проблему, запустите приложение и нажмите кнопку A. Появится окно с сообщением о том, что свойство IsChecked Button A установлено в true . Теперь выберите Button B. Появится другое окно сообщения, в котором IsChecked свойство IsChecked Button B установлено в значение true , но нет windows сообщения, указывающего, что свойство IsChecked Button A установлено в false – свойство не было изменено.

Теперь снова нажмите кнопку A. Кнопка будет выбрана в окне, но не появится окно сообщения – свойство IsChecked не было изменено. Наконец, снова нажмите кнопку B – тот же результат. Свойство IsChecked не обновляется вообще ни для одной из кнопок после первого нажатия кнопки.

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

      

Похоже, они установили привязку к свойству IsChecked в .NET 4. Проект, который был нарушен в VS2008, работает в VS2010.

В интересах любого, кто исследует этот вопрос в будущем, вот решение, которое я в конечном итоге выполнил. Он основывается на ответе Джона Боуэна, который я выбрал как лучшее решение проблемы.

Во-первых, я создал стиль для прозрачного списка, содержащего переключатели в качестве элементов. Затем я создал кнопки в списке – мои кнопки фиксированы, а не читаются в приложении в качестве данных, поэтому я жестко закодировал их в разметке.

Я использую перечисление, называемое ListButtons в модели представления, чтобы представлять кнопки в списке, и я использую свойство Tag каждой кнопки, чтобы передать строковое значение значения enums, которое будет использоваться для этой кнопки. Свойство ListBox.SelectedValuePath позволяет мне указать свойство Tag в качестве источника для выбранного значения, которое я привязываю к модели представления с использованием свойства SelectedValue . Я думал, что мне понадобится конвертер значений для преобразования между строкой и ее значением enums, но встроенные преобразователи WPF обрабатывали преобразование без проблем.

Вот полная разметка для Window1.xaml :

                      Button A Button B    

Модель просмотра имеет одно свойство SelectedButton, которое использует перечисление ListButtons для отображения выбранной кнопки. Свойство вызывает событие в базовом classе, который я использую для моделей просмотра, что вызывает событие PropertyChanged :

 namespace RadioButtonMvvmDemo { public enum ListButtons {ButtonA, ButtonB} public class Window1ViewModel : ViewModelBase { private ListButtons p_SelectedButton; public Window1ViewModel() { SelectedButton = ListButtons.ButtonB; } ///  /// The button selected by the user. ///  public ListButtons SelectedButton { get { return p_SelectedButton; } set { p_SelectedButton = value; base.RaisePropertyChangedEvent("SelectedButton"); } } } } 

В моем производственном приложении средство SetButton вызовет метод classа сервиса, который примет действие, требуемое при выборе кнопки.

И чтобы быть полным, вот базовый class:

 using System.ComponentModel; namespace RadioButtonMvvmDemo { public abstract class ViewModelBase : INotifyPropertyChanged { #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion #region Protected Methods ///  /// Raises the PropertyChanged event. ///  /// The name of the changed property. protected void RaisePropertyChangedEvent(string propertyName) { if (PropertyChanged != null) { PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName); PropertyChanged(this, e); } } #endregion } } 

Надеюсь, это поможет!

Одним из решений является обновление ViewModel для переключателей в настройщике свойств. Если для кнопки A установлено значение True, установите для кнопки B значение false.

Другим важным фактором при привязке к объекту в DataContext является то, что объект должен реализовать INotifyPropertyChanged. Когда какое-либо связанное свойство изменяется, событие должно быть запущено и содержать имя измененного свойства. (Нулевая проверка опущена в примере для краткости.)

 public class ViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool _ButtonAChecked = true; public bool ButtonAChecked { get { return _ButtonAChecked; } set { _ButtonAChecked = value; PropertyChanged(this, new PropertyChangedEventArgs("ButtonAChecked")); if (value) ButtonBChecked = false; } } protected bool _ButtonBChecked; public bool ButtonBChecked { get { return _ButtonBChecked; } set { _ButtonBChecked = value; PropertyChanged(this, new PropertyChangedEventArgs("ButtonBChecked")); if (value) ButtonAChecked = false; } } } 

Редактировать:

Проблема заключается в том, что при первом нажатии на кнопку B значение IsChecked изменяется, и привязка проходит через, но кнопка A не передает через свое непроверенное состояние свойство ButtonAChecked. Посредством ручного обновления в коде установщик свойств ButtonAChecked будет вызван в следующий раз, когда будет нажата кнопка A.

Не уверен в каких-либо ошибках IsChecked, одном из возможных рефакторингов, которые вы можете внести в свою модель просмотра: представление имеет ряд взаимоисключающих состояний, представленных рядом с RadioButtons, только один из которых в любой момент времени может быть выбран. В модели представления просто есть 1 свойство (например, перечисление), которое представляет возможные состояния: stateA, stateB и т. Д. Таким образом вам не понадобится все отдельные ButtonAIsChecked и т. Д.

Вот еще один способ сделать это

ПОСМОТРЕТЬ:

        

ViewModel:

  private string selectedTitle; public string SelectedTitle { get { return selectedTitle; } set { SetProperty(ref selectedTitle, value); } } public RelayCommand TitleCommand { get { return new RelayCommand((p) => { selectedTitle = (string)p; }); } } 

Небольшое расширение ответа Джона Боуэна: оно не работает, когда значения не реализуют ToString() . Что вам нужно, а не устанавливать Content RadioButton в TemplateBinding, просто ContentPresenter в него:

      

Таким образом, вы можете дополнительно использовать DisplayMemberPath или ItemTemplate если это необходимо. RadioButton просто «обертывает» элементы, предоставляя выбор.

Вы должны добавить название группы для кнопки «Радио»

      

Я знаю, что это старый вопрос, и исходная проблема была решена в .NET 4. и, честно говоря, это немного не по теме.

В большинстве случаев, когда я хотел использовать RadioButtons в MVVM, он должен выбирать между элементами enums , для этого требуется привязать свойство bool в пространстве VM к каждой кнопке и использовать их для установки общего свойства enums, которое отражает фактический выбор, это очень утомительно. Поэтому я придумал решение, которое можно использовать повторно и очень легко реализовать, и не требует ValueConverters.

Вид почти такой же, но после того, как вы перечислите его, сторона VM может быть выполнена с одним свойством.

MainWindowVM

 using System.ComponentModel; namespace EnumSelectorTest { public class MainWindowVM : INotifyPropertyChanged { public EnumSelectorVM Selector { get; set; } private string _colorName; public string ColorName { get { return _colorName; } set { if (_colorName == value) return; _colorName = value; RaisePropertyChanged("ColorName"); } } public MainWindowVM() { Selector = new EnumSelectorVM ( typeof(MyColors), MyColors.Red, false, val => ColorName = "The color is " + ((MyColors)val).ToString() ); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void RaisePropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } 

Класс, выполняющий всю работу, наследуется от DynamicObject. При просмотре со стороны он создает свойство bool для каждого элемента в перечислении с префиксом «Is», «IsRed», «IsBlue» и т. Д., Которые могут быть связаны с XAML. Наряду со значением Value, которое содержит фактическое значение enums .

 public enum MyColors { Red, Magenta, Green, Cyan, Blue, Yellow } 

EnumSelectorVM

 using System; using System.ComponentModel; using System.Dynamic; using System.Linq; namespace EnumSelectorTest { public class EnumSelectorVM : DynamicObject, INotifyPropertyChanged { //------------------------------------------------------------------------------------------------------------------------------------------ #region Fields private readonly Action _action; private readonly Type _enumType; private readonly string[] _enumNames; private readonly bool _notifyAll; #endregion Fields //------------------------------------------------------------------------------------------------------------------------------------------ #region Properties private object _value; public object Value { get { return _value; } set { if (_value == value) return; _value = value; RaisePropertyChanged("Value"); _action?.Invoke(_value); } } #endregion Properties //------------------------------------------------------------------------------------------------------------------------------------------ #region Constructor public EnumSelectorVM(Type enumType, object initialValue, bool notifyAll = false, Action action = null) { if (!enumType.IsEnum) throw new ArgumentException("enumType must be of Type: Enum"); _enumType = enumType; _enumNames = enumType.GetEnumNames(); _notifyAll = notifyAll; _action = action; //do last so notification fires and action is executed Value = initialValue; } #endregion Constructor //------------------------------------------------------------------------------------------------------------------------------------------ #region Methods //--------------------------------------------------------------------- #region Public Methods public override bool TryGetMember(GetMemberBinder binder, out object result) { string elementName; if (!TryGetEnumElemntName(binder.Name, out elementName)) { result = null; return false; } try { result = Value.Equals(Enum.Parse(_enumType, elementName)); } catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException || ex is OverflowException) { result = null; return false; } return true; } public override bool TrySetMember(SetMemberBinder binder, object newValue) { if (!(newValue is bool)) return false; string elementName; if (!TryGetEnumElemntName(binder.Name, out elementName)) return false; try { if((bool) newValue) Value = Enum.Parse(_enumType, elementName); } catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException || ex is OverflowException) { return false; } if (_notifyAll) foreach (var name in _enumNames) RaisePropertyChanged("Is" + name); else RaisePropertyChanged("Is" + elementName); return true; } #endregion Public Methods //--------------------------------------------------------------------- #region Private Methods private bool TryGetEnumElemntName(string bindingName, out string elementName) { elementName = ""; if (bindingName.IndexOf("Is", StringComparison.Ordinal) != 0) return false; var name = bindingName.Remove(0, 2); // remove first 2 chars "Is" if (!_enumNames.Contains(name)) return false; elementName = name; return true; } #endregion Private Methods #endregion Methods //------------------------------------------------------------------------------------------------------------------------------------------ #region Events public event PropertyChangedEventHandler PropertyChanged; protected virtual void RaisePropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion Events } } 

Чтобы ответить на изменения, вы можете либо подписаться на событие NotifyPropertyChanged, либо передать анонимный метод конструктору, как указано выше.

И, наконец, MainWindow.xaml

    Red Magenta Blue Cyan Green Yellow     

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

У меня очень похожая проблема в VS2015 и .NET 4.5.1

XAML:

          

….

Как вы можете видеть в этом коде, у меня есть listview, а элементы в шаблоне – это радиообъекты, принадлежащие имени группы.

Если я добавлю новый элемент в коллекцию с выбранным свойством «Выбранный набор» в «Истина», он будет отмечен флажком, а остальные кнопки будут отмечены.

Я решаю его, сначала беря проверочную кнопку и устанавливаю ее вручную, но это не так, как это должно быть сделано.

код позади:

 `.... lstInCallList.ItemsSource = ContactCallList AddHandler ContactCallList.CollectionChanged, AddressOf collectionInCall_change ..... Public Sub collectionInCall_change(sender As Object, e As NotifyCollectionChangedEventArgs) 'Whenever collection change we must test if there is no selection and autoselect first. If e.Action = NotifyCollectionChangedAction.Add Then 'The solution is this, but this shouldn't be necessary 'Dim seleccionado As RadioButton = getCheckedRB(lstInCallList) 'If seleccionado IsNot Nothing Then ' seleccionado.IsChecked = False 'End If DirectCast(e.NewItems(0), PhoneCall).Selected = True ..... End sub 

`

 Male Female 

Ниже приведен код для IValueConverter

 public class GenderConvertor : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return !(bool)value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return !(bool)value; } } 

это сработало для меня. Даже значение получилось привязанным как к виду, так и к viewmodel в соответствии с щелчком переключателя. True -> Мужской и False -> Женский

Interesting Posts

Получение странного IP-адреса с маршрутизатора

Ресурс не найден TextView

Как настроить / создать UIPopoverController

.NET 4.0 assembly ссылок на сборку предупреждений MSB3644

Позволяя клавише «Ввод» нажать кнопку «Отправить», в отличие от использования только MouseClick

генерировать Zip-файл из azure blob-файлов

Windows 7 слишком долго запускает и выключает

Обмен Linux-сервером до полной загрузки памяти

Получение «файла не обнаружено» в заголовке Bridging при импорте инфраструктур Objective-C в проект Swift

Как запустить программы Java, нажав на их значок в Windows?

Остановка сценариев от взлома вашего сайта

Что такое выведенная защелка и как она создается, когда ей не хватает инструкции else в условии condition.can кто-нибудь объясняет кратко?

Java читает файл и сохраняет текст в массиве

Установка Windows 7 – проблема с CD \ DVD-драйвером на ноутбуке

Маршрутизация на основе ролей MVC

Давайте будем гением компьютера.