WPF ComboBox SelectedItem 在 TabControl 开关上设置为 Null

Posted

技术标签:

【中文标题】WPF ComboBox SelectedItem 在 TabControl 开关上设置为 Null【英文标题】:WPF ComboBox SelectedItem Set to Null on TabControl Switch 【发布时间】:2011-03-27 15:25:28 【问题描述】:

我的 WPF 应用程序中遇到了一个简单的问题,我的头撞到了桌子上。我有一个 TabControl,其中每个 TabItem 都是使用类似于此的 DataTemplate 为 ViewModel 生成的视图:

<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 似乎不是正确的解决方案。任何人都可以对这种情况有所了解吗?

编辑:setter 断点处的调用堆栈仅显示 [外部代码] - 没有提示。

【问题讨论】:

您是否检查过在代码中第一次设置了所选项目?我遇到过一些选择可见但 selecteditem==null 的情况,尤其是使用 SubSonic 3 类。 这是一个好主意 - 但值肯定是第一次存储。当我中断时,我可以看到 value = null 并且我的变量正在存储之前选择的值。 你能显示那个断点的调用堆栈吗? 不 - 我应该提到这一点,但调用堆栈上的唯一信息是当前调用将属性设置为 null,以及 [外部代码]。 你认为这与切换标签时引发的事件有关吗?我的意思是,该事件可以向下传递到触发 SelectedItem 更改的 ComboBox? 【参考方案1】:

我们刚刚遇到了同样的问题。我们找到了一个描述该问题的博客条目。看起来这是 WPF 中的一个错误,并且有一个解决方法: ItemsSource 绑定之前指定 SelectedItem 绑定,问题应该会消失。

博客文章的链接:

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

【讨论】:

即使链接不起作用,建议的更改确实可以解决问题。 这似乎只能在某些时候解决问题 如果您使用 SelectedValue 而不是 SelectedIndex,此解决方案也适用 你帮助了我,但在我的情况下是相反的。我放了 SelectedItem,然后放了 ItemsSource。我认为这对我来说很有效,因为我的视图总是存在的,但它的 DataContext 会发生变化。因此,每次设置新上下文时,该视图中的 SelectedItem 都会设置为 null。我希望它有意义,如果没有,请尝试一下;它也可能适合你。【参考方案2】:

我的应用正在使用 avalondock 和 prims 并且遇到了确切的问题。我对 BSG 有同样的想法,当我们在 MVVM 应用程序中切换选项卡或文档内容时,列表视图 + 框、组合框等控件已从 VisualTree 中删除。我窃听并看到它们中的大多数数据都被重置为 null,例如 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 绑定到组合框,我们该怎么做,这样做不好 如果签入 SelectedEmployee 模型的 propertyChanged 事件。

基本上,我们不希望它的值被重置为空,保持它的预值。我找到了一个新的解决方案 始终如一。通过使用附加属性,我为 Selector 控件创建了 KeepSelection a-Pro,bool 类型,从而将其继承的所有内容提供为 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>

但是,如果选择器的 itemssource 有项目,则 selecteditem 永远不会为空。它可能会影响 一些特殊的上下文。

希望对您有所帮助。 祝你好运! :D

龙山

【讨论】:

康丁非常好。感谢您的解决方案。【参考方案3】:

通常,我使用 SelectedValue 而不是 SelectedItem。如果我需要与 SelectedValue 关联的对象,那么我将包含它的查找字段添加到目标对象(因为我使用 T4 模板生成我的视图模型,这往往是在一个部分类中)。如果您使用可为空的属性来存储 SelectedValue,那么您将遇到上述问题,但是如果将 SelectedValue 绑定到不可为空的值(例如 int),则 WPF 绑定引擎将丢弃该空值,因为它不适合目标。

【讨论】:

【参考方案4】:

编辑: 下面的东西有效(我希望......);我开发它是因为我遵循MVVM Lite 页面上描述的SelectedItems 路线。但是 - 为什么我要依赖SelectedItems?将IsSelected 属性添加到我的项目(如here 所示)会自动保留所选项目(缺少上面链接中的mentioned cavet)。最后,容易多了!

初始帖子: 好的-这是一件工作;我有一个带有 SelectionMode="Extension" 的多列 ListView,这使得整个事情变得相当复杂。我的出发点是从类似于 describe here 的工作区调用 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 选定项。因此,即使选项卡项清除了 ListView.SelectedItems,我的 ViewModel 选定项列表也会保留。在 ViewModel 中:

    if (_isActive)  
      // ... update ViewModel selected items, referred below as vm.selectedItems
    
    

    最后,当 tabItem 重新启用时,我连接到 'Loaded' 事件并恢复 SelectedItems。这是在视图的代码隐藏中完成的。 (请注意,虽然我的 ListView 有多个列,一个用作键,其他列仅供参考。ViewModel selectedItems 列表只保留键。否则,下面的比较会更复杂):

    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】:

滚动浏览包含ComboBoxes 的虚拟化DataGrid 时,我遇到了同样的问题。使用 IsSynchronizedWithCurrentItem 不起作用,更改 SelectedItemItemsSource 绑定的顺序也不起作用。但这里有一个看起来很有效的丑陋技巧:

首先,给你的ComboBox 一个x:Name。对于具有单个 ComboBox 的控件,这应该在 XAML 中。例如:

<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();
        
    

【讨论】:

【参考方案7】:

我曾经遇到过类似的问题。组合框似乎丢失了 VisibilityChanged 事件中的选定项。解决方法是在这种情况发生之前清除绑定,并在回来时将其重置。您也可以尝试将 Binding 设置为 Mode=TwoWay

希望对你有帮助

一月

【讨论】:

【参考方案8】:

我遇到了同样的问题,并通过附加到 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;
        
    

【讨论】:

【参考方案9】:

我认为问题可能在于您没有告诉组合框何时绑定回源。试试这个:

<ComboBox ItemsSource="Binding Path=BarList" DisplayMemberPath="Name" SelectedItem="Binding Path=SelectedBar,  UpdateSourceTrigger=PropertyChanged"/

【讨论】:

【参考方案10】:

您可以使用 MVVM 框架 Catel 和 catel:TabControl 元素,这个问题已经解决了。

【讨论】:

【参考方案11】:

如果 value 变为 null,请不要更改 ViewModel 的属性。

public Bar SelectedBar

    get  return barSelected; 
    set  if (value != null) SetProperty(ref barSelected, value); 

就是这样。

【讨论】:

以上是关于WPF ComboBox SelectedItem 在 TabControl 开关上设置为 Null的主要内容,如果未能解决你的问题,请参考以下文章

WPF combobox selectedItem的问题

WPF:为啥我的 ComboBox SelectedItem 不显示?

WPF MVVM ComboBox SelectedItem 或 SelectedValue 不起作用

WPF中ComboBox控件的SelectedItem和SelectedValue的MVVM绑定

WPF Datagrid Combobox SelectedItem 未正确绑定到 Powershell 对象

WPF ComboBox SelectedItem 在 TabControl 开关上设置为 Null