Passing Wpf Objects Between Threads (With Source Code)
When working on yaTImer's new report engine I got myself into a bit of a problem, I'm blogging about it because I couldn't find an answer on the web and I can't believe I'm the only one with this problem, so I hope someone will find my solution helpful (or maybe suggest a better one).
My reports engine generates a FixedDocument that I can print or show to the user, because the report can contain a lot of information generating the document can potentially take some time, so I went for the easy solution and dropped a BackgroundWorker into the code.
So this:
void Window_Loaded(object sender, RoutedEventArgs e)
{
_documentViewer.Document = GenerateDocument();
}
Becomes this:
// WARNING: THIS CODE DOESN'T WORK
private BackgroundWorker _backgroundWorker;
private FixedDocument _result;
void Window_Loaded(object sender, RoutedEventArgs e)
{
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += DoGenerateDocument;
_backgroundWorker.RunWorkerCompleted += FinishedGenerating;
_backgroundWorker.RunWorkerAsync();
}
void DoGenerateDocument(object sender, DoWorkEventArgs e)
{
_result = GenerateDocument(); // this line throws an exception
}
void FinishedGenerating(object sender, RunWorkerCompletedEventArgs e)
{
_documentViewer.Document = _result;
}
While the code was very elegant it doesn't work, I got an exception with a very nice error message: "The calling thread must be STA, because many UI components require this."
Fortunately the error message is very clear and it's easy to find the solution on the web, you can't use Wpf objects from a thread-pool thread (or from a BackgroundWorker) you have to create your own thread, the code to create the thread is very simple:
Thread _backgroundThread;
_backgroundThread = new Thread(DoGenerateDocument);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();
Now because we no longer have BackgroundWorker events we have to write our own code to pass data between threads, this is easy to do with the Dispacher class
// WARNING: THIS CODE DOESN'T WORK
private Thread _backgroundThread;
void Window_Loaded(object sender, RoutedEventArgs e)
{
_backgroundThread = new Thread(DoGenerateDocument);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();
}
void DoGenerateDocument()
{
FixedDocument result = GenerateDocument();
Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(Action<FixedDocument> )FinishedGenerating,
result);
}
void FinishedGenerating(FixedDocument result)
{
_documentViewer.Document = result; // now this line throws an exception
}
This code is written inside a Wpf window, and the Window class has a Dispacher property that returns the dispatcher for the thread that created the window, if you're code doesn't use a window then you need to get the dispatcher associated with the thread you want to communicate with, this is done by reading Dispacher.CurrentDispacher from that thread. But this code also doesn't work, when I pass the FixedDocument into the document viewer I get this lovely exception: "The calling thread cannot access this object because a different thread owns it."
This is where things get complicated, I couldn't find any way to marshal the document into the UI thread.
Since this is a fixed document the natural thing to do is to save it into an XPS file (XPS is the Wpf version of PDF) and then read it from the other thread, I've tried doing this with a MemoryStream (I don't want to create an actual file) and I discovered a problem with this approach – apparently you can't read an XPS from a memory stream, you need an actual file.
At that point I got the clever idea of using XAML, you can read XAML from a memory stream and the code to read and write XAML is actually simpler then the XPS code, here is the final code using XamlReader and XamlWriter:
private Thread _backgroundThread;
void Window_Loaded(object sender, RoutedEventArgs e)
{
_backgroundThread = new Thread(DoGenerateDocument);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();
}
void DoGenerateDocument()
{
FixedDocument result = GenerateDocument();
MemoryStream stream = new MemoryStream();
XamlWriter.Save(result, stream);
Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(Action<MemoryStream> )FinishedGenerating,
stream);
}
void FinishedGenerating(MemoryStream stream)
{
stream.Seek(0, SeekOrigin.Begin);
FixedDocument result = (FixedDocument)XamlReader.Load(stream);
_documentViewer.Document = result;
}
This trick should use with any Wpf object, not just FixedDocument.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Exception in thread main java.lang. NoClassDefFoundError 오류 해결 방법즉,/home/hadoop/jarfile) 시스템은 Hello World 패키지 아래의class라는 클래스 파일을 실행하고 있다고 오인하여 시스템의 CLASSPATH 아래 (일반적으로 현재 디렉터리를 포함) Hell...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.