Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/285.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用包含复选框的WPF树视图时防止无限循环_C#_Wpf_Mvvm_Treeview_Inotifypropertychanged - Fatal编程技术网

C# 使用包含复选框的WPF树视图时防止无限循环

C# 使用包含复选框的WPF树视图时防止无限循环,c#,wpf,mvvm,treeview,inotifypropertychanged,C#,Wpf,Mvvm,Treeview,Inotifypropertychanged,这是我的第一个问题,如果格式不完美,我表示歉意 我是WPF和MVVM的新手,我遇到了一个似乎无法解决的问题 我有一个树视图,它显示一个MenuItem层次结构,每个MenuItem都有一个复选框,用于父节点和子节点。当前解决方案允许用户单击父节点,并根据需要选中/取消选中所有子项 我现在需要实现与此相反的方法,如果用户单击其中一个子节点,则应选择父节点(如果尚未选择) 我目前的问题是,以编程方式检查父节点会触发父节点的INotifiedPropertyChanged事件,该事件会重新检查我的子节

这是我的第一个问题,如果格式不完美,我表示歉意

我是WPF和MVVM的新手,我遇到了一个似乎无法解决的问题

我有一个树视图,它显示一个MenuItem层次结构,每个MenuItem都有一个复选框,用于父节点和子节点。当前解决方案允许用户单击父节点,并根据需要选中/取消选中所有子项

我现在需要实现与此相反的方法,如果用户单击其中一个子节点,则应选择父节点(如果尚未选择)

我目前的问题是,以编程方式检查父节点会触发父节点的INotifiedPropertyChanged事件,该事件会重新检查我的子节点

如何防止这种情况发生

这是我的菜单项代码:

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;
                //}
            }
        }
    }
公共类菜单项:INotifyPropertyChanged
{
字符串\u名称;
列表_子项=新列表();
布尔被检查;
MenuItem(父母);
公共列表子项
{
获取{return\u subItems;}
设置
{
_分项=价值;
RaiseProperty变更(“子项”);
}
}
公共字符串名
{
获取{return\u name;}
设置
{
_名称=值;
RaiseProperty变更(“名称”);
}
}
公共场所被检查
{
获取{return\u已检查;}
设置
{
_isChecked=值;
RaisePropertyChanged(“已检查”);
}
}
公共菜单项父项
{
获取{return\u parent;}
设置
{
_父=值;
RaiseProperty变更(“母公司”);
}
}
公共事件属性更改事件处理程序属性更改;
私有void RaisePropertyChanged(字符串propertyName)
{
if(PropertyChanged!=null)
PropertyChanged(这是新的PropertyChangedEventArgs(propertyName));
如果(propertyName==“IsChecked”)
{
如果(父项==null)
{
foreach(子项中的MenuItem子项)
Child.IsChecked=this.IsChecked;
}
//如果(父项!=null)
//{
//Parent.IsChecked=IsChecked?true:Parent.IsChecked;
//}
}
}
}
上面的注释代码就是我遇到错误的地方


如果您能提供任何指导,我们将不胜感激。

如果您检查了其中一个孩子,我想您需要另一个财产来存放。有点像是儿童检查


在UI中,您可以通过多重绑定将这两个属性(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");
            }
        }

将代码移动到setter,而不是在事件中处理它,对我来说很有效

根据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);
       }
    }

你可以采取几种不同的方法

1计算父对象的已选中属性

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");
}
这将通过家长监听孩子的PropertyChanged事件来实现,然后如果其中任何一个为true,则会检查家长返回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
方法,那么只有在通过setter直接设置父级时,才会触发级联

注意:在代码中,您不是在处理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");
}

此外,在进行任何更改之前,通过检查当前值是否已更改,可以改进上述所有功能

只有当值与当前值实际不同时,才可以尝试引发事件。值应始终不同。我基本上只需要让PropertyChanged事件在没有从方法调用的情况下启动。这不是答案,而是对您的方法的初步评论。不要在RaisePropertyChanged中实现此逻辑!IMHO在相应的检查设置程序中实现此请求更合适。。。当然,具体的答案应该是这样的……你得到了什么错误?
列表子项
应该是
可观察的集合子项
,使用私有设置。当前,我的菜单项模型和视图模型仍然包含在一个类中,这就是为什么选中的子项不需要私有。下一步是将其拆分。我认为这段代码不能像预期的那样工作。。。如果设置父节点的_isChecked字段,则不会激发NotifyPropertyChanged,因此如果选中parentnode,则不会显示UI。或者我认为它是错误的?测试它,它会按预期工作。在我对MVVM的有限理解中,由于在最后一行中调用了RaisePropertyChanged(“isChecked”),因此在设置_isChecked之后,UI将更新。对于子级,而不是父级。。但是如果它对你有用,我很高兴。。。我只是想知道,代码的最后一行调用父事件,子事件在foreach中单独调用。谢谢你的帮助。嗨,迈克,谢谢你的回答。只是一个简单的问题。事件的处理与引发。2与删除处理程序并在属性更改后再次添加处理程序所获得的结果之间有什么区别?当您引发事件y时,对话与倾听之间有什么区别