診斷
您問題的關鍵在於您在ListBox
上設置了IsSynchronizedWithCurrentItem="True"
。它的作用是保持ListBox.SelectedItem
和ListBox.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 Patterns)Prism 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似乎很容易出現意想不到的行爲。
刪除選擇應該只需要將ViewModel中的SelectedItem屬性設置爲null。由於問題是模式更改,只需將您的selecteditem存儲在本地變量中,將selecteditem設置爲null,然後*然後*從集合中刪除所選項目。 –
放在[#WPF](https://chat.stackoverflow.com/rooms/18165/wpf)中,如果我沒有在聊天中激活,請給我一個ping。我查看了您的代碼,但無法立即找出如何觸發該問題。還有其他很多有幫助的居民,如果我不活躍,他們可以伸出援助之手。 – Maverik
布蘭登的選擇改變的想法也是我的想法。另外,如果您將Selector.IsSelected綁定到IsActive ..您的選擇可以自動跟隨IsActive標誌,而不必處理當前項目 – Maverik