Создание графиков в реальном времени с использованием внешних данных
Я создаю графический плоттер WPF в реальном времени, который будет отображать точки данных по мере их поступления. Он использует библиотеку динамического отображения данных. ( http://dynamicdatadisplay.codeplex.com/)
В настоящее время Приложение настроено так, что оно:
Обновляет графический плоттер каждый раз, когда происходит изменение в ObservableCollection, которое я создал в приложении WPF.
Добавляет точки данных с помощью пользовательского метода AddDataPoint(...), который изменяет ObservableCollection.
Приложение работает, как и ожидалось, в среде (когда я нажимаю клавишу F5, чтобы отладить свое решение), но это только потому, что я передаю "поддельные" точки данных для тестирования приложения. Я использую внутренний DispatchTimer / Random.Next (..) для непрерывной подачи в сгенерированные точки данных к внутреннему экземпляру ObservableCollection; однако я не знаю, как разрешить внешнему классу или внешнему источнику данных вводить "реальные" данные для построения графика.
Я действительно новичок в WPF и C# в целом, поэтому, хотя я много занимался Google по этому вопросу, я не смог найти конкретный ответ (например, привязку данных - мне кажется, что это только для использования в приложении). Я застрял, когда дело доходит до передачи данных в реальном времени из внешнего источника в мое приложение WPF.
Я попытался добавить файл.exe WPF в качестве ресурса во внешний класс / решение, которое предоставит данные, и запустить экземпляр WPF с помощью:
"WPFAppNameSpace".MainWindow w = new "WPFAppNameSpace".MainWindow();
w.Show();
w.AddDataPoint(...);
Но это не сработало. Окно даже не появляется! Можно ли передать внешний класс (не приложение WPF) в мое графическое приложение WPF? Если так, как я могу сделать это и что я должен исследовать?
Вот некоторые фрагменты кода из моего проекта:
XAML
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0" Title="MainWindow" Height="480" Width="660" Loaded="Window_Loaded" WindowState="Maximized"> <Grid> <d3:ChartPlotter Name="plotter" Margin="12,10,12,14"> <d3:ChartPlotter.MainHorizontalAxis> <d3:HorizontalAxis Name="xAxis"></d3:HorizontalAxis> </d3:ChartPlotter.MainHorizontalAxis> <d3:ChartPlotter.MainVerticalAxis> <d3:VerticalAxis Name="yAxis"></d3:VerticalAxis> </d3:ChartPlotter.MainVerticalAxis> <d3:VerticalAxisTitle Content="Voltage"/> <d3:HorizontalAxisTitle Content="Test Identifier"/> <d3:Header TextBlock.FontSize="20" Content="Dynamically Updated Graph"/> </d3:ChartPlotter> </Grid> </Window>
MainWindow.xaml.cs
namespace WpfApplication1 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { //Dummy Variables for Testing private readonly Random rand = new Random(); private DispatcherTimer dt = new DispatcherTimer(); ... //Data Sources for Graph private List<EnumerableDataSource<DataPoint>> enumSources; private List<ObservableCollection<DataPoint>> observableSources; private int dataSourceIndex = 0; public MainWindow() { InitializeComponent(); } //Automatically Called after MainWindow() Finishes. Initlialize Graph. private void Window_Loaded(object sender, RoutedEventArgs e) { //Initlizes Data Sources for Graph enumSources = new List<EnumerableDataSource<DataPoint>>(); observableSources = new List<ObservableCollection<DataPoint>>(); //Adds a new Source to the Graph (New Line on Graph) addNewSource("Test Data " + dataSourceIndex); //TESTING PURPOSES dt.Interval = new TimeSpan(25000); dt.Tick += new EventHandler(timerAction); dt.IsEnabled = true; dt.Start(); } //Adds data into the observableSource and alerts the enumSource to add point into Graph private void AsyncAppend(...) {...} //Adds a new DataPoint onto the Graph and Specifies if it starts a new Line. public void AddDataPoint(..., bool newLine) {...} //Tests Function for Adding New Points/New Lines to Graph; Called by Timer private void timerAction(object sender, EventArgs e) { //current count of points in a particular line var count = observableSources[dataSourceIndex].Count; if (count < 100) { //Adds Data to Current Line AddDataPoint(...); } else { //Starts New Line and Adds Data AddDataPoint(..., true); } } //Adds a new Data Source onto the Graph (Starts a New Line) private void addNewSource(string legendKey){...} //DataPoint Object to Pass into the Graph private class DataPoint { //X-Coord of the Point public double xCoord { get; set; } //Y-Coord of the Point public double yCoord { get; set; } //DataPoint's Label Name public string labelName { get; set; } //Constructor for DataPoint public DataPoint(double x, double y, string label = "MISSNG LBL") { xCoord = x; yCoord = y; labelName = label; } } }
1 ответ
В качестве примера Reactive Extensions приведу класс, который получает данные путем моделирования с произвольным интервалом от 0 до 5 секунд.
public class DataAquisitionSimulator:IObservable<int>
{
private static readonly Random RealTimeMarketData = new Random();
public IDisposable Subscribe(IObserver<int> observer)
{
for (int i = 0; i < 10; i++)
{
int data = RealTimeMarketData.Next();
observer.OnNext(data);
Thread.Sleep(RealTimeMarketData.Next(5000));
}
observer.OnCompleted();
return Disposable.Create(() => Console.WriteLine("cleaning up goes here"));
}
}
Он имитирует получение рыночных данных (в данном случае только случайные целые числа) и отправку их наблюдателю. Между наблюдениями он спит некоторое время, чтобы смоделировать задержку рынка.
Вот скелетный класс, который был настроен как потребитель...
public class DataConsumer : IObserver<int>
{
private readonly IDisposable _disposable;
public DataConsumer(DataAquisitionSimulator das)
{
_disposable = das.Subscribe(this);
_disposable.Dispose();
}
public void OnCompleted()
{
Console.WriteLine("all done");
}
public void OnError(Exception error)
{
throw error;
}
public void OnNext(int value)
{
Console.WriteLine("New data " + value + " at " + DateTime.Now.ToLongTimeString());
}
}
Он реализует три необходимых метода и отмечает, что OnNext вызывается при каждом появлении новых данных. Предположительно, этот класс будет реализован в вашей виртуальной машине и немедленно вставит новые данные в конвейер привязки, чтобы пользователи могли их визуализировать.
Чтобы увидеть взаимодействие этих двух классов, вы можете добавить это в консольное приложение...
static void Main(string[] args)
{
DataAquisitionSimulator v = new DataAquisitionSimulator();
DataConsumer c = new DataConsumer(v);
}
Проектирование ваших потоков является ключевым, но в остальном это пример того, как внешние данные с задержкой и нерегулярными наблюдениями могут быть структурированы.