我的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断点处的调用堆栈仅显示[外部代码] - 没有提示。
答案 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。
我确保在我的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。这是在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
我使用上面的
解决了我的问题答案 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
不起作用,也没有更改SelectedItem
和ItemsSource
绑定的顺序。但这是一个似乎有效的丑陋黑客:
首先,给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); }
}
就是这样。