我有一个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; }
Run Code Online (Sandbox Code Playgroud)
问题是由于某种原因,ListBox由于SelectedItem应该由(#1)触发的更改导致的更新被推迟(更新的消息ListBox可能最终在WPF消息循环中等待直到当前计算完成).
另一方面,oldActiveSchema从中移除ItemsSource是立即的,并且还立即触发更改为SelectedItem旧的旁边的一个(当您移除所选项目时,相反地选择邻居).并且因为SelectedItem触发器的更改我的功能设置CurrentSchema为错误的(相邻)项目,它重写用户选择的CurrentSchema(#1),并且当ListBox由于PropertyChanged运行而更新消息时,它只是用相邻的消息更新它.
任何帮助是极大的赞赏.
实际代码,如果有人想深入挖掘:
问题的关键在于您设置IsSynchronizedWithCurrentItem="True"了ListBox. 它的作用是保持ListBox.SelectedItem和ListBox.Items.CurrentItem同步。此外,ListBox.Items.CurrentItem与源集合的默认集合视图的属性同步ICollectionView.CurrentItem(在您的情况下返回此视图CollectionViewSource.GetDefaultView(Schemas))。现在,当您从集合中删除一个项目Schemas(该项目也恰好是CurrentItem相应集合视图的项目)时,该视图默认将其更新CurrentItem为下一项(如果删除的项目是最后一项,则更新为前一项,或者null如果删除的项目则更新为前一项)该项目是该系列中唯一的项目)。
问题的第二部分是,当ListBox.SelectedItem更改导致视图模型属性更新时,您的RaisePropertyChangedEvent(nameof(ActiveSchema))处理将在更新过程完成后进行,特别是在从设置器返回控件之后ActiveSchema。您可以观察到 getter 并没有立即被命中,而是只有在 setter 完成之后才被命中。重要的是,CurrentItem视图的Schemas也不会立即更新以反映新选择的项目。另一方面,当您设置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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
在课堂里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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
但请注意,这需要引用程序集PresentationFramework。如果您不希望视图模型程序集中出现这种依赖关系,则可以创建一个由视图订阅的事件,并且视图将运行所需的代码(该代码已经依赖于程序集PresentationFramework)。此方法通常称为交互请求模式(请参阅MSDN上的Prism 5.0指南中的用户交互模式部分)。
#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;
});
}
}
Run Code Online (Sandbox Code Playgroud)
这需要引用程序集WindowsBase,这可以通过利用解决方案 #3 中描述的方法在视图模型程序集中再次避免。
就我个人而言,我会选择解决方案 #1 或 #2,因为它可以让你的PowerManager类保持干净,而 #3 和 #4 似乎容易出现意外行为。
| 归档时间: |
|
| 查看次数: |
713 次 |
| 最近记录: |