WPF ComboBox SelectedItem在TabControl Switch上设置为Null

时间:2010-08-10 04:45:04

标签: wpf xaml binding

我的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包含一个简单的Property:public Bar SelectedBar { get; set; }。我的问题是,当我为我的ComboBox设置值时,更改为另一个选项卡,然后更改回来,ComboBox再次为空。如果我在我的属性的setter上设置断点,当我切换到另一个选项卡时,我看到该属性已分配给null

根据我的理解,当切换选项卡时,它将从VisualTree中删除 - 但为什么将我的ViewModel属性设置为null?这使得我很难保持持久状态,并且检查value != null似乎不是正确的解决方案。任何人都可以在这种情况下摆脱一些吗?

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

11 个答案:

答案 0 :(得分:20)

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

博客文章的链接:

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

答案 1 :(得分:3)

我的应用正在使用avalondock&amp; prims并且有确切的问题。我对BSG有同样的想法,当我们在MVVM应用程序中切换选项卡或文档内容时,控件如listview + box,combobox从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事件。

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

但是,如果选择器的itemssource包含项目,则selecteditem将永远不会为null。它可能会影响 一些特殊的背景。

希望有所帮助。 快乐的说法! :d

longsam

答案 2 :(得分:1)

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

答案 3 :(得分:1)

修改 下面的东西工作(我希望...);我之所以开发它是因为我遵循了MVVM Lite页面上描述的SelectedItems路线。但是 - 为什么我要依靠SelectedItems?向我的商品添加IsSelected属性(如图here所示)会自动保留所选商品(上述链接中的mentioned cavet除外)。最后,更容易!

Inital Post: 好的 - 这是一件工作;我有一个带有SelectionMode =“Extension”的多列ListView,这使得整个事情相当复杂。我的出发点是从工作空间调用tabItems,类似于描述here

  1. 我确保在我的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;
    }
    
  2. 这使我只有在标签项(工作空间)实际处于活动状态时才更新ViewModel所选项。因此,即使选项卡项清除ListView.SelectedItems,我的ViewModel选定项列表也会保留。在ViewModel中:

    if (_isActive) { 
      // ... update ViewModel selected items, referred below as vm.selectedItems
    }
    
  3. 最后,当tabItem重新启用时,我连接到'Loaded'事件并恢复了SelectedItems。这是在View的代码隐藏中完成的。 (请注意,虽然我的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);
            }
          }
        }
      }
    }
    

答案 4 :(得分:1)

如果您在WPF中提起异步选择,那么从ComboBox中删除 IsSynchronizedWithCurrentItem =“True”,请参阅有关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

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

我使用上面的

解决了我的问题

答案 5 :(得分:0)

我曾经遇到过类似的问题。似乎组合框在VisibilityChanged事件中丢失了所选项。 Workarround是在发生之前清除绑定,并在返回时重置它。您也可以尝试将Binding设置为Mode = TwoWay

希望这有帮助

答案 6 :(得分:0)

我遇到了同样的问题,并使用附加到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的活动Tab时,Combobox将从您的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;
        }
    }
}

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

滚动浏览包含DataGrid es的虚拟化ComboBox时遇到同样的问题。使用IsSynchronizedWithCurrentItem不起作用,也没有更改SelectedItemItemsSource绑定的顺序。但这是一个似乎有效的丑陋黑客:

首先,给ComboBox x:Name ComboBox。对于具有单个<ComboBox x:Name="mComboBox" SelectedItem="{Binding SelectedTarget.WritableData, Mode=TwoWay}"> 的控件,这应该在XAML中。例如:

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

然后在代码隐藏中添加这两个事件处理程序:

q_inc.run()

答案 9 :(得分:0)

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

答案 10 :(得分:0)

如果值变为null,则不允许更改ViewModel的属性。

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

就是这样。