Подтвердить что ты не робот

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 не кажется правильным решением. Может ли кто-нибудь пролить некоторые подобные ситуации?

Изменить: стек вызовов в точке останова сеттера показывает только [Внешний код] - никаких подсказок нет.

4b9b3361

Ответ 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); }
}

Что это.