[英]Preventing infinite loops when using INotifyPropertyChanged with a WPF treeview containing checkboxes
這是我的第一個問題,如果格式不正確,我深表歉意。
我是WPF和MVVM的新手,我遇到了一個似乎無法解決的問題。
我有一個樹視圖,它顯示一個MenuItem層次結構,其中每個MenuItem都有一個復選框,用於父級和子級節點。 當前的解決方案允許用戶單擊父節點,並根據需要選中/取消選中所有子項。
我現在需要實現相反的操作,如果用戶單擊子節點之一,則應該選擇父節點(如果尚未選擇)。
我目前遇到的問題是,以編程方式檢查父節點會觸發父節點的INotifiedPropertyChanged事件,從而重新檢查我的子節點。
如何防止這種情況發生?
這是我的MenuItem代碼:
public class MenuItem : INotifyPropertyChanged
{
string _name;
List<MenuItem> _subItems = new List<MenuItem>();
bool _isChecked;
MenuItem _parent;
public List<MenuItem> SubItems
{
get { return _subItems; }
set
{
_subItems = value;
RaisePropertyChanged("SubItems");
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
RaisePropertyChanged("IsChecked");
}
}
public MenuItem Parent
{
get { return _parent; }
set
{
_parent = value;
RaisePropertyChanged("Parent");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
if (propertyName == "IsChecked")
{
if (Parent == null)
{
foreach (MenuItem Child in _subItems)
Child.IsChecked = this.IsChecked;
}
//if (Parent != null)
//{
// Parent.IsChecked = IsChecked ? true :Parent.IsChecked;
//}
}
}
}
上面的注釋代碼是我遇到錯誤的地方。
任何指導將不勝感激。
根據OP已經編寫的答案,再詳細一點的答案
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
if (_parent == null)
{
foreach (MenuItem Child in _subItems)
{
Child._isChecked = this._isChecked;
Child.RaisePropertyChanged("IsChecked");
}
}
if (_parent != null)
{
_parent.NotifyChecked(_isChecked);
}
RaisePropertyChanged("IsChecked");
}
}
public void NotifyChecked(bool childChecked)
{
_isChecked = childChecked;
RaisePropertyChanged("IsChecked");
if (_parent != null)
{
_parent.NotifyChecked(_isChecked);
}
}
我認為,如果選中其中一個孩子,則需要其他屬性來存儲。 類似於IsChildChecked。
在UI中,您可以使用MultiBinding將這兩個屬性(IsChecked和IsChildChecked)綁定到節點的IsChecked。 使用轉換器進行設置。
機器學習的評論使我得到了答案:
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
if (_parent == null)
{
foreach (MenuItem Child in _subItems)
{
Child._isChecked = this._isChecked;
Child.RaisePropertyChanged("IsChecked");
}
}
if (_parent != null)
{
_parent._isChecked = _isChecked ? true : _parent._isChecked;
_parent.RaisePropertyChanged("IsChecked");
}
RaisePropertyChanged("IsChecked");
}
}
將代碼移到設置器上,而不是在發生事件時對其進行處理對我來說很有效。
您可以采取幾種不同的方法,
1計算父母的被檢查財產
這將通過父級偵聽孩子的PropertyChanged事件,然后如果它們中的任何一個為true來為父級IsChecked返回true,則將起作用
private bool isChecked;
public bool IsChecked
{
get{ return isChecked || Children.Any(c=>IsChecked);}
set
{
isChecked = value;
RaisePropertyChanged("IsChecked");
foreach(var child in Children)child.IsChecked
}
}
public void Child_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
if(e.PropertyName == "IsChecked")
RaisePropertyChanged("IsChecked");
}
這種方法的好處是可以獨立保持父母的點擊狀態
2翻轉1回合並計算孩子的IsChecked屬性
private bool isChecked;
public bool IsChecked
{
get{ return isChecked || Parent.IsChecked;}
set
{
isChecked = value;
RaisePropertyChanged("IsChecked");
}
}
public void Parent_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
if(e.PropertyName == "IsChecked")
RaisePropertyChanged("IsChecked");
}
3創建第二條路由以在不觸發級聯的情況下更改狀態
private bool isChecked;
public bool IsChecked
{
get{ return isChecked;}
set
{
SetIsChecked( value);
foreach(var child in Children)Parent.SetIsChecked(isChecked)
}
}
public void SetIsChecked(bool value)
{
isChecked = value;
RaisePropertyChanged("IsChecked");
}
這樣,只要子級直接調用SetIsChecked
方法,則級聯僅在通過設置器直接設置父級時觸發
注意:在您的代碼中,您不處理PropertyChanged事件,而僅引發它
處理看起來像這樣
public MenuItem Parent
{
get { return _parent; }
set
{
//remove old handler
// this stops listening to the old parent if there is one
if(_parent != null)
_parent.PropertyChange-=Parent_PropertyChanged;
//notice that the value of _parent changes here so _parent above is not the same as _parent used below
_parent = value;
//add new handler
// this starts listening to the new parent if there is one
if(_parent != null)
_parent.PropertyChange+=Parent_PropertyChanged;
RaisePropertyChanged("Parent");
}
}
//handler
public void Parent_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
if(e.PropertyName == "IsChecked")
RaisePropertyChanged("IsChecked");
}
通過在進行任何更改之前檢查當前值是否已更改,上述所有內容都可以得到改善
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.