WPFを使用してC#でBitmapImageを非同期的にロードするための最良の方法は何ですか?
6 に答える
元の投稿から数年後ですが、私はこれを調べていて、2セントを投入しなければなりませんでした(他の誰かが私が調べていたのと同じものを探しに来た場合に備えて)。
ストリームを使用して画像をバックグラウンドでロードしてから表示する必要がある画像コントロールがあります。
私が遭遇し続けた問題は、BitmapSource、それはStreamソースであり、Imageコントロールはすべて同じスレッド上になければならないということです。
この場合、Bindingを使用してIsAsynch = trueに設定すると、クロススレッド例外がスローされます。
BackgroundWorkerはWinFormsに最適であり、これをWPFで使用できますが、WPFでWinFormアセンブリを使用することは避けたいと思います(プロジェクトの肥大化はお勧めしません。これも経験則です)。この場合も無効な相互参照例外がスローされるはずですが、私はそれをテストしませんでした。
1行のコードでこれらのいずれかが機能することがわかります。
//Create the image control
Image img = new Image {HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch};
//Create a seperate thread to load the image
ThreadStart thread = delegate
{
//Load the image in a seperate thread
BitmapImage bmpImage = new BitmapImage();
MemoryStream ms = new MemoryStream();
//A custom class that reads the bytes of off the HD and shoves them into the MemoryStream. You could just replace the MemoryStream with something like this: FileStream fs = File.Open(@"C:\ImageFileName.jpg", FileMode.Open);
MediaCoder.MediaDecoder.DecodeMediaWithStream(ImageItem, true, ms);
bmpImage.BeginInit();
bmpImage.StreamSource = ms;
bmpImage.EndInit();
//**THIS LINE locks the BitmapImage so that it can be transported across threads!!
bmpImage.Freeze();
//Call the UI thread using the Dispatcher to update the Image control
Dispatcher.BeginInvoke(new ThreadStart(delegate
{
img.Source = bmpImage;
img.Unloaded += delegate
{
ms.Close();
ms.Dispose();
};
grdImageContainer.Children.Add(img);
}));
};
//Start previously mentioned thread...
new Thread(thread).Start();
データバインディングを使用していると仮定すると、Binding.IsAsyncプロパティをTrueに設定することが、これを実現するための標準的な方法のようです。バックグラウンドスレッドを使用してコードビハインドファイルにビットマップをロードしている場合+ディスパッチャーオブジェクトは、UIを非同期で更新する一般的な方法です
これにより、HttpClient を使用して非同期ダウンロードを行うことにより、UI スレッドで BitmapImage を作成できます。
private async Task<BitmapImage> LoadImage(string url)
{
HttpClient client = new HttpClient();
try
{
BitmapImage img = new BitmapImage();
img.CacheOption = BitmapCacheOption.OnLoad;
img.BeginInit();
img.StreamSource = await client.GetStreamAsync(url);
img.EndInit();
return img;
}
catch (HttpRequestException)
{
// the download failed, log error
return null;
}
}
aku の回答を詳しく説明するために、IsAsync を設定する場所に関する小さな例を次に示します。
ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"
これは、XAML で行うことです。
System.ComponentModel.BackgroundWorkerを使用または拡張します:http:
//msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
個人的には、これがクライアントアプリで非同期操作を実行する最も簡単な方法だと思います。(これはWinFormsで使用しましたが、WPFでは使用していません。これはWPFでも機能すると思います。)
私は通常Backgroundworkerを拡張しますが、そうする必要はありません。
public class ResizeFolderBackgroundWorker : BackgroundWorker
{
public ResizeFolderBackgroundWorker(string sourceFolder, int resizeTo)
{
this.sourceFolder = sourceFolder;
this.destinationFolder = destinationFolder;
this.resizeTo = resizeTo;
this.WorkerReportsProgress = true;
this.DoWork += new DoWorkEventHandler(ResizeFolderBackgroundWorker_DoWork);
}
void ResizeFolderBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DirectoryInfo dirInfo = new DirectoryInfo(sourceFolder);
FileInfo[] files = dirInfo.GetFiles("*.jpg");
foreach (FileInfo fileInfo in files)
{
/* iterate over each file and resizing it */
}
}
}
これはあなたがあなたのフォームでそれをどのように使うかです:
//handle a button click to start lengthy operation
private void resizeImageButtonClick(object sender, EventArgs e)
{
string sourceFolder = getSourceFolderSomehow();
resizer = new ResizeFolderBackgroundWorker(sourceFolder,290);
resizer.ProgressChanged += new progressChangedEventHandler(genericProgressChanged);
resizer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(genericRunWorkerCompleted);
progressBar1.Value = 0;
progressBar1.Visible = true;
resizer.RunWorkerAsync();
}
void genericRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Visible = false;
//signal to user that operation has completed
}
void genericProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
//I just update a progress bar
}