It's my first time I using WPF and I have a problem. I can't update my oxyplot model from another thread. I can draw point in not another thread, but when I'm trying to do so in another nothing happens. Now I have this code:
private void Button_Click(object sender, RoutedEventArgs e)
{
doComputingThread compute = new doComputingThread();
Thread _MainThread = new Thread(new ThreadStart(compute.MainThread));
_MainThread.Start();
}
class doComputingThread{
public doComputingThread()
{
DataPlot = new PlotModel();
DataPlot.Series.Add(new LineSeries());
}
public void MainThread()
{
bool flag;
_timer = new System.Timers.Timer();
_timer.Interval = 10;
_timer.Elapsed += (sender, e) => { GuiRefresher(true); };
_timer.Enabled = true;
Thread _ComputeThread = new Thread(new ThreadStart(ProducerThread));
_ComputeThread.Start();
}
public void ProducerThread()
{
//populate queue
int X = 0;
int Y = 0;
for (double t = 0; t < 2 * 3.14; t = t + 0.1)
{
X = (int)(Math.Cos(t) * 5000);
Y = (int)(Math.Sin(t) * 5000);
Coordinate.X = X;
Coordinate.Y = Y;
_queue.Enqueue(Coordinate);
}
public void GuiRefresher(object flag)
{
if (_queue.TryDequeue(out Coordinate))
{
//this part didn't refresh my oxyplot
Dispatcher.CurrentDispatcher.Invoke(() =>
{
(DataPlot.Series[0] as LineSeries).Points.Add(new DataPoint(Coordinate.X, Coordinate.Y));
DataPlot.InvalidatePlot(true);
});
}
All working as expected except the part when Dispatcher.CurrentDispatcher
. I didn't understand why my plot didn't refresh.
I have some idea that I don't understand what thread is UI thread in this case with WPF and maybe I should initiate my threads in doComputingThread
constructor.
Xaml:
<ui:WslMainWindow x:Class="fpga_control.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:oxy="http://oxyplot.org/wpf"
xmlns:local="clr-namespace:fpga_control"
xmlns:ui="clr-namespace:Keysight.Ccl.Wsl.UI;assembly=Keysight.Ccl.Wsl"
xmlns:DynamicVectorImages="clr-namespace:Keysight.Ccl.Wsl.UI.Controls.DynamicVectorImages;assembly=Keysight.Ccl.Wsl"
Title="Example 1 (WPF)" Height="461.311" Width="621.393">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid >
<oxy:PlotView Model="{Binding DataPlot}" Margin="10,10,152,0" Height="418" VerticalAlignment="Top"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="464,10,0,0" VerticalAlignment="Top" Width="137" Height="38" RenderTransformOrigin="0.303,1.929" Click="Button_Click"/>
<TextBox x:Name="txb" HorizontalAlignment="Left" Height="23" Margin="468,53,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="133"/>
</Grid>
Your code appears to be adding the same co-ordinate to the queue in the loop. You're not creating a new Coordinate
instance each time during the loop.
Also your _queue.TryDequeue(out Coordinate)
line produces a syntax error for me as it's trying to use Coordinate
as a variable rather than a class.
Without the definition for Coordinate
I can't confirm any of this.
Also, I cannot see where you've actually added your DataPlot
to your form or any control on your form. It just doesn't appear that you're displaying anything in any case.
The bottom-line is that your example code isn't a minimal, complete and verifiable example. So I can only guess at what the issues are.
I am going to give you an alternative that I think will give you what you want.
To start with I'm providing a simple definition for Coordinate
:
public class Coordinate
{
public int X;
public int Y;
}
Now I'm going to use Microsoft's Reactive Framework to do all of the threading, computation, and dispatching of your code - here's how I would write the Button_Click
handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
DataPlot = new PlotModel();
DataPlot.Series.Add(new LineSeries());
int steps = 60;
IObservable<Coordinate> query =
Observable
.Generate(
0,
n => n < steps,
n => n + 1,
n => n * 2.0 * Math.PI / steps,
n => TimeSpan.FromMilliseconds(10.0))
.Select(t => new Coordinate()
{
X = (int)(Math.Cos(t) * 5000),
Y = (int)(Math.Sin(t) * 5000),
});
IDisposable subscription =
query
.ObserveOnDispatcher()
.Subscribe(c =>
{
(DataPlot.Series[0] as LineSeries).Points.Add(new DataPoint(c.X, c.Y));
DataPlot.InvalidatePlot(true);
});
}
With this code you no longer need your doComputingThread
class at all.
The Observable.Generate
code produces the t
values for the 60
data-points that your original code produced, but it does so only one value at a time every 10.0
milliseconds. This code is effectively a timer and the producer of the t
steps.
The .Select
maps the t
value to a Coordinate
value.
The subscription
is the execution of the query
. The first step is to marshall the values back to the UI thread with the .ObserveOnDispatcher()
call and then the .Subscribe
takes each value and runs the c => ...
delegate on the UI thread.
This should update the plot nicely.
If you want to stop the plotting early just call subscription.Dispose()
and it will stop.
You need to NuGet "System.Reactive" and "System.Reactive.Windows.Threading" to get the bits. You will also need the following using
statements to get the code to compile:
using System.Reactive;
using System.Reactive.Linq;
Just don't forget - you still need to add the DataPlot
to your form somehow.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.