首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >从ListBox中删除选定项时删除所选内容

从ListBox中删除选定项时删除所选内容
EN

Stack Overflow用户
提问于 2017-03-09 18:42:07
回答 1查看 1.6K关注 0票数 7

我有一个ListBox,它的ItemsSource绑定到(正确)实现INotifyCollectionChanged的自定义类,而SelectedItem绑定到ViewModel中的字段。

问题是,当我从SelectedItem集合中删除当前的ItemsSource时,它会立即将所选内容更改为相邻的项。我非常希望它只是删除了选择。

对我来说这是个问题的原因是这样的。ItemsSource类包含来自其他集合的元素,这些元素要么满足某些(在运行时常量期间)谓词,要么满足ActiveActive与is SelectedItem是“同步”的(原因是这样的)。因此,只有当项目被选中时,它才有可能在ListBox中被允许,这意味着当用户选择其他项目时,它可能会消失。

在更改SelectedItem时调用我的函数(在“模型”中很深):

代码语言:javascript
运行
复制
//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; }

问题是,由于应该由(#1)触发的ListBox更改而导致的SelectedItem更新由于某种原因而被推迟(用于更新ListBox的消息可能在WPF消息循环中结束,并等待当前计算完成)。

另一方面,从oldActiveSchema中删除ItemsSource是即时的,并且会立即触发SelectedItem更改为旧项旁边的SelectedItem(当您删除所选的项时,会选择相邻的项)。而且,由于SelectedItem的更改触发了我的函数,将CurrentSchema设置为错误(相邻)项,因此它重写用户选择的CurrentSchema (#1),并且在运行要更新ListBox的消息时,只需使用相邻的消息更新。

任何帮助都是非常感谢的。

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

  • ListBox
  • ViewModel
  • 模型法
  • 卡斯塔克当相邻项被选择为SelectedItem而不是一个用户选择时
    • 第46行:用户选择的SelectedItem输入的方法应该是活动的
    • 第45行:旧的SelectedItem不再是活动的->被从集合中删除(44-41)
    • 第32行:MoveCurrencyOffDeletedElement移动SelectedItem
    • 第5行:将SelectedItem更改为相邻的

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-03-19 11:49:11

诊断

解决问题的关键是在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不是立即命中,而是在设置器完成之后才被击中。重要的是,也没有立即更新Schemas视图的Schemas以反映新选择的项。另一方面,当您在以前选择的项上设置IsActive = false时,它会立即从Schemas集合中“删除”该项,这反过来会导致更新集合视图的CurrentItem,并且链将立即继续更新ListBox.SelectedItem。您可以观察到,此时ActiveSchema设置器将再次被击中。因此,您的ActiveSchema将再次被更改(到前面选择的项旁边的项),甚至在您完成以前的更改之前(对用户选择的项)。

解决方案

有几种方法可以解决这一问题:

#1

在您的IsSynchronizedWithCurrentItem="False"上设置ListBox (或者保持不动)。这将使你的问题不顾一切地消失。但是,如果由于某种原因需要它,则使用任何其他解决方案。

#2

通过使用保护标志防止重入尝试设置ActiveSchema

代码语言:javascript
运行
复制
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类中:

代码语言:javascript
运行
复制
//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类中:

代码语言:javascript
运行
复制
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程序集)。这种方法通常被称为交互请求模式(请参阅Prism 5.0指南中关于MSDN的用户交互模式部分)。

#4

将以前选择的项的“删除”推迟到绑定更新完成。这可以通过使用Dispatcher对要执行的代码排队来实现。

代码语言:javascript
运行
复制
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似乎容易出现意外行为。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42703092

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档