我一直在使用UWP构建一个程序,该程序利用Telerik Charting控件向用户显示传入的数据。在我的最新测试中,未经请求的数据正在接收并在Chart上动态绘制,我遇到了一个未处理的异常(NullReferenceException)。这似乎仅在用户将鼠标悬停在带有显示TrackBall信息的图表(Telerik的ChartTrackBallBehavior)的图表上时发生。如果用户的鼠标在其他地方或没有触发TrackBall信息在图表上显示,则永远不会发生此异常。
到目前为止,我已经成功地跟踪了NullException发生在Telerik的UWP用户界面中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的“值”。到目前为止,我还无法确定将什么“ Value”设置为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从我的Chart中完全删除(注释掉),直到确定解决方案为止。删除“行为”后,不会发生此异常。
答案 0 :(得分:0)
您的代码中存在一些问题。
IntersectionTemplate
根本没有应用于您的“ ScatterLineSeries”?我检查了Telerik文档TrackBall Behavior。他们将IntersectionTemplate
而不是telerikChart:LineSeries
放在telerikChart:RadCartesianChart.Behaviors
中。因此,您需要在后面的代码中将IntersectionTemplate
应用于ScatterLineSeries。MainChart.Series.Clear();
来清除图表,并在每次有新数据时重新绘制它。当有大量数据时,它将导致性能问题。我建议您只将新的ScatterLineSeries添加到图表中,然后将旧数据保留在那里。List<DataToChart> 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);
}
}