2017-03-09 105 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