我有一个ListBox
,其ItemsSource
绑定到一个自定义类(正确)实现INotifyCollectionChanged
和SelectedItem
绑定到ViewModel中的字段。
问题在于,当我从SelectedItem
集合中删除当前ItemsSource
时,会立即将所选内容更改为相邻项目。如果它只是删除了选择,我非常希望。
对我来说这样的问题之所以如此。 ItemsSource
类包含来自某些其他集合的元素,这些元素要么满足一些(在运行时常量期间)谓词,要么Active
。成为Active
是"同步"因为SelectedItem
(其原因)。因此,只有在ListBox
被选中时才允许项目被允许,这意味着当用户选择其他项目时,它可能会消失。
我的功能(深入"型号")在SelectedItem
发生变化时被调用:
//Gets old Active item
var oldActiveSchema = Schemas.FirstOrDefault(sch => sch.IsActive);
//Makes the new Item active (which triggers adding it into `ItemsSource` in case it didn't satisfy the Predicate)
((PowerSchema)newActiveSchema).IsActive = true;
//Triggers PropertyChanged on ViewModel with the new Active item
CurrentSchema = newActiveSchema;
RaisePropertyChangedEvent(nameof(CurrentSchema)); (#1)
//Changes the old item so it stops being Active -> gets removed from `ItemsSource` (#2)
if (oldActiveSchema != null) { ((PowerSchema)oldActiveSchema).IsActive = false; }
问题在于,出于某种原因,ListBox
由于SelectedItem
的更改而导致的更新(#1}}被推迟(#1)被推迟(更新的消息) ListBox
可能最终在WPF消息循环中等待,直到当前计算结束。)
另一方面,从oldActiveSchema
移除ItemsSource
是立竿见影的,也会立即触发SelectedItem
更改为旧版{1}}一个(当您删除所选项目时,相反选择一个邻居)。并且因为SelectedItem
的更改触发了将CurrentSchema
设置为错误(相邻)项的函数,所以它会重写用户选择的CurrentSchema
(#1)并在更新消息时由于ListBox
运行PropertyChanged
,它只会使用相邻的更新。
非常感谢任何帮助。
实际代码,如果有人想深入挖掘:
SelectedItem
而不是用户选择的那个时
SelectedItem
将该方法输入为应该激活的方法SelectedItem
停止有效 - >从集合中删除(44-41)MoveCurrencyOffDeletedElement
移动SelectedItem
SelectedItem
更改为相邻的答案 0 :(得分:3)
问题的关键是您在IsSynchronizedWithCurrentItem="True"
上设置ListBox
。它的作用是使ListBox.SelectedItem
和ListBox.Items.CurrentItem
保持同步。此外,ListBox.Items.CurrentItem
与源集合的默认集合视图的ICollectionView.CurrentItem
属性同步(在您的情况下,此视图由CollectionViewSource.GetDefaultView(Schemas)
返回)。现在,当您从Schemas
集合中删除一个恰好是相应集合视图的CurrentItem
的项目时,默认情况下该视图会将其CurrentItem
更新为下一个项目(或之前的项目)如果删除的项目是最后一项,则为1;如果删除的项目是集合中唯一的项目,则为null
。
问题的第二部分是,当ListBox.SelectedItem
更改导致更新您的视图模型属性时,您的RaisePropertyChangedEvent(nameof(ActiveSchema))
在更新过程后被处理完成后,特别是在从ActiveSchema
setter返回控件之后。您可以观察到getter不会立即被击中,但只有在setter完成后才会被击中。重要的是,CurrentItem
视图的Schemas
也不会立即更新以反映新选择的项目。另一方面,当您在之前选择的项目上设置IsActive = false
时,会立即从Schemas
集合中“删除”此项目,从而导致更新CurrentItem
集合视图,链条立即继续更新ListBox.SelectedItem
。您可以观察到此时ActiveSchema
setter将再次被击中。因此,即使在您完成上一次更改(处理用户选择的项目)之前,您的ActiveSchema
也会再次更改(到之前所选项目旁边的项目)。
有几种方法可以解决这个问题:
<强>#1 强>
在IsSynchronizedWithCurrentItem="False"
上设置ListBox
(或保持不变)。这会让你的问题毫不费力地消失。但是,如果由于某种原因需要,请使用任何其他解决方案。
<强>#2 强>
阻止可重入尝试使用警卫标志设置ActiveSchema
:
bool ignoreActiveSchemaChanges = false;
public IPowerSchema ActiveSchema
{
get { return pwrManager.CurrentSchema; }
set
{
if (ignoreActiveSchemaChanges) return;
if (value != null && !value.IsActive)
{
ignoreActiveSchemaChanges = true;
pwrManager.SetPowerSchema(value);
ignoreActiveSchemaChanges = false;
}
}
}
这会导致视图模型忽略集合视图CurrentItem
的自动更新,最终ActiveSchema
将保持预期值。
<强>#3 强>
在“删除”之前选择的项目之前,手动将集合视图的CurrentItem
更新为新选择的项目。您将需要对MainWindowViewModel.Schemas
集合的引用,因此您可以将其作为参数传递给setNewCurrSchema
方法,或者将代码封装在委托中并将其作为参数传递。我只会展示第二个选项:
在PowerManager
课程中:
//we pass the action as an optional parameter so that we don't need to update
//other code that uses this method
private void setNewCurrSchema(IPowerSchema newActiveSchema, Action action = null)
{
var oldActiveSchema = Schemas.FirstOrDefault(sch => sch.IsActive);
((PowerSchema)newActiveSchema).IsActive = true;
CurrentSchema = newActiveSchema;
RaisePropertyChangedEvent(nameof(CurrentSchema));
action?.Invoke();
if (oldActiveSchema != null)
{
((PowerSchema)oldActiveSchema).IsActive = false;
}
}
在MainWindowViewModel
课程中:
public IPowerSchema ActiveSchema
{
get { return pwrManager.CurrentSchema; }
set
{
if (value != null && !value.IsActive)
{
var action = new Action(() =>
{
//this will cause a reentrant attempt to set the ActiveSchema,
//but it will be ignored because at this point value.IsActive == true
CollectionViewSource.GetDefaultView(Schemas).MoveCurrentTo(value);
});
pwrManager.SetPowerSchema(value, action);
}
}
}
请注意,这需要引用PresentationFramework
程序集。如果您不希望视图模型程序集中存在该依赖项,则可以创建一个视图将订阅的事件,并且视图将运行所需的代码(这已取决于PresentationFramework
程序集)。此方法在 MSDN 上的 Prism 5.0 指南中通常称为交互请求模式(请参阅User Interaction Patterns)部分。 / p>
<强>#4 强>
推迟先前所选项目的“删除”,直到绑定更新完成。这可以通过使用Dispatcher
:
private void setNewCurrSchema(IPowerSchema newActiveSchema)
{
var oldActiveSchema = Schemas.FirstOrDefault(sch => sch.IsActive);
((PowerSchema)newActiveSchema).IsActive = true;
CurrentSchema = newActiveSchema;
RaisePropertyChangedEvent(nameof(CurrentSchema));
if (oldActiveSchema != null)
{
//queue the code for execution
//in case this code is called due to binding update the current dispatcher will be
//the one associated with UI thread so everything should work as expected
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
((PowerSchema)oldActiveSchema).IsActive = false;
});
}
}
这需要引用WindowsBase
程序集,通过使用针对解决方案#3描述的方法,可以在视图模型程序集中避免这种情况。
就个人而言,我会选择解决方案#1或#2,因为它会让你的PowerManager
课程变得干净,#3和#4似乎容易出现意外行为。