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】:滚动浏览包含ComboBox
es 的虚拟化DataGrid
时,我遇到了同样的问题。使用 IsSynchronizedWithCurrentItem
不起作用,更改 SelectedItem
和 ItemsSource
绑定的顺序也不起作用。但这里有一个看起来很有效的丑陋技巧:
首先,给你的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 MVVM ComboBox SelectedItem 或 SelectedValue 不起作用
WPF中ComboBox控件的SelectedItem和SelectedValue的MVVM绑定