How to copy DispatcherObject (BitmapSource) to another stream?

I am trying to figure out how to copy DispatcherObject (in my case, BitmapSource) to another thread.

Use Case:
I have a WPF application that should show the window in a new thread (the application is actually an Outlook add-on, and we need to do this because Outlook has some interceptions in the main UI thread and steals some hot keys that we need to use - "lost" in translation "in the Outlook interface, WPF (which we use for the user interface) and Winforms (we need to use certain winforms controls provided by Microsoft).

In doing so, I have my WPFMessageBox implementation that is configured by setting some static properties - and one of them is BitmapSource for the icon. This is used so that at startup I can install WPFMessageBox.Icon once, and from then on, each WPFMessageBox will have the same icon.

The problem is that the BitmapSource that is assigned to the icon is a DispatcherObject, and when reading it will throw an InvalidOperationException: "The calling thread cannot access this object because it has a different thread."

How can I clone this BitmapSource into an actual stream? It has the Clone () and CloneCurrentValue () methods that do not work (they also throw the same exception). I also had to use originalIcon.Dispatcher.Invoke (do the cloning here), but Disputcher BitmapSource is NULL, and yet - I would create a copy on the wrong thread and still could not use it on mine. BitmapSource.IsFrozen == true.

Any idea on how to copy BitmapSource to another stream (without completely reconstructing from the image file in the new stream)?

EDIT: So freezing doesn't help: I ​​end up with a BitmapFrame (Window.Icon doesn't use any other ImageSource type anyway), and when I assign it as Window.Icon in another thread, even if frozen, I get InvalidOperationException : "The calling thread cannot access this object because another thread belongs to it." with the following stack trace:

WindowsBase.dll!System.Windows.Threading.Dispatcher.VerifyAccess() + 0x4a bytes WindowsBase.dll!System.Windows.Threading.DispatcherObject.VerifyAccess() + 0xc bytes PresentationCore.dll!System.Windows.Media.Imaging.BitmapDecoder.Frames.get() + 0xe bytes PresentationFramework.dll!MS.Internal.AppModel.IconHelper.GetIconHandlesFromBitmapFrame(object callingObj = {WPFControls.WPFMBox.WpfMessageBoxWindow: header}, System.Windows.Media.Imaging.BitmapFrame bf = {System.Windows.Media.Imaging.BitmapFrameDecode}, ref MS.Win32.NativeMethods.IconHandle largeIconHandle = {MS.Win32.NativeMethods.IconHandle}, ref MS.Win32.NativeMethods.IconHandle smallIconHandle = {MS.Win32.NativeMethods.IconHandle}) + 0x3b bytes > PresentationFramework.dll!System.Windows.Window.UpdateIcon() + 0x118 bytes PresentationFramework.dll!System.Windows.Window.SetupInitialState(double requestedTop = NaN, double requestedLeft = NaN, double requestedWidth = 560.0, double requestedHeight = NaN) + 0x8a bytes PresentationFramework.dll!System.Windows.Window.CreateSourceWindowImpl() + 0x19b bytes PresentationFramework.dll!System.Windows.Window.SafeCreateWindow() + 0x29 bytes PresentationFramework.dll!System.Windows.Window.ShowHelper(object booleanBox) + 0x81 bytes PresentationFramework.dll!System.Windows.Window.Show() + 0x48 bytes PresentationFramework.dll!System.Windows.Window.ShowDialog() + 0x29f bytes WPFControls.dll!WPFControls.WPFMBox.WpfMessageBox.ShowDialog(System.Windows.Window owner = {WPFControlsTest.MainWindow}) Line 185 + 0x10 bytes C# 
+4
source share
4 answers

The key is to create a bitmap in the stream that you want to use. Thus, you cannot cache your icon in any static field / property, load it (from a file, resource, stream, or any other) every time you open a new window in a new stream.

BitmapFrame can only be used in a stream that was created only.

Even cloning doesn't work here, as you correctly stated (which just sucks).

I had exactly the same problem, and I decided to just load the icon every time, in my particular case, just by calling

 // get your stream somewhere - window.Icon = BitmapFrame.Create(stream) 

And here is how you can get your icon from a resource in WPF:

 var streamResourceInfo = Application.GetResourceStream(new Uri(@"pack://application:,,,/YourAssembly;relative path to the icon", UriKind.RelativeOrAbsolute)); // use streamResourceInfo.Stream 
+3
source

As soon as you call Freeze , it should work with multiple threads.

+5
source

One workaround that works, although not very efficient, is creating a memory stream from the image data and then restoring the image to the stream you want to use.

Example for BitmapSource :

 Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate() { //serialize image on UI thread imageStream = GetImageBytes(cameraImage); } ... //reconstruct image on a different thread: Bitmap bitmap = new Bitmap(imageStream); private MemoryStream GetImageBytes(BitmapSource image) { MemoryStream ms = new MemoryStream(); BitmapEncoder encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(image)); encoder.Save(ms); ms.Seek(0, SeekOrigin.Begin); return ms; } 
+2
source

bitmapSourceForOtherThread = new WriteableBitmap(previousBitmapSource);

It comes at a price, but it's pretty cheap compared to serialization.

Long answer .

+2
source

Source: https://habr.com/ru/post/1308673/


All Articles