Как скопировать DispatcherObject (BitmapSource) в другой поток? - PullRequest
5 голосов
/ 03 мая 2010

Я пытаюсь выяснить, как я могу скопировать DispatcherObject (в моем случае BitmapSource) в другой поток.

Вариант использования:
У меня есть приложение WPF, которое должно показывать окно в новом потоке (на самом деле это надстройка для Outlook, и мы должны сделать это, потому что Outlook имеет некоторые хуки в основном потоке пользовательского интерфейса и воровывает определенные горячие клавиши, которые нам нужны - «потерял» в переводе 'во взаимодействии с Outlook, WPF (который мы используем для пользовательского интерфейса) и Winforms (нам нужно использовать определенные элементы управления winforms, предоставляемые Microsoft).

С этим у меня есть моя реализация WPFMessageBox, которая настраивается путем установки некоторых статических свойств - и одним из них является BitmapSource для значка. Это используется для того, чтобы при запуске я мог установить WPFMessageBox.Icon один раз, и с тех пор каждый WPFMessageBox будет иметь одинаковый значок.

Проблема заключается в том, что BitmapSource, который назначен значку, является DispatcherObject, и при чтении он вызывает InvalidOperationException: «Вызывающий поток не может получить доступ к этому объекту, поскольку его принадлежит другой поток.».

Как я могу клонировать этот BitmapSource в реальный поток? У него есть методы Clone () и CloneCurrentValue (), которые не работают (они также выдают то же исключение). Мне также пришло в голову использовать originalIcon.Dispatcher.Invoke (выполните клонирование здесь), но Dispatcher в BitmapSource имеет значение null, и, тем не менее, я создал бы копию в неправильном потоке и все равно не смог бы использовать ее в моем. BitmapSource.IsFrozen == true.

Есть идеи, как скопировать BitmapSource в другой поток (без полной реконструкции его из файла изображения в новом потоке)?

EDIT: Таким образом, замораживание не помогает: в конце концов, у меня есть BitmapFrame (Window.Icon в любом случае не использует никакой другой вид ImageSource), и когда я назначаю его как Window.Icon в другом потоке, даже если он заморожен, я get 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#

Ответы [ 4 ]

7 голосов
/ 03 мая 2010

После вызова Freeze он должен работать в нескольких потоках.

3 голосов
/ 14 декабря 2011

bitmapSourceForOtherThread = new WriteableBitmap(previousBitmapSource);

Это по цене, но это довольно дешево по сравнению с сериализацией.

Длинный ответ .

3 голосов
/ 30 декабря 2010

Ключ к создает растровое изображение в потоке, который вы хотите использовать. Таким образом, вы не можете кэшировать свой значок в каком-то статическом поле / свойстве, загружать его (из файла, ресурса, потока или чего-либо еще) каждый раз, когда вы открываете новое окно в новом потоке.

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 
2 голосов
/ 30 декабря 2010

Один из обходных путей, который работает, хотя и не очень производительный, - это создание потока памяти из данных изображения, а затем восстановление изображения в потоке, в котором вы хотите его использовать.

Пример для 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;
}
...