В фоновом потоке формирую матрицу случайными числами:
public void Step()
{
Point p;
Brush b;
Random r = new Random();
for (int i = 0; i < mh; i++)
{
for (int j = 0; j < mw; j++)
{
p = new Point(i*5, j*5);
int ch = r.Next(0, 100);
if (ch <= 33)
{
b = Brushes.Green;
vl.Add(new VisualList(p, b));
}
else if (ch > 66)
{
b = Brushes.Red;
vl.Add(new VisualList(p, b));
}
else
{
b = Brushes.Yellow;
vl.Add(new VisualList(p, b));
}
}
}
}
И пытаюсь ее отрисовать с помощью DrawingVisual:
private void Print()
{
visual = new DrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
for (int i = 0; i < vl.Count; i++)
{
VisualList vlist = vl[i];
Brush brush = Brushes.Black;
dc.DrawRectangle(vlist.Brushd, null, new Rect(vlist.Pointd, new Size(4, 4)));
}
}
this.Dispatcher.BeginInvoke((Action)(() =>
{
drawingSurface.AddVisual(visual);
}));
}
В итоге получаю ошибку: Необработанное исключение типа "System.Reflection.TargetInvocationException" в mscorlib.dll
Дополнительные сведения: Адресат вызова создал исключение.
Как из фонового потока рисовать на форме?
Полный код
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace matrix
{
///
public MainWindow()
{
InitializeComponent();
}
private void TestWindow_Loaded(object sender, RoutedEventArgs e)
{
Thread trMatrix = new Thread(Draw);
trMatrix.SetApartmentState(ApartmentState.STA);
trMatrix.Start();
}
private void Draw()
{
while (Stop)
{
Step();
Print();
}
}
private void Print()
{
visual = new DrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
for (int i = 0; i < vl.Count; i++)
{
VisualList vlist = vl[i];
Brush brush = Brushes.Black;
dc.DrawRectangle(vlist.Brushd, null, new Rect(vlist.Pointd, new Size(4, 4)));
}
}
this.Dispatcher.BeginInvoke((Action)(() =>
{
drawingSurface.AddVisual(visual);
}));
}
public void Step()
{
Point p;
Brush b;
Random r = new Random();
for (int i = 0; i < mh; i++)
{
for (int j = 0; j < mw; j++)
{
p = new Point(i*5, j*5);
int ch = r.Next(0, 100);
if (ch <= 33)
{
b = Brushes.Green;
vl.Add(new VisualList(p, b));
}
else if (ch > 66)
{
b = Brushes.Red;
vl.Add(new VisualList(p, b));
}
else
{
b = Brushes.Yellow;
vl.Add(new VisualList(p, b));
}
}
}
}
}
public class VisualList
{
public VisualList(Point pointd, Brush brushd)
{
Pointd = pointd;
Brushd = brushd;
}
public Point Pointd
{
get;
set;
}
public Brush Brushd
{
get;
set;
}
}
}
Код класса рисования
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Media;
namespace TestDifferentWpf
{
class DrawingClass : Canvas
{
private List
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
public void AddVisual(Visual visual)
{
visuals.Add(visual);
base.AddVisualChild(visual);
base.AddLogicalChild(visual);
}
public void DeleteVisual(Visual visual)
{
visuals.Remove(visual);
base.RemoveVisualChild(visual);
base.RemoveLogicalChild(visual);
}
}
}
XAML
Ответ
Нет, так, как вы хотите, нельзя: все FrameworkElement'ы, которые добавляются друг в друга как Child'ы, должны принадлежать одному потоку.
Поэтому так просто рисовать из фонового потока не получится.
Обычно никто не заморачивается и рисует в главном потоке. Но если очень хочется, вам придётся освоить PresentationSource и VisualTarget
PresentationSource — это штука, позволяющая вставить в визуальное дерево какую-то совершенно чужую вещь. А VisualTarget позволяет соединять визуальные поддеревья, бегущие в разных потоках.
Вот примерная имплементация, которая производит рендеринг в фоновом потоке. Она сделана по этим источникам:
https://blogs.msdn.microsoft.com/dwayneneed/2007/04/26/multithreaded-ui-hostvisual/
http://gettinggui.com/creating-a-busy-indicator-in-a-separate-thread-in-wpf/
Итак, для начала, заводим нашу имплементацию PresentationSource
// https://blogs.msdn.microsoft.com/dwayneneed/2007/04/26/multithreaded-ui-hostvisual/
// https://github.com/higankanshi/Meta.Vlc/blob/master/Meta.Vlc.Wpf
// /VisualTargetPresentationSource.cs
public class VisualTargetPresentationSource : PresentationSource, IDisposable
{
public VisualTargetPresentationSource(HostVisual hostVisual)
{
_visualTarget = new VisualTarget(hostVisual);
AddSource();
}
public override Visual RootVisual
{
get => _visualTarget.RootVisual;
set
{
Visual oldRoot = _visualTarget.RootVisual;
_visualTarget.RootVisual = value;
RootChanged(oldRoot, value);
if (value is UIElement rootElement)
{
rootElement.Measure(new Size(double.PositiveInfinity,
double.PositiveInfinity));
rootElement.Arrange(new Rect(rootElement.DesiredSize));
}
}
}
protected override CompositionTarget GetCompositionTargetCore() => _visualTarget;
public override bool IsDisposed => _isDisposed;
public void Dispose()
{
RemoveSource();
_isDisposed = true;
}
private VisualTarget _visualTarget;
private bool _isDisposed;
}
Остальную функциональность я упаковал в MainWindow, но её, вероятно, стоит разбить на вспомогательные классы.
Объяснение по коду. В конструкторе создаётся HostVisual, на который «наденется» VisualTarget через VisualTargetPresentationSource, и добавляется в визуальное дерево. Поскольку стандартные контролы не умеют добавлять Visual'ы, мы пользуемся DrawingClass из вопроса.
Далее, мы создаём STA-поток, в котором и будет происходить рендеринг поддерева, и запускаем его.
В потоке мы создаём VisualTargetPresentationSource. Затем, нам нужно сначала создать диспетчер, а потом выполнить на нём код, для этого используется трюк с InvokeAsync. (Dispatcher.Run() — блокирующая функция!)
В коде мы создаём Random (один раз, а не на каждой итерации), и в цикле вызываем функции Step и Draw из вопроса. Я немного переписал функции, чтобы они работали не с полями, а с параметрами. Ну и между итерациями я вставил Task.Delay, чтобы не гонять вечный холостой цикл.
Вот весь код:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var hv = new HostVisual();
drawingSurface.AddVisual(hv);
Thread trMatrix = new Thread(() => Draw(hv)) { IsBackground = true };
trMatrix.SetApartmentState(ApartmentState.STA);
trMatrix.Start();
}
void Draw(HostVisual outerhv)
{
VisualTargetPresentationSource vtps = new VisualTargetPresentationSource(outerhv);
var dispatcher = Dispatcher.CurrentDispatcher;
dispatcher.InvokeAsync(async () =>
{
Random r = new Random();
while (true)
{
var vl = Step(r);
var visual = Print(vl);
vtps.RootVisual = visual;
await Task.Delay(50);
}
});
Dispatcher.Run();
vtps.Dispose();
}
public List
List
for (int i = 0; i < mh; i++)
{
for (int j = 0; j < mw; j++)
{
Point p = new Point(i * 5, j * 5);
int ch = r.Next(0, 3);
Brush b = ch == 0 ? Brushes.Green :
ch == 1 ? Brushes.Yellow :
Brushes.Red;
vl.Add(new VisualList(p, b));
}
}
return vl;
}
private Visual Print(List
return visual;
}
}
Получилось вот что:
На старой системе пользоваться async/await может быть сложно, вместо этого приходится использовать таймер:
public void Draw(HostVisual outerhv)
{
VisualTargetPresentationSource vtps = new VisualTargetPresentationSource(outerhv);
var dispatcher = Dispatcher.CurrentDispatcher;
DispatcherTimer dt = new DispatcherTimer(DispatcherPriority.Normal, dispatcher)
{
Interval = TimeSpan.FromMilliseconds(50),
IsEnabled = true
};
Random r = new Random();
dt.Tick += (o, args) =>
{
var vl = Step(r);
var visual = Print(vl);
vtps.RootVisual = visual;
};
Dispatcher.Run();
vtps.Dispose();
}
Комментариев нет:
Отправить комментарий