【问题标题】:WPF ComboBox SelectedItem Set to Null on TabControl SwitchWPF ComboBox SelectedItem 在 TabControl 开关上设置为 Null
【发布时间】: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?

标签: wpf xaml binding


【解决方案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。

    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。这是在视图的代码隐藏中完成的。 (请注意,虽然我的 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); }
                  }
                  

                  就是这样。

                  【讨论】:

                    猜你喜欢
                    • 2023-03-09
                    • 2011-02-23
                    • 1970-01-01
                    • 2014-05-30
                    • 2011-01-10
                    • 2012-06-19
                    • 1970-01-01
                    • 2016-06-15
                    • 1970-01-01
                    相关资源
                    最近更新 更多