诊断
您问题的关键在于您在ListBox
上设置了IsSynchronizedWithCurrentItem="True"
。它的作用是保持ListBox.SelectedItem
和ListBox.Items.CurrentItem
同步。另外,ListBox.Items.CurrentItem
与源集合的默认集合视图的ICollectionView.CurrentItem
属性同步(在您的情况下,此视图由CollectionViewSource.GetDefaultView(Schemas)
返回)。现在,当您从Schemas
集合中删除一个项目(也恰好是相应集合视图的CurrentItem
)时,默认情况下视图将其CurrentItem
更新为下一个项目(或前一个,如果删除的项目是最后一个,或者如果删除的项目是集合中唯一的项目,则为null
)。
问题的第二部分是,当ListBox.SelectedItem
改变引起的更新您的视图模型属性,您的RaisePropertyChangedEvent(nameof(ActiveSchema))
处理后的更新过程结束时,特别是从ActiveSchema
返回的控制之后二传手。您可以观察到吸气剂不会立即被击中,而只会在吸气器完成后才会被击中。重要的是,Schemas
视图的CurrentItem
也不会立即更新以反映新选择的项目。另一方面,当您在先前选择的项目上设置IsActive = false
时,会立即从Schemas
集合中“移除”该项目,这会导致集合视图的CurrentItem
更新,并且链条会立即继续更新ListBox.SelectedItem
。你可以观察到在这一点上,ActiveSchema
二传手会再次被击中。因此即使在您完成前一个更改(对用户选择的项目)的处理之前,您的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
)。该方法通常被称为交互请求模式(参见User Interaction Patterns)Prism 5.0指南MSDN)。
#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似乎很容易出现意想不到的行为。
删除选择应该只需要将ViewModel中的SelectedItem属性设置为null。由于问题是模式更改,只需将您的selecteditem存储在本地变量中,将selecteditem设置为null,然后*然后*从集合中删除所选项目。 –
放在[#WPF](https://chat.stackoverflow.com/rooms/18165/wpf)中,如果我没有在聊天中激活,请给我一个ping。我查看了您的代码,但无法立即找出如何触发该问题。还有其他很多有帮助的居民,如果我不活跃,他们可以伸出援助之手。 – Maverik
布兰登的选择改变的想法也是我的想法。另外,如果您将Selector.IsSelected绑定到IsActive ..您的选择可以自动跟随IsActive标志,而不必处理当前项目 – Maverik