2017-03-09 106 views
6

删除我有一个ListBox有其ItemsSource绑定到(正确)实现了一个INotifyCollectionChanged并在视图模型绑定到一个领域SelectedItem自定义类。删除选择时选择的项目会从列表框中

问题是,当我从ItemsSource集合中删除当前SelectedItem时,它立即将选择更改为相邻项目。我非常希望如果它只是删除选择。

对我来说这是一个问题的原因如下。 ItemsSource类包含来自某些其他集合的元素,它们可以满足一些(在运行时常量期间)Predicate或Active。正在ActiveSelectedItem“同步”(这是有原因的)。因此,只有在选择了某个项目时,才有可能在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)推迟(消息更新ListBox可能最终在一个WPF消息循环,并在那里等待,直到当前计算完成)。

oldActiveSchemaItemsSource去除,而另一方面,是即时的,也即时启动SelectedItem一个一个的旁边旧的变化(当您删除选定的项目,一个邻居被选中代替) 。并且由于SelectedItem的更改触发了我的功能,它将CurrentSchema设置为错误(相邻)项目,它会重写用户选择的CurrentSchema(#1),并且在更新ListBox由于PropertyChanged得到运行时,它只会更新它相邻的一个。

任何帮助,非常感谢。


实际的代码,如果有人想深入了解:

  • ListBox
  • ViewModel
  • The model's method
  • Callstack当相邻项目被选为SelectedItem而不是一个用户选择
    • 线46:由用户选择的SelectedItem进入方法为一体,是应该得到活性
    • 线45:旧SelectedItem停止活跃 - >被从集合中移除(44-41)
    • 线32: MoveCurrencyOffDeletedElement移动SelectedItem
    • 线5:SelectedItem得到改变到相邻的一个
+0

删除选择应该只需要将ViewModel中的SelectedItem属性设置为null。由于问题是模式更改,只需将您的selecteditem存储在本地变量中,将selecteditem设置为null,然后*然后*从集合中删除所选项目。 –

+0

放在[#WPF](https://chat.stackoverflow.com/rooms/18165/wpf)中,如果我没有在聊天中激活,请给我一个ping。我查看了您的代码,但无法立即找出如何触发该问题。还有其他很多有帮助的居民,如果我不活跃,他们可以伸出援助之手。 – Maverik

+1

布兰登的选择改变的想法也是我的想法。另外,如果您将Selector.IsSelected绑定到IsActive ..您的选择可以自动跟随IsActive标志,而不必处理当前项目 – Maverik

回答

1

诊断

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

+0

谢谢,我有类似于#3和#4的想法,但这两种解决方案在我看来似乎有点“脏”。另外,我真的不想用UI相关的黑客来污染'PowerManager'。 #2是一个好主意。尽管它很明显,但我没有想到那个。不过,我会和#1一起去。我有点误解了IsSynchronizedWithCurrentItem ='做了什么,并认为这是我的用例需要的。事实并非如此。 – Petrroll