WPF ComboBox SelectedItem установлен на Null на TabControl Switch
У меня есть простая проблема в моем приложении WPF, из-за которого я ударяю головой о стол. У меня есть TabControl, где каждый TabItem представляет собой представление, сгенерированное для ViewModel, с использованием DataTemplate, похожего на это:
<DataTemplate DataType="{x:Type vm:FooViewModel}">
<vw:FooView/>
</DataTemplate>
FooView содержит ComboBox:
<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar}"/>
и FooViewModel содержит простое свойство: public Bar SelectedBar { get; set; }
. Моя проблема в том, что когда я устанавливаю значение для своего ComboBox, перейдите на другую вкладку, а затем измените ее обратно, ComboBox снова пуст. Если я устанавливаю точку останова на сеттер для моего свойства, я вижу, что свойство присваивается null
при переключении на другую вкладку.
Из того, что я понимаю, когда табуляция переключается, она удаляется из VisualTree - но почему она устанавливает для свойства ViewModel значение null
? Это затрудняет для меня постоянное состояние, и проверка value != null
не кажется правильным решением. Может ли кто-нибудь пролить некоторые подобные ситуации?
Изменить: стек вызовов в точке останова сеттера показывает только [Внешний код] - никаких подсказок нет.
Ответы
Ответ 1
мы столкнулись с одной и той же проблемой. Мы нашли запись в блоге, описывающую проблему. Похоже, что это ошибка в WPF, и есть обходной путь:
Укажите привязку SelectedItem до привязки ItemsSource, и проблема не исчезнет.
Ссылка на статью в блоге:
http://www.metanous.be/pharcyde/post/Bug-in-WPF-combobox-databinding.aspx
Ответ 2
Мое приложение использует avalondock и prims и имеет эту точную проблему. Я так же думал с BSG, когда мы переключали вкладку или содержимое документа в приложении MVVM, элементы управления в виде списка + поле, combobox удаляется из VisualTree. Я прослушивал и видел, что большинство данных из них были reset равными нулю, например itemssource, selecteditem,.., но selectedboxitem все еще удерживал текущее значение.
Подход в модели, проверьте, что его значение равно null, затем возвращаем так:
private Employee _selectedEmployee;
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
if (_selectedEmployee == value ||
IsAdding ||
(value == null && Employees.Count > 0))
{
return;
}
_selectedEmployee = value;
OnPropertyChanged(() => SelectedEmployee);
}
Но этот подход может только решить довольно хорошо на первом уровне привязки. Я имею в виду,
как мы идем, если хотим привязать SelectedEmployee.Office к combobox, сделать то же самое не хорошо
если проверить событие propertyChanged модели SelectedEmployee.
В принципе, мы не хотим, чтобы его значение было reset null, сохраняйте его предварительную ценность. Я нашел новое решение
последовательно. Используя прикрепленное свойство, я создал KeepSelection a-Pro, тип bool для элементов управления Selector, таким образом, поставляя все свои унаследованные suck как listview, combobox...
public class SelectorBehavior
{
public static bool GetKeepSelection(DependencyObject obj)
{
return (bool)obj.GetValue(KeepSelectionProperty);
}
public static void SetKeepSelection(DependencyObject obj, bool value)
{
obj.SetValue(KeepSelectionProperty, value);
}
// Using a DependencyProperty as the backing store for KeepSelection. This enables animation, styling, binding, etc...
public static readonly DependencyProperty KeepSelectionProperty =
DependencyProperty.RegisterAttached("KeepSelection", typeof(bool), typeof(SelectorBehavior),
new UIPropertyMetadata(false, new PropertyChangedCallback(onKeepSelectionChanged)));
static void onKeepSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as Selector;
var value = (bool)e.NewValue;
if (value)
{
selector.SelectionChanged += selector_SelectionChanged;
}
else
{
selector.SelectionChanged -= selector_SelectionChanged;
}
}
static void selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selector = sender as Selector;
if (e.RemovedItems.Count > 0)
{
var deselectedItem = e.RemovedItems[0];
if (selector.SelectedItem == null)
{
selector.SelectedItem = deselectedItem;
e.Handled = true;
}
}
}
}
Финал, я использую этот подход просто в xaml:
<ComboBox lsControl:SelectorBehavior.KeepSelection="true"
ItemsSource="{Binding Offices}"
SelectedItem="{Binding SelectedEmployee.Office}"
SelectedValuePath="Id"
DisplayMemberPath="Name"></ComboBox>
Но selectitem никогда не будет null, если у элементов selector items есть элементы. Это может повлиять
некоторый особый контекст.
Надеюсь, что это поможет.
Счастливый conding!: D
longsam
Ответ 3
Как правило, я использую SelectedValue вместо SelectedItem. Если мне нужен объект, связанный с SelectedValue, то я добавляю поле поиска, содержащее это, к целевому объекту (поскольку я использую шаблоны T4 для gen viewmodels, это, как правило, в частичном классе). Если вы используете свойство nullable для хранения SelectedValue, тогда вы столкнетесь с проблемой, описанной выше, однако, если привязка SelectedValue к значению, отличному от nullable (например, int), тогда механизм привязки WPF отбросит нулевое значение как неприемлемое для цель.
Ответ 4
Edit:
Ниже работ (надеюсь...); Я разработал его, потому что я следил за маршрутом SelectedItems
, описанным на странице MVVM Lite. Однако - почему я хочу полагаться на SelectedItems
? Добавление свойства IsSelected
к моим элементам (как показано ниже здесь) автоматически сохраняет выбранные элементы (меньше упомянутой каветки в ссылке выше). В конце концов, намного проще!
Inital Post:
хорошо - это была часть работы; У меня есть несколько столбцов ListView с SelectionMode = "Расширение", что делает все довольно сложным. Моя начальная точка вызывает tabItems из рабочих областей, подобных описанию здесь.
-
Я убедился, что в моей модели ViewModel я знаю, когда активен элемент вкладки (рабочая область). (Это немного похоже на here) - конечно, кому-то сначала нужно инициализировать SelectedWorkspace.
private Int32 _selectedWorkspace;
public Int32 SelectedWorkspace {
get { return _selectedWorkspace; }
set {
_selectedWorkspace = value;
base.OnPropertyChanged("SelectedWorkspace");
}
}
protected Int32 _thisWorkspaceIdx = -1;
protected Int32 _oldSelectedWorkspace = -1;
public void OnSelectedWorkspaceChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "SelectedWorkspace") {
if (_oldSelectedWorkspace >= 0) {
Workspaces[_oldSelectedWorkpace].OnIsActivatedChanged(false);
}
Workspaces[SelectedWorkspace].OnIsActivatedChanged(true);
_oldSelectedWorkspace = SelectedWorkspace;
}
}
protected bool _isActive = false;
protected virtual void OnIsActivatedChanged(bool isActive) {
_isActive = isActive;
}
-
Это позволило мне обновить выбранные элементы ViewModel только в том случае, если элемент табуляции (рабочее пространство) был фактически активным. Следовательно, список избранных элементов ViewModel сохраняется, даже если элемент табуляции очищает ListView.SelectedItems. В ViewModel:
if (_isActive) {
// ... update ViewModel selected items, referred below as vm.selectedItems
}
-
Наконец, когда tabItem получил повторную активацию, я подключился к событию "Loaded" и восстановил SelectedItems. Это делается в коде для представления. (Обратите внимание, что хотя мой ListView имеет несколько столбцов, один служит ключом, другие - только для информации. Список избранных ViewModelItems содержит только ключ. В противном случае сравнение ниже было бы более сложным):
private void myList_Loaded(object sender, RoutedEventArgs e) {
myViewModel vm = DataContext as myViewModel;
if (vm.selectedItems.Count > 0) {
foreach (string myKey in vm.selectedItems) {
foreach (var item in myList.Items) {
MyViewModel.MyItem i = item as MyViewModel.MyItem;
if (i.Key == myKey) {
myList.SelectedItems.Add(item);
}
}
}
}
}
Ответ 5
если вы подаете асинхронный выбор в WPF, затем удалите его для IsSynchronizedWithCurrentItem = "True" для ComboBox, пожалуйста, обратитесь к документу о IsSynchronizedWithCurrentItem:
<ComboBox
Name="tmpName"
Grid.Row="10"
Width="250"
Text="Best Match Position List"
HorizontalAlignment="Left"
Margin="14,0,0,0"
SelectedItem="{Binding Path=selectedSurceList,Mode=TwoWay}"
ItemsSource="{Binding Path=abcList}"
DisplayMemberPath="Name"
SelectedValuePath="Code"
IsEnabled="{Binding ElementName=UserBestMatchYesRadioBtn,Path=IsChecked}">
</ComboBox>
также фиксирует привязку
первое использование SelectedItem
затем ItemsSource
исх:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/fb8a8ad2-83c1-43df-b3c9-61353979d3d7/comboboxselectedvalue-is-lost-when-itemssource-is-updated?forum=wpf
http://social.msdn.microsoft.com/Forums/en-US/c9e62ad7-926e-4612-8b0c-cc75fbd160fd/bug-in-wpf-combobox-data-binding
Я решаю свою проблему, используя приведенные выше
Ответ 6
У меня была аналогичная проблема. Кажется, что combobox теряет выбранный элемент в событии VisibilityChanged. Workarround - очистить привязку до того, как это произойдет, и reset при возврате. Вы также можете попробовать установить привязку к режиму = TwoWay
Надеюсь, что это поможет
Jan
Ответ 7
У меня была такая же проблема, и я решил ее с помощью следующего метода, связанного с Combobox DataContextChanged-Event:
private void myCombobox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement && e.NewValue == null)
((FrameworkElement)sender).DataContext = e.OldValue;
}
Поэтому каждый раз, когда вы хотите удалить datacontext из выпадающего списка, старый datacontext будет установлен снова.
Каждый раз, когда вы меняете активную вкладку TabControl, Combobox будет удаляться из вашего VisualTree и добавляться, если вы вернетесь к тому, которое было выведено с помощью combobox. Если поле со списком удалено из VisualTree, также для DataContext установлено значение null.
Или вы используете класс, который реализовал такую функцию:
public class MyCombobox : ComboBox
{
public MyCombobox()
{
this.DataContextChanged += MyCombobox_DataContextChanged;
}
void MyCombobox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement && e.NewValue == null)
((FrameworkElement)sender).DataContext = e.OldValue;
}
public void SetDataContextExplicit(object dataContext)
{
lock(this.DataContext)
{
this.DataContextChanged -= MyCombobox_DataContextChanged;
this.DataContext = dataContext;
this.DataContextChanged += MyCombobox_DataContextChanged;
}
}
}
Ответ 8
Я думаю, проблема может заключаться в том, что вы не сообщаете поле Combo, когда нужно привязываться к исходному. Попробуйте следующее:
<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar, UpdateSourceTrigger=PropertyChanged}"/
Ответ 9
У меня была такая же проблема при прокрутке виртуализации DataGrid
, содержащей ComboBox
es. Использование IsSynchronizedWithCurrentItem
не работало и не меняло порядок привязок SelectedItem
и ItemsSource
. Но вот уродливый хак, который, похоже, работает:
Сначала дайте ComboBox
x:Name
. Это должно быть в XAML для элемента управления с одним ComboBox
. Например:
<ComboBox x:Name="mComboBox" SelectedItem="{Binding SelectedTarget.WritableData, Mode=TwoWay}">
Затем добавьте эти два обработчика событий в ваш код:
using System.Windows.Controls;
using System.Windows;
namespace SATS.FileParsing.UserLogic
{
public partial class VariableTargetSelector : UserControl
{
public VariableTargetSelector()
{
InitializeComponent();
mComboBox.DataContextChanged += mComboBox_DataContextChanged;
mComboBox.SelectionChanged += mComboBox_SelectionChanged;
}
/// <summary>
/// Without this, if you grab the scrollbar and frantically scroll around, some ComboBoxes get their SelectedItem set to null.
/// Don't ask me why.
/// </summary>
void mComboBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateTarget();
}
/// <summary>
/// Without this, picking a new item in the dropdown does not update IVariablePair.SelectedTarget.WritableData.
/// Don't ask me why.
/// </summary>
void mComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateSource();
}
}
}
Ответ 10
Вы можете использовать MVVM-структуру Catel и элемент catel: TabControl, эта проблема уже решена.
Ответ 11
Просто не позволяйте изменять свойство ViewModel, если значение становится нулевым.
public Bar SelectedBar
{
get { return barSelected; }
set { if (value != null) SetProperty(ref barSelected, value); }
}
Что это.