从ListBox中删除所选项目时删除选择

Pet*_*oll 7 wpf

我有一个ListBox是有它ItemsSource绑定到(正确)实现了一个自定义类INotifyCollectionChangedSelectedItem绑定到一个ViewModel的字段.

问题是,当我SelectedItemItemsSource集合中删除当前时,它会立即将选择更改为相邻项.如果它只是删除了选择,我非常希望.

这对我来说是个问题的原因如下.的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运行而更新消息时,它只是用相邻的消息更新它.

任何帮助是极大的赞赏.


实际代码,如果有人想深入挖掘:

  • 列表框
  • 视图模型
  • 模型的方法
  • 选择相邻项目时的CallstackSelectedItem而不是一个用户选择的
    • 第46行:SelectedItem用户选择的方法输入方法作为应该激活的方法
    • 第45行:旧的SelectedItem停止活动 - >从集合中删除(44-41)
    • 第32行:MoveCurrencyOffDeletedElement移动SelectedItem
    • 第5行:SelectedItem变为邻居

Grx*_*x70 4

诊断

问题的关键在于您设置IsSynchronizedWithCurrentItem="True"ListBox. 它的作用是保持ListBox.SelectedItemListBox.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 似乎容易出现意外行为。