如何根据每个元素的筛选属性从ObservableCollection中汇总每个元素的属性?

huangapple go评论73阅读模式
英文:

How to sum a property from each element in an ObservableCollection Based on a filter property on each element?

问题

I'm working on a system that has a list of Records displayed in a list.

Each item has the following properties:

  • Name
  • Id
  • Size
  • A Boolean indicating if this item should be included or not - ShouldBeIncluded

A user can check a checkbox to change the boolean of the item from false to true. etc.

Based on a user changing various item's checkboxes I need to keep a "TotalSize" property up to date so that it reflects the sum of all item's size property in the list where the item's "ShouldBeIncluded" property is true.

I'm trying to do this within a UI based app running ReactiveUI/Dynamic Data.

I use an MVVM approach in everything else I do. So I'm not looking for tying this code directly to my view layer.

For the purposes of this question I've minimized this all down to a simple reproduction though.

I think I need to Observe property changes on the ObservableCollection but I cannot figure out how to then bind this back to the ReactiveUI property.

  • I've tried various DynamicData methods, including the ones I've commented out.
  • I've tried ObservableAsAProperty helpers
  • I've tried doing this manually

But I want to understand the most Idiomatic way to do this, but everything I'm trying to do seems to overcomplicate it.

Here's some boilerplate code that shows where I'm at:

using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace ConsoleApp1;
/** Simplified for this question, but let's say I'm building a UI with this layout
 * | Name    | Should be Included| Size |
 * | Record 1| [ ]               |5     |
 * | Record 2| [ ]               |5     |
 * ... etc.
 * 
 * Total: the sum of items' size where their "Should be included" checkbox is true
 */

// For some boilerplate code here we go

// Our "Record" which has the properties from the table above
public class Record : ReactiveObject
{
    public string Id { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;

    [Reactive]
    public int Size { get; set; } = 0;

    [Reactive]
    public bool ShouldBeIncluded { get; set; } = false;

    public Record(string id, name, int size)
    {
        Id = id;
        Name = name;
        Size = size;
    }
}

// A "View Model" that can be used to display them
public class Test
{
    public ObservableCollection<Record> records;

    // How do I keep Total up to date with the correct value, in this case 15?
    public int Total { get; set; }

    public Test()
    {
        records = new ObservableCollection<Record>();

        //records.ToObservableChangeSet(x => x.Id).Filter(x=> x.ShouldBeIncluded). What's next? Is this right?;
    }
}
public class Program
{
    public static void Main(string[] args)
    {
        // Create a new test object
        var t = new Test();

        // Add 10 records to it, each with a size of 5
        for (var i = 0; i < 10; i++)
        {
            t.records.Add(new Record(i.ToString(), $"Record {i}", 5));
        }

        // Mark 3 records as needing to be "Included"
        t.records[0].ShouldBeIncluded = true;
        t.records[3].ShouldBeIncluded = true;
        t.records[5].ShouldBeIncluded = true;

        Debug.Assert(t.Total == 15);
    }
}

You can also find a minimal reproduction Avalonia app on GH: https://github.com/ProbablePrime/Repro-ReactiveUI-MVVM-Sum-Filter

英文:

I'm working on a system that has a list of Records displayed in a list.

Each item has the following properties:

  • Name
  • Id
  • Size
  • A Boolean indicating if this item should be included or not - ShouldBeIncluded

A user can check a checkbox to change the boolean of the item from false to true. etc.

Based on a user changing various item's checkboxes I need to keep a "TotalSize" property up to date so that it reflects the sum of all item's size property in the list where the item's "ShouldBeIncluded" property is true.

I'm trying to do this within a UI based app running ReactiveUI/Dynamic Data.

I use an MVVM approach in everything else I do. So I'm not looking for tieing this code directly to my view layer.

For the purposes of this question I've minimised this all down to a simple reproduction though.

I think I need to Observe property changes on the ObservableCollection but I cannot figure out how to then bind this back to the ReactiveUI property.

  • I've tried various DynamicData methods, including the ones I've commented out.
  • I've tried ObservableAsAProperty helpers
  • I've tried doing this manually

But I want to understand the most Idiomatic way to do this, but everything I'm trying to do seems to overcomplicate it.

Here's some boilerplate code that shows where I'm at:

using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace ConsoleApp1;
/** Simplified for this question, but let's say I'm building a UI with this layout
 * | Name    | Should be Included| Size |
 * | Record 1| [ ]               |5     |
 * | Record 2| [ ]               |5     |
 * ... etc.
 * 
 * Total: the sum of items' size where their "Should be included" checkbox is true
 */


// For some boilerplate code here we go

// Our "Record" which has the properties from the table above
public class Record : ReactiveObject
{
    public string Id { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;

    [Reactive]
    public int Size { get; set; } = 0;

    [Reactive]
    public bool ShouldBeIncluded { get; set; } = false;

    public Record(string id, string name, int size)
    {
        Id = id;
        Name = name;
        Size = size;
    }
}

// A "View Model" that can be used to display them
public class Test
{
    public ObservableCollection<Record> records;

    // How do I keep Total up to date with the correct value, in this case 15?
    public int Total { get; set; }

    public Test()
    {
        records = new ObservableCollection<Record>();

        //records.ToObservableChangeSet(x => x.Id).Filter(x=> x.ShouldBeIncluded). What's next? Is this right?;
    }

}
public class Program
{
    public static void Main(string[] args)
    {
        // Create a new test object
        var t = new Test();

        // Add 10 records to it, each with a size of 5
        for (var i = 0; i < 10; i++)
        {
            t.records.Add(new Record(i.ToString(), $"Record {i}", 5));
        }

        // Mark 3 records as needing to be "Included"
        t.records[0].ShouldBeIncluded = true;
        t.records[3].ShouldBeIncluded = true;
        t.records[5].ShouldBeIncluded = true;

        Debug.Assert(t.Total == 15);
    }
}

You can also find a minimal reproduction Avalonia app on GH: https://github.com/ProbablePrime/Repro-ReactiveUI-MVVM-Sum-Filter

答案1

得分: 1

自UI界面执行此操作时,您可以请求计算 TotalSize 并调用 NotifyPropertyChanged(TotalSize)

如果您想通过更改布尔值来完成此操作,您的模型应在属性的setter中实现 NotifyProperyChanged

不管怎样,根据您的Debug.Assert,您的代码没有计算 TotalSize。我会采纳 @Pieterjan 的建议来计算它:public int Total => records.Where(r => r.ShouldBeIncluded).Select(r => r.Size).Sum();

英文:

Since you are doing this from the UI, you can request de calculation of TotalSize and NotifyPropertyChanged(TotalSize).

If you want to do it by changes on the boolean, your model should implement NotifyProperyChanged in the setter of the Property.

Anyhow, your code is not calculating TotalSize (As noted by your Debug.Assert). I would use @Pieterjan suggestion for calculating it: public int Total => records.Where(r => r.ShouldBeIncluded).Select(r => r.Size).Sum();

答案2

得分: 1

我在Avalonia讨论板上问了同样的问题。

有位用户名为alxgenov的用户

提供了一个基于DynamicData的完美解决方案

private ObservableAsPropertyHelper<int> total;
public int Total => total.Value;

// 在构造函数中
total = Records
    .ToObservableChangeSet(x => x.Id) 
    .AutoRefresh(x => x.ShouldBeIncluded)
    .Filter(x => x.ShouldBeIncluded)
    .Sum(x => x.Size)
    .ToProperty(this, x => x.Total);

这将保持Total更新为Avalonia可以绑定到的ObservableProperty。

感谢大家的帮助!

您可以在示例应用中看到此实现:https://github.com/ProbablePrime/Repro-ReactiveUI-MVVM-Sum-Filter/commit/55d1042e52a5da6f773e0dc37f099226e27b9829

英文:

I asked this same question over on the Avalonia discussions board.

A user named: alxgenov

Provided a DynamicData based answer that works perfectly!

private ObservableAsPropertyHelper&lt;int&gt; total;
public int Total =&gt; total.Value;

// In constructor
total = Records
    .ToObservableChangeSet(x =&gt; x.Id) 
    .AutoRefresh(x =&gt; x.ShouldBeIncluded)
    .Filter(x =&gt; x.ShouldBeIncluded)
    .Sum(x =&gt; x.Size)
    .ToProperty(this, x =&gt; x.Total);

This will keep Total up to date as an ObservableProperty that Avalonia can bind to.

Thank you all for your help!

You can see this implemented in the example app here: https://github.com/ProbablePrime/Repro-ReactiveUI-MVVM-Sum-Filter/commit/55d1042e52a5da6f773e0dc37f099226e27b9829

huangapple
  • 本文由 发表于 2023年4月11日 14:19:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/75982903.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定