【问题标题】:How to resolve "Value cannot be null" Exception with ChartTrackBallBehavior如何使用 ChartTrackBallBehavior 解决“值不能为空”异常
【发布时间】:2019-06-07 08:19:34
【问题描述】:

我一直致力于在 UWP 中构建一个程序,该程序利用 Telerik Charting 控件向用户显示传入数据。在我最新的测试中,在图表上动态接收和绘制未经请求的数据,我遇到了一个未处理的异常 (NullReferenceException) 正在发生。这似乎只发生在用户将鼠标悬停在图表上且图表显示轨迹球信息(Telerik 的 ChartTrackBallBehavior)时。如果用户的鼠标在其他地方或没有触发轨迹球信息显示在图表上,则永远不会出现此异常。

到目前为止,我已经设法将 NullException 跟踪到发生在 Telerik UWP UI 中 ChartTrackBallBehavior.cs 中的函数 GetIntersectionTemplate() 内。除此之外,我不知道我能做些什么来解决这个问题,以免它再次发生。

这是发生异常时的典型堆栈跟踪:

System.ArgumentNullException: Value cannot be null.
   at Telerik.UI.Xaml.Controls.Chart.ChartTrackBallBehavior.GetIntersectionTemplate(DependencyObject instance)
   at Telerik.UI.Xaml.Controls.Chart.ChartTrackBallBehavior.UpdateIntersectionPoints(ChartDataContext context)
   at Telerik.UI.Xaml.Controls.Chart.ChartTrackBallBehavior.UpdateVisuals()
   at Telerik.UI.Xaml.Controls.Chart.RadChartBase.NotifyUIUpdated()
   at Telerik.UI.Xaml.Controls.Chart.PresenterBase.UpdateUI(ChartLayoutContext context)

我尝试在将每个系列添加到图表之前和之后禁用 ChartTrackBallBehavior,但无济于事。 我尝试手动将焦点更改为图表以外的其他控件,但无济于事。 我已尝试在不同区域手动调用 Chart.UpdateLayout(),但结果是这些调用在同一位置 (ChartTrackBallBehavior.cs) 内创建了相同的 NullReferenceException。

问题的核心似乎是这个“值”被错误地设置为 null。到目前为止,我还无法确定将什么“值”设置为 null,我只能假设它在 GetIntersectionTemplate() 函数调用中遇到 NullReferenceException throw() 调用。但我不知道为什么会这样,也不知道我能做些什么。

我制作了一个最小的项目来复制问题。请注意,它会在重新绘制图表上的所有系列之前清除图表中的所有系列。这样做是为了等同于我自己的项目,并且似乎与问题本身有关。这个系列清除和重新添加过程是因为用户可以随时更改要在图表上显示的系列。

我可能可以更改编码结构以从不同的方向解决此问题,但目前我想更好地了解导致此问题的原因并尽可能解决它,否则我可能需要重写大部分代码,不幸的是,时间不在我这边。

这里是示例代码。请注意,我为此代码使用 Telerik.UI.for.UniversalWindowsPlatform 版本 1.0.1.5。

MainPage.xaml

<Page
    x:Class="ExceptionReplicator.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ExceptionReplicator"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:telerikChart="using:Telerik.UI.Xaml.Controls.Chart"
    xmlns:telerikPrimitives="using:Telerik.UI.Xaml.Controls.Primitives"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <telerikChart:RadCartesianChart x:Name="MainChart" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10">
            <telerikChart:RadCartesianChart.Grid>
                <telerikChart:CartesianChartGrid MajorLinesVisibility="XY"/>
            </telerikChart:RadCartesianChart.Grid>

            <telerikChart:RadCartesianChart.Behaviors>
                <telerikChart:ChartPanAndZoomBehavior ZoomMode="Both" PanMode="Both"/>
                <telerikChart:ChartTrackBallBehavior x:Name="TrackBallBehaviour" InfoMode="Multiple" ShowIntersectionPoints="True">
                    <telerikChart:ChartTrackBallBehavior.LineStyle>
                        <Style TargetType="Polyline">
                            <Setter Property="Stroke" Value="Tomato"/>
                            <Setter Property="StrokeThickness" Value="2"/>
                            <Setter Property="StrokeDashArray" Value="1,2"/>
                        </Style>
                    </telerikChart:ChartTrackBallBehavior.LineStyle>
                    <telerikChart:ChartTrackBallBehavior.IntersectionTemplate>
                        <DataTemplate>
                            <Ellipse Width="10" Height="10" Fill="Tomato"/>
                        </DataTemplate>
                    </telerikChart:ChartTrackBallBehavior.IntersectionTemplate>

                </telerikChart:ChartTrackBallBehavior>
            </telerikChart:RadCartesianChart.Behaviors>

            <telerikChart:RadCartesianChart.VerticalAxis>
                <telerikChart:LinearAxis x:Name="Vertical" Title="Y Axis" Minimum="0"/>
            </telerikChart:RadCartesianChart.VerticalAxis>
            <telerikChart:RadCartesianChart.HorizontalAxis>
                <telerikChart:LinearAxis x:Name="Horizontal" Title="X Axis"/>
            </telerikChart:RadCartesianChart.HorizontalAxis>

        </telerikChart:RadCartesianChart>
    </Grid>
</Page>

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace ExceptionReplicator
{
    using System;
    using System.Threading;
    using Telerik.UI.Xaml.Controls.Chart;
    using Windows.UI.Core;

    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private Timer DataTimer;        // timer to periodically add data to the chart
        private int LineCount = 1;      // arbitrary line counter to differentiate lines from each other

        // custom class for holding the data to be displayed on the chart
        private class Data
        {
            public int XValue { get; set; }
            public int YValue { get; set; }
        }

        // overarching class that holds all of the data for ONE line/series
        private class DataToChart
        {
            // List of all the data points within this instance of DataToChart
            public List<Data> DataPoints;

            // Constructor to initialise DataPoints
            public DataToChart()
            {
                DataPoints = new List<Data>();
            }
        }

        // Overarching container to hold data for ALL lines/series
        private List<DataToChart> allData = new List<DataToChart>();

        public MainPage()
        {
            this.InitializeComponent();

            // set up the timer to call every 10s to add new data to the chart. warning: this will run infinitely
            DataTimer = new Timer(DataCallback, null, (int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
        }

        // Generic callback to call AddLineToChart() on the other thread to handle the Chart's data
        private void DataCallback(object state)
        {
            var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => AddLineToChart());
        }

        // Code to handle adding a line to the chart
        private void AddLineToChart()
        {
            // Using Random() to create random data
            Random rand = new Random();
            DataToChart dataToChart = new DataToChart();

            for (int i = 0; i < 50; i++)
            {
                dataToChart.DataPoints.Add(new Data
                {
                    XValue = i,
                    YValue = rand.Next(0, 100)
                });
            }

            // Add the data for this line/series to the overarching container
            allData.Add(dataToChart);

            // re-initialise the line count
            LineCount = 1;

            // Currently the code needs to clear the chart and redraw it each time new data is introduced
            MainChart.Series.Clear();

            // For each line/series in the main container
            foreach (DataToChart data in allData)
            {
                // Make a series for the line
                ScatterLineSeries scatterLineSeries = new ScatterLineSeries
                {
                    Name = $"Line {LineCount}",
                    ItemsSource = dataToChart.DataPoints,
                    XValueBinding = new PropertyNameDataPointBinding("XValue"),
                    YValueBinding = new PropertyNameDataPointBinding("YValue"),
                    DisplayName = $"Line {LineCount}",
                    LegendTitle = $"Line {LineCount}",
                };

                // Add the line to the Chart's Series collection
                MainChart.Series.Add(scatterLineSeries);

                // Increment arbitrary counter
                LineCount++;
            }

            // Re-set the timer to fire again in 10s
            DataTimer.Change((int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
        }
    }
}

我需要找到一种解决方案,以确保在引入新数据时不再出现此异常。任何帮助将不胜感激。

在短期内,我已经从我的图表中完全删除了 ChartTrackBallBehavior(已将其注释掉),直到我确定解决方案为止。移除 Behavior 后,不会发生此异常。

【问题讨论】:

    标签: c# xaml charts uwp telerik


    【解决方案1】:

    您的代码中存在一些问题。

    1. 您是否发现您的IntersectionTemplate 根本没有应用于您的“ScatterLineSeries”?我检查了 Telerik 文档TrackBall Behavior。他们将IntersectionTemplate 放在telerikChart:LineSeries 中,而不是telerikChart:RadCartesianChart.Behaviors。因此,您需要在后面的代码中将IntersectionTemplate 应用于 ScatterLineSeries。
    2. 每次有新数据时,您调用MainChart.Series.Clear(); 清除图表并重新绘制图表。当有大量数据时,它会导致性能问题。我建议您只需将新的 ScatterLineSeries 添加到图表中并保留旧数据。
    3. 您声明了List&lt;DataToChart&gt; allData 变量。我建议你使用ObservableCollection Class。该类实现了INotifyPropertyChanged接口,当集合中有新数据时,会通知UI。

    结合以上三点,我做了一个代码示例供大家参考:

    <Page.Resources>
        <DataTemplate x:Key="ChartTrackIntersectionTemplate">
            <Ellipse Width="10" Height="10" Fill="Tomato" />
        </DataTemplate>
    </Page.Resources>
    <Grid>
        <telerikChart:RadCartesianChart x:Name="MainChart" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10,10,10,10">
            <telerikChart:RadCartesianChart.Grid>
                <telerikChart:CartesianChartGrid MajorLinesVisibility="XY" />
            </telerikChart:RadCartesianChart.Grid>
    
            <telerikChart:RadCartesianChart.Behaviors>
                <telerikChart:ChartPanAndZoomBehavior ZoomMode="Both" PanMode="Both" />
                <telerikChart:ChartTrackBallBehavior x:Name="TrackBallBehaviour" InfoMode="Multiple" ShowIntersectionPoints="True">
                    <telerikChart:ChartTrackBallBehavior.LineStyle>
                        <Style TargetType="Polyline">
                            <Setter Property="Stroke" Value="Tomato" />
                            <Setter Property="StrokeThickness" Value="2" />
                            <Setter Property="StrokeDashArray" Value="1,2" />
                        </Style>
                    </telerikChart:ChartTrackBallBehavior.LineStyle>
                </telerikChart:ChartTrackBallBehavior>
            </telerikChart:RadCartesianChart.Behaviors>
    
            <telerikChart:RadCartesianChart.VerticalAxis>
                <telerikChart:LinearAxis x:Name="Vertical" Title="Y Axis" Minimum="0" />
            </telerikChart:RadCartesianChart.VerticalAxis>
            <telerikChart:RadCartesianChart.HorizontalAxis>
                <telerikChart:LinearAxis x:Name="Horizontal" Title="X Axis" />
            </telerikChart:RadCartesianChart.HorizontalAxis>
        </telerikChart:RadCartesianChart>
    </Grid>
    
    public sealed partial class MainPage : Page
    {
        private Timer DataTimer;        // timer to periodically add data to the chart
        private int LineCount = 1;      // arbitrary line counter to differentiate lines from each other
        private DataTemplate ChartTrackIntersectionTemplate;
    
        // custom class for holding the data to be displayed on the chart
        private class Data
        {
            public int XValue { get; set; }
            public int YValue { get; set; }
        }
    
        // overarching class that holds all of the data for ONE line/series
        private class DataToChart
        {
            // List of all the data points within this instance of DataToChart
            public List<Data> DataPoints;
    
            // Constructor to initialise DataPoints
            public DataToChart()
            {
                DataPoints = new List<Data>();
            }
        }
    
        // Overarching container to hold data for ALL lines/series
        private ObservableCollection<DataToChart> allData = new ObservableCollection<DataToChart>();
    
        public MainPage()
        {
            this.InitializeComponent();
            // set up the timer to call every 10s to add new data to the chart. warning: this will run infinitely
            DataTimer = new Timer(DataCallback, null, (int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
            ChartTrackIntersectionTemplate = this.Resources["ChartTrackIntersectionTemplate"] as DataTemplate;
            allData.CollectionChanged += AllData_CollectionChanged;
        }
    
        private void AllData_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            foreach (DataToChart data in e.NewItems)
            {
                // Make a series for the line
                ScatterLineSeries scatterLineSeries = new ScatterLineSeries
                {
                    Name = $"Line {LineCount}",
                    ItemsSource = data.DataPoints,
                    XValueBinding = new PropertyNameDataPointBinding("XValue"),
                    YValueBinding = new PropertyNameDataPointBinding("YValue"),
                    DisplayName = $"Line {LineCount}",
                    LegendTitle = $"Line {LineCount}",
                };
                ChartTrackBallBehavior.SetIntersectionTemplate(scatterLineSeries, ChartTrackIntersectionTemplate);
                // Add the line to the Chart's Series collection
                MainChart.Series.Add(scatterLineSeries);
                // Increment arbitrary counter
                LineCount++;
            }
        }
    
        // Generic callback to call AddLineToChart() on the other thread to handle the Chart's data
        private async void DataCallback(object state)
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => AddLineToChart());
        }
    
        // Code to handle adding a line to the chart
        private void AddLineToChart()
        {
            // Using Random() to create random data
            Random rand = new Random();
            DataToChart dataToChart = new DataToChart();
    
            for (int i = 0; i < 50; i++)
            {
                dataToChart.DataPoints.Add(new Data
                {
                    XValue = i,
                    YValue = rand.Next(0, 100)
                });
            }
    
            // Add the data for this line/series to the overarching container
            allData.Add(dataToChart);
            DataTimer.Change((int)TimeSpan.FromSeconds(10).TotalMilliseconds, Timeout.Infinite);
        }
    }
    

    【讨论】:

    • 对于 (1):您解决了一个我没有看到的问题!我在示例中偶然包含了它。我已将更改实施到我的程序中,并且可以正常工作。对于 (2):我已将程序更改为不再使用 MainChart.Series.Clear() 并处理系列来调整图表并且异常消失了。我仍然不明白异常,但它不再发生。对于(3):我不知道我可以使用这样的类。在这一点上不太适合我的程序使用它(尽管它适合这个例子),但我会记住这一点以备不时之需。感谢您的帮助
    • 我最终实现了 ObservableCollection 的使用,因为事实证明它可以使编码变得更简单。谢谢你带我去那门课。
    猜你喜欢
    • 2022-12-06
    • 2020-03-01
    • 1970-01-01
    • 2014-01-06
    • 2022-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多