【问题标题】:Chart bind to non-primitive type图表绑定到非原始类型
【发布时间】:2017-10-17 15:41:16
【问题描述】:

我有一个 C#.NET Windows 窗体应用程序,它使用带有多个线系列的图表。我有一个存储读数的物理设备,当我下载读数时,我希望图表能够填写详细信息。

我在软件中为可量化的值(例如温度)创建了类型,这些值可能会根据位置使用不同的单位,允许我对显示单位进行用户设置,并且只需在后台处理所有内容而无需关心,并且当它显示给用户时,它的单位是正确的。

C#.NET Charting.Series 类非常严格,当尝试使用绑定数组进行绘制时,它会抛出一个带有以下语句的 ArgumentExeption:“系列数据点不支持 Units.Temperature 类型的值,只有这些类型的值可以可使用:Double、Decimal、Single、int、long、uint、ulong、String、DateTime、short、ushort"。

Temperature 类型有一个名为 Value 的属性,它是一个 double,并且可以隐式转换为 double,但这似乎无关紧要。我希望有一种方法可以绑定它,以便在它从结果数组中请求值时进行转换,以便它可以正确绘制图形,任何人都知道这是怎么回事 可以做吗?

我希望避免使用 LogItem[] 和次要的 double[] 来进行转换。

private LogItem[] results;

private async void GraphPage_Load(object sender, EventArgs e) {
    var count = await device.LogCount.Read(CancelToken);
    results = new LogItem[count];
    graph.DataSource = results;
    graph.Series[0].XValueMembers = nameof(LogItem.DateTime); //Type is System.DateTime
    graph.Series[0].YValueMembers = nameof(LogItem.Temperature); //Type my library, Units.Temperature

    var prog = new Progress<double>();
    prog.ProgressChanged += (sender2, e2) => {
        BeginInvoke(new Action(() => {
            progBar.Value = results.Count(log => !Equals(log, LogItem.Empty));
            CheckEmptyPoints(); //Sets or clears Points[x].IsEmpty;
            graph.Invalidate(); //Force graph to draw new results, maybe a better way of doing this?
        }));
    };
    await device.DownloadLogs(CancelToken, results, prog); //Takes output buffer and IProgress to report more data has been downloaded.
}

【问题讨论】:

    标签: c# .net winforms data-binding charts


    【解决方案1】:

    您可以使用 linq 将结果调整为图表友好的形状。例如:

    var list = results.Select(x => new { X = x.DateTime, Y = x.Temprature.Value }).ToList();
    this.chart1.DataSource = list;
    this.chart1.Series[0].XValueMember = "X";
    this.chart1.Series[0].YValueMembers = "Y";
    

    XY 只是占位符,您可以使用 DateTimeTempratue 或任何合适的标签。

    【讨论】:

    • 我确实提到我希望避免需要辅助缓冲区,并且使用 Linq 可能会更糟,因为它会不断重新分配内存。我可以使用 Points.Insert() 修改机制以使用系列的内部列表并删除数据绑定。但很高兴知道如何做到这一点。
    • 对于这样一个简单的查询,通常内存不会成为问题。但您也可以使用循环和chart1.Series[0].Points.AddXY(x, y); 向图表添加点。
    • 问题是我正在下载潜在的数千条记录,这应该将对象放入 .NET 中的 LOH 中......虽然实际上它总是保持相同的大小,所以已知的 LOH 问题不应该发生。我需要进一步了解 Linq 的工作原理,因为我可能已经根据您的建议想到了解决方案。
    • 它创建了一些新对象,正如我们在Select 语句中所要求的那样。而AddXY 方法只使用您现有的值。如果你真的需要,你可以安排一次内存测试。 (当您的对象是普通对象时,通常不需要此类测试。)
    • 我有一个关于 Linq 可能如何工作的理论,但我不知道如何确认它,我在网上找不到太多。因为所有的 Linq 扩展语句都在 IEnumerable 上工作并返回 IEnumerable,所以我想知道它们是否是通过调用 GetEnumerator() 然后创建一个新的 IEnumerator 来实现的。然后每个调用都可以传递给原始枚举器,然后是执行操作的 Linq 函数。如果这就是它在幕后的工作方式,那么这是一个非常好的转换机制。
    【解决方案2】:

    非常感谢Reza Aghaei 的帮助。

    Linq 语句确实提供了转换函数的答案,但只是稍作调整,因此它不会创建新数组并在每次刷新时重新绑定。

    解决方法是使用Charting.DataPointsCollection.DataBind(IEnumerable dataSource, string xField, string yField, string otherFields)绑定数据。但是要使用 Linq 语句创建一个 IEnumerable 对象以用于数据绑定,并且在每次调用 MoveNext 时,此 IEnumerable 将从数组中获取数据并转换为图表。

    // IEnumerable<LogItem>.Select(...) creates an IEnumerable which references the array,
    //   and performs the selection and conversion on each call to MoveNext().
    
    graph.Series[0].Points.DataBind(
        results.Select(l => new { X = l.DateTime, Y = l.OilCondition.Value }),
        "X",
        "Y",
        string.Empty);
    

    编辑:上面确实可以正常工作,但是根据 Points.DataBind(,,,) 上的文档,评估只执行一次,因此每次都需要调用 DataBind(,,,) 方法。

    【讨论】:

    • 在标记答案之前确认一切正常。
    猜你喜欢
    • 2011-02-05
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 2020-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多