c# - tutorial - wpf vs winforms
cargar asincrónicamente una BitmapImage en C#usando WPF (6)
Suponiendo que está utilizando el enlace de datos, establecer la propiedad Binding.IsAsync en True parece ser una forma estándar de lograr esto. Si está cargando el mapa de bits en el archivo de código subyacente utilizando el hilo de fondo + el objeto Dispatcher es una forma común de actualizar la IU asíncrona
¿Cuál es la mejor manera de cargar asincrónicamente una imagen de mapa de bits en C # usando WPF? Parece que existen muchas soluciones, pero ¿existe un patrón estándar o mejores prácticas?
¡Gracias!
Use o extienda System.ComponentModel.BackgroundWorker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Personalmente, considero que esta es la forma más fácil de realizar operaciones asíncronas en aplicaciones cliente. (He usado esto en WinForms, pero no en WPF. Asumo que esto también funcionará en WPF).
Normalmente extiendo Backgroundworker, pero no es necesario.
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 */
}
}
}
Así es como lo usarías en tu forma:
//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
}
Para elaborar sobre la respuesta de aku, aquí hay un pequeño ejemplo de dónde establecer el IsAsync:
ItemsSource="{Binding IsAsync=True,Source={StaticResource ACollection},Path=AnObjectInCollection}"
Eso es lo que harías en XAML.
Solo estaba investigando esto y tuve que dar mi granito de arena, aunque unos años después de la publicación original (por si acaso alguien más viene a buscar lo mismo que yo estaba buscando).
Tengo un control de imagen que necesita tener su imagen cargada en segundo plano usando un flujo , y luego se muestra.
El problema con el que me encontré es que BitmapSource , su origen de Stream y el control Image tenían que estar todos en el mismo hilo.
En este caso, usar un enlace y establecer que es IsAsynch = verdadero arrojará una excepción de hilo cruzado.
Un BackgroundWorker es ideal para WinForms, y puede usarlo en WPF, pero prefiero evitar el uso de los ensamblados de WinForm en WPF (no se recomienda la hinchazón de un proyecto, y también es una buena regla general). Esto también arrojaría una excepción de referencia cruzada no válida en este caso, pero no lo probé.
Resulta que una línea de código hará que cualquiera de estos funcione:
//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();
BitmapCacheOption.OnLoad
var bmp = await System.Threading.Tasks.Task.Run(() =>
{
BitmapImage img = new BitmapImage();
img.BeginInit();
img.CacheOption = BitmapCacheOption.OnLoad;
img.UriSource = new Uri(path);
img.EndInit();
ImageBrush brush = new ImageBrush(img);
}
Esto le permitirá crear BitmapImage en el hilo de la interfaz de usuario utilizando HttpClient para realizar la descarga asincrónica:
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;
}
}