Как скопировать DispatcherObject (Bitmapsource) в другой поток?
-
02-10-2019 - |
Вопрос
Я пытаюсь выяснить, как я могу скопировать DispatcherObject (в моем случае Bitmapsource) в другой ветку.
В случае использования:
У меня есть приложение WPF, которое должно показать окно в новой ветке (приложение на самом деле является Addin Outlook, и нам нужно сделать это, потому что у Outlook есть несколько крючков в основной ветке пользовательского интерфейса и крадут определенные горячие клавиши, которые нам нужно использовать - В переводе «Interop of Outlook, WPF (который мы используем для пользовательского интерфейса) и Winforms (нам нужно использовать определенные средства управления Winforms, предоставленные Microsoft)).
При этом у меня есть реализация WPFMessagebox, которая настроена путем установки некоторых статических свойств - и один из них является Bitmapsource для значка. Это используется таким образом, чтобы при запуске я мог установить wpfmessagebox.icon один раз, и с тех пор каждый wpfmessagebox будет иметь одинаковый значок.
Проблема заключается в том, что Bitmapsource, который присваивается в значке, является диспетчером, и когда он прочитал, он будет выбросить Invalidoperation Exception: «Вызовный поток не может получить доступ к этому объекту, потому что другой поток владеет им».
Как я могу клонировать этот Bitmapsource в реальную ветку? У него есть методы Clone () и CloneCurrentValue (), которые не работают (они также бросают такое же исключение). Мне также пришло в голову использовать OriginalIcon.dispatcher.invoke (сделайте клонирование здесь) - но диспетчер Bitmapsource - нулевой, и все же - я бы создал копию в неправильном потоке и все еще не мог бы использовать ее на моем. Bitmapsource.isfrozen == true.
Есть идеи о том, как скопировать Bitmapsource в другой поток (без полной реконструкции его из файла изображения в новом потоке)?
РЕДАКТИРОВАТЬ:Итак, замораживание не помогает: в конце концов, у меня есть растровый рамский каплей (window.icon не принимает никакого другого вида изображений в любом случае), и когда я назначаю его как окно .icon в другой потоке, даже если заморожен, я Получите InvalidoperationException: «Вызовный поток не может получить доступ к этому объекту, потому что другой поток владеет им». С следующей трассировкой стека:
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#
Решение
Ключ должен Создайте Растровая карта на потоке, которую вы хотите использовать. Таким образом, вы не можете кэшировать свой значок в каком -то статическом поле/свойстве, загружайте его (из файла, ресурс, поток или что -то еще) каждый раз, когда вы открываете новое окно в новом потоке.
BitmapFrame может использоваться в потоке, который он был создан только.
Даже клонирование здесь не работает, как вы правильно указали (что просто отстой).
я имел точно та же самая проблема и решила это просто загружая значок каждый раз, в моем конкретном случае, просто позвонив
// get your stream somewhere -
window.Icon = BitmapFrame.Create(stream)
И вот как вы можете получить свой значок из ресурса в WPF:
var streamResourceInfo = Application.GetResourceStream(new Uri(@"pack://application:,,,/YourAssembly;relative path to the icon", UriKind.RelativeOrAbsolute));
// use streamResourceInfo.Stream
Другие советы
Как только вы позвоните Freeze
, это должно работать на нескольких потоках.
bitmapSourceForOtherThread = new WriteableBitmap(previousBitmapSource);
Это идет по цене, но это довольно дешево по сравнению с сериализацией.
Один обходной путь, который работает, хотя и не очень производительный, - это создание потока памяти из данных изображения, а затем реконструировать изображение в потоке, на котором вы хотите использовать его.
Пример для 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;
}