WPF ComboBox SelectedItem установлен в нуль на переключателе TabControl - PullRequest
17 голосов
/ 10 августа 2010

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

Редактировать: в стеке вызовов в точке останова установщика отображается только [Внешний код] - здесь никаких подсказок.

Ответы [ 11 ]

20 голосов
/ 02 ноября 2010

мы просто столкнулись с той же проблемой.Мы нашли запись в блоге, описывающую проблему.Похоже, что это ошибка в WPF, и есть обходной путь: Укажите привязку SelectedItem до привязки ItemsSource, и проблема должна быть устранена.

Ссылка на статью блога:

http://www.metanous.be/pharcyde/post/Bug-in-WPF-combobox-databinding.aspx

3 голосов
/ 13 октября 2010

Мое приложение использует avalondock & prims и имело именно эту проблему.Я тоже думал с BSG, когда мы переключали вкладку или содержимое документа в приложении MVVM, элементы управления, такие как listview + box, выпадающий список, удалялись из VisualTree.Я прослушал и увидел, что большинство данных из них были сброшены до нуля, таких как itemssource, selecteditem, .. но selectedboxitem все еще содержал текущее значение.

Подход в модели, проверьте, что его значение равно нулю, а затем верните так:

 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 с выпадающим списком, делать то же самое нехорошо, если проверка в свойстве propertyChanged модели SelectedEmployee.

По сути, мы не хотим, чтобы его значение сбрасывалось на ноль, сохраняем его-значение.Я нашел новое решение последовательно.Используя прикрепленное свойство, я создал KeepSelection a-Pro, тип bool, для элементов управления Selector, таким образом предоставив все его унаследованные отстой как 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;
        }
    }
}
}

Final, я использую этот подход просто в xaml:

<ComboBox lsControl:SelectorBehavior.KeepSelection="true"  
        ItemsSource="{Binding Offices}" 
        SelectedItem="{Binding SelectedEmployee.Office}" 
        SelectedValuePath="Id" 
        DisplayMemberPath="Name"></ComboBox>

Но selecteditem никогда не будет иметь нулевого значения, если в источнике предметов селектора есть предметы.Это может повлиять на какой-то особый контекст.

Надеюсь, это поможет.Счастливого кондинга!: D

longsam

1 голос
/ 11 февраля 2014

если вы используете асинхронный выбор в 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

Я решаю свою проблему, используя выше

1 голос
/ 01 января 2014

Редактировать: Ниже все работает (надеюсь ...);Я разработал его, потому что следовал маршруту SelectedItems, описанному на странице MVVM Lite .Однако - почему я хочу положиться на SelectedItems?Добавление свойства IsSelected к моим элементам (как показано здесь ) автоматически сохраняет выбранные элементы (за исключением упомянутого упомянутого cavet в ссылке выше).В конце концов, намного проще!

Начальное сообщение: ОК - это была работа;У меня есть многоколонный ListView с SelectionMode = "Extension", что делает все это довольно сложным.Моя отправная точка - вызов tabItems из рабочих пространств, подобных описанным здесь .

  1. Я убедился, что в моей ViewModel я знаю, когда активен элемент вкладки (рабочая область),(Это немного похоже на здесь ) - конечно, кто-то должен сначала инициализировать 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;
    }
    
  2. Это позволило мне обновить только выбранные элементы ViewModelесли элемент вкладки (рабочая область) действительно активен.Следовательно, мой список выбранных элементов ViewModel сохраняется даже тогда, когда элемент вкладки очищает ListView.SelectedItems.В ViewModel:

    if (_isActive) { 
      // ... update ViewModel selected items, referred below as vm.selectedItems
    }
    
  3. Последнее, когда tabItem был повторно включен, я подключился к событию «Loaded» и восстановил SelectedItems.Это делается в коде позади View.(Обратите внимание, что, хотя мой ListView имеет несколько столбцов, один служит ключом, другие служат только для информации. Список selectedItems ViewModel только сохраняет ключ. В противном случае приведенное ниже сравнение будет более сложным):

    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);
            }
          }
        }
      }
    }
    
1 голос
/ 03 мая 2012

Обычно я использую SelectedValue вместо SelectedItem. Если мне нужен объект, связанный с SelectedValue, тогда я добавляю поле поиска, содержащее это, к целевому объекту (так как я использую шаблоны T4 для генерации моих моделей просмотра, это, как правило, в частичном классе). Если вы используете свойство Nullable для хранения SelectedValue, вы столкнетесь с проблемой, описанной выше, однако, если привязать SelectedValue к ненулевому значению (например, int), то механизм связывания WPF отбросит нулевое значение как неприемлемое для цель.

0 голосов
/ 14 декабря 2017

Только не допускайте изменения свойства вашего ViewModel, если значение становится нулевым.

public Bar SelectedBar
{
    get { return barSelected; }
    set { if (value != null) SetProperty(ref barSelected, value); }
}

Вот и все.

0 голосов
/ 03 июля 2017

Вы можете использовать инфраструктуру MVVM Catel и элемент catel: TabControl, где эта проблема уже решена.

0 голосов
/ 28 октября 2016

У меня была такая же проблема при прокрутке виртуализации 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();
        }
    }
}
0 голосов
/ 20 октября 2015

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

<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar,  UpdateSourceTrigger=PropertyChanged}"/
0 голосов
/ 29 сентября 2015

У меня была та же проблема, и я решил ее с помощью следующего метода, присоединенного к Combobox DataContextChanged-Event:

private void myCombobox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (sender is FrameworkElement && e.NewValue == null)
        ((FrameworkElement)sender).DataContext = e.OldValue;
}

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

Каждый раз, когда вы изменяете активную вкладку вашего TabControl, комбинированный список удаляется из вашего VisualTree и добавляется, если вы вернетесь к тому с помощью комбинированного списка.Если поле со списком удалено из 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;
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...