【问题标题】:INPCs not firing for multiple properties from setter of "driving" ObservableCollectionINPC 不会从“驱动”ObservableCollection 的设置器中触发多个属性
【发布时间】:2013-08-09 19:14:26
【问题描述】:

我的 Senario: 我有两个 ListBox 绑定到两个不同的 ObervableCollections - 一个形状集合和一个颜色集合,用户可以从中选择以查找具有匹配属性标准的部件号。 (我的应用中存在更多属性集合,但为了清楚起见,我将它们省略了。)

在从两个包含属性值的列表框中进行选择后,我会在名为ResultingPNsIntersect 的集合中收集拥有所选属性属性的结果部件号。 (结果部件号的集合显示在第三个 ListBox 中。)

应该发生什么:在两个列表框中的任何一个中进行选择后,拥有 Selected'Attribute' 的结果部件号的交集应该更新,以便只有相关零件编号保持不变。如果选择了一个形状,则包含 ColorsCollection 的列表框必须更新,以便只有与具有 SelectedShape 的 PartNumber 相关的颜色属性(在 ColorsCollection 中)显示在第二个列表框中。

我的问题: 选择形状后,ResultingPNsIntersect ObservableCollection 会更新,但不会触发 ColorsCollection 的 PropertyChanged 通知,因此第二个列表框永远不会更新以向用户提供更新的颜色属性从中选择。

我之前在其他应用程序中也这样做过,没有任何问题。我认为没有必要订阅CollectionChanged,因为我没有在 ResultingPNsIntersect 中编辑属性值——我正在用新值替换集合。请帮我看看我的代码在哪里失败以及原因,以便我更好地了解 INPC 触发所需的条件。

xaml 绑定:

<ListBox x:Name="SelectFromAvailableShapesLB" DockPanel.Dock="Top"  
         ItemsSource="{Binding AvailableShapesCollection}" 
         DisplayMemberPath="AttVal" 
         SelectedItem="{Binding SelectedShape, Mode=TwoWay}"/>


<ListBox x:Name="SelectFromAvailableColorsLB" DockPanel.Dock="Top"  
         ItemsSource="{Binding AvailableColorsCollection}" 
         DisplayMemberPath="AttVal" 
         SelectedItem="{Binding SelectedColor, Mode=TwoWay}"/>


<ListBox x:Name="PnsResultingFromAttributeSelectionsLB" DockPanel.Dock="Top"  
         ItemsSource="{Binding ResultingPNsIntersect}" 
         DisplayMemberPath="PartNum" 
         SelectedItem="{Binding SelectedPartNum, Mode=TwoWay}"/>

我的 ViewModel:

    public ObservableCollection<AttributeValuesLibrary> AvailableShapesCollection
    {
        get
        {
            if (_resultingPNsIntersect != null)
            {
                foreach (PartNumber shape in _resultingPNsIntersect.Where(x => x.ShapeID != null))
                {
                    if (!_availableShapesCollection.Contains(shape.AttributeValuesLibrary_Shape))
                    {
                        this._availableShapesCollection.Add(shape.AttributeValuesLibrary_Shape);
                    }
                }

            }
            return _availableShapesCollection;
        }
        set
        {
            if (_availableShapesCollection != value)
            {
                this._availableShapesCollection = value;
                RaisePropertyChanged("AvailableShapesCollection");
            }
        }
    }


    public ObservableCollection<AttributeValuesLibrary> AvailableColorsCollection
    {
        get
        {
            if (_resultingPNsIntersect != null)
            {
                foreach (PartNumber color in _resultingPNsIntersect.Where(x => x.ColorID != null))
                {
                    if (!_availableColorsCollection.Contains(color.AttributeValuesLibrary_Color))
                    {
                        _availableColorsCollection.Add(color.AttributeValuesLibrary_Color);
                    }
                }

            }
            return _availableColorsCollection;
        }
        set
        {
            if (_availableColorsCollection != value)
            {
                _availableColorsCollection = value;
                RaisePropertyChanged("AvailableColorsCollection");
            }
        }
    }

    public AttributeValuesLibrary SelectedShape
    {
        get
        {
            return _selectedShape;
        }
        set
        {
            if (_selectedShape != value)
            {
                _selectedShape = value;
                RaisePropertyChanged("SelectedShape");
                RaisePropertyChanged("ResultingPNsIntersect");
            }

        }
    }


    public ObservableCollection<ConnectorPartNumber> ConnAttPNResults
    {
        get
        {
            // If a shape has been selected, we need to navigate to it's related PartNumbers and add those to the intersection
            // contained by ResultingPNsIntersection. 
            if (_selectedShape != null)
            {
                var shapeResults = _context.PartNumbers.Where(x => x.AttributeValuesLibrary_Shape.AttValID == _selectedShape.AttValID);

                if (_resultingPNsIntersect != null)
                {
                    var resultsFromPreviousSelection = _resultingPNsIntersect;
                    _resultingPNsIntersect = new ObservableCollection<PartNumber>(resultsFromPreviousSelection.Intersect(shapeResults));
                }
                else if (_resultingPNsIntersect == null)
                {
                    _resultingPNsIntersect = new ObservableCollection<PartNumber>(shapeResults);
                }
              }
            return _resultingPNsIntersect;
        }
        set
        {
            if (_resultingPNsIntersect != value)
            {
                this._resultingPNsIntersect = value;
                RaisePropertyChanged("ResultingPNsIntersect");
                RaisePropertyChanged("AvailableColorsCollection");  <--Not firing!!!!!

            }


        }
    }

提前致谢!

::UPDATE:: 如果我将RaisePropertyChanged("AvailableColorsCollection") 放在我的SelectedShape 的设置器中,我可以强制它工作。但是,当然,它在那里意义不大,因为 AvailableColorsCollection 依赖于 ResultingPNsIntersect 集合,该集合会根据属性列表框中的选择而变化。

【问题讨论】:

    标签: c# wpf mvvm observablecollection inotifypropertychanged


    【解决方案1】:

    我认为总的来说,您的方法会给您带来问题。与其在属性的 getter 中实现逻辑,不如考虑让属性变得更加“笨拙”。我会将这个逻辑从属性中移出到一个修改你可用选项的辅助方法中:

    private readonly ObservableCollection<AttributeValuesLibrary> _availableShapesCollection =
        new ObservableCollection<AttributeValuesLibrary>();
    private readonly ObservableCollection<AttributeValuesLibrary> _availableColorsCollection =
        new ObservableCollection<AttributeValuesLibrary>();
    
    public ObservableCollection<AttributeValuesLibrary> AvailableShapesCollection
    {
        get { return _availableShapesCollection; }
    }
    
    public ObservableCollection<AttributeValuesLibrary> AvailableColorsCollection
    {
        get { return _availableColorsCollection; }
    }
    
    public AttributeValuesLibrary SelectedShape
    {
        get { return _selectedShape; }
        set
        {
            if (_selectedShape != value)
            {
                _selectedShape = value;
                RaisePropertyChanged("SelectedShape");
                SelectedShapeChanged();
            }
        }
    }
    
    public ObservableCollection<ConnectorPartNumber> ConnAttPNResults
    {
        get { return _resultingPNsIntersect; }
        set
        {
            if (_resultingPNsIntersect != value)
            {
                this._resultingPNsIntersect = value;
                RaisePropertyChanged("ResultingPNsIntersect");
                UpdateAvailableOptions();
            }
        }
    }
    
    private void SelectedShapeChanged()
    {
        // If a shape has been selected, we need to navigate to it's related 
        // PartNumbers and add those to the intersection contained by ResultingPNsIntersection. 
        if (_selectedShape != null)
        {
            var shapeResults = _context.PartNumbers.Where(x => x.AttributeValuesLibrary_Shape.AttValID == _selectedShape.AttValID);
    
            if (_resultingPNsIntersect != null)
            {
                var resultsFromPreviousSelection = _resultingPNsIntersect;
                ConnAttPNResults = new ObservableCollection<PartNumber>(resultsFromPreviousSelection.Intersect(shapeResults));
            }
            else
            {
                ConnAttPNResults = new ObservableCollection<PartNumber>(shapeResults);
            }
        }
    }
    
    private void UpdateAvailableOptions()
    {
        if (_resultingPNsIntersect != null)
        {
            _availableColorsCollection.Clear();
            _availableShapesCollection.Clear();
    
            foreach (PartNumber color in _resultingPNsIntersect.Where(x => x.ColorID != null).Distinct())
            {
                _availableShapesCollection.Add(color.AttributeValuesLibrary_Color);
            }
    
            foreach (PartNumber shape in _resultingPNsIntersect.Where(x => x.ShapeID != null).Distinct())
            {
                shapes.Add(shape.AttributeValuesLibrary_Shape);
            }
        }
    }
    

    如果您希望有可设置的属性,UpdateAvailableOptions 可以创建新集合并将AvailableColorsCollectionAvailableShapesCollection 属性设置为新实例(但随后不需要ObservableCollections)。

    实际上,我什至会更进一步。由于您不希望任何人更改您的可用集合,我会让它们返回 ReadonlyObersvableCollection 实例,并使其返回类型为 IEnumerable&lt;T&gt;

    【讨论】:

    • 糟糕...好的,我更改了名称,以便在示例中更清晰。我的代码中的所有内容都是正确的,只是在我的问题中的类比中,我做了一个类型-o...我道歉
    • 好的,我已经更新了我的答案,因为我不认为你的方法真的是你想要使用的方法。
    • 实际上,可用集合必须更新。当用户在其中一个可用集合中进行选择时,它会更新与选择相关的 PartNumbers 集合,然后更新 OTHER Available 集合,以便现在只有交集中的属性可用。我需要在进行选择时重新计算 ObservableCollections 本身。我希望这很清楚。
    • 对。但是您想要更新 ViewModel 中的集合。例如,您不会希望用户向 AvailableColors 添加新颜色。这就是为什么我建议这些集合是只读的。就其他选择而言,您可以像SelectedShape 属性一样编写它们(调用您要重命名的SelectedShapeChanged)。请注意,当ConnAttPNResults 属性更改时,它会更新可用项目。
    • 对,我的逻辑也是如此。我不明白为什么我必须在 SelectedShape 的设置器中引发“AvailableColorsCollection”通知。当我把它放在 ResultingPNIntersect 的设置器中时,这不应该提高吗?这就是更改实际上取决于的内容——不是选择了什么属性,而是部件号的交集发生了更改的事实。谢谢你的帮助,@AbeHeiderbrecht
    猜你喜欢
    • 2016-04-30
    • 2011-10-30
    • 2013-04-09
    • 1970-01-01
    • 2021-11-08
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    相关资源
    最近更新 更多