WPF UI multitarea

Estoy creando algunos UI programmatically que implica un procesamiento pesado. Básicamente lo que quiero es ejecutar una animación de cargador mientras mi UI se está construyendo / agregando a la ventana. La interfaz de usuario que se agrega son algunas cuadrículas y algunas imágenes que se cargan en ellas.

Hasta ahora, he probado BackgroundWorker, pero dado que necesito usar el subproceso de la interfaz de usuario para agregar la interfaz de usuario que estoy creando, el cargador no comenzará / animará hasta que la interfaz de usuario que se está agregando haya finalizado.

También probé algunos métodos de sincronización sin ningún resultado. Mi último bash fue algo como esto, pero todavía necesito acceder al hilo de la interfaz de usuario que eventualmente congelaría la animación de la interfaz de usuario hasta que el trabajo esté terminado.

private async void begin() { await this.LongRunOpAsync(); } public Task LongRunOpAsync() { return Task.Run(() => { InitGrid(); stopLoadingScreen(); }); } 

Puede que me esté perdiendo algo, pero no sé qué, y tampoco tengo ideas para hacer esto. Cualquier ayuda sería apreciada.

EDITAR: Método que tiene el trabajo pesado

 private void makeIgrid() { Grid hostgrid = new Grid(); hostgrid.Name = "imagesHostGrid"; hostgrid.Width = 700; hostgrid.VerticalAlignment = VerticalAlignment.Top; hostgrid.HorizontalAlignment = HorizontalAlignment.Center; hostgrid.SetValue(Canvas.ZIndexProperty, 0); this.RegisterName(hostgrid.Name, hostgrid); Grid imagegrid = new Grid(); imagegrid.Name = "imagegrid"; imagegrid.Height = height2; //imagegrid.Width = 700; imagegrid.SetValue(Canvas.ZIndexProperty, 0); imagegrid.VerticalAlignment = VerticalAlignment.Top; imagegrid.HorizontalAlignment = HorizontalAlignment.Center; imagegrid.Margin = new Thickness(0, height1, 0, 0);//(left,top,right,bottom) RowDefinition iRow1 = new RowDefinition(); iRow1.Height = new GridLength(2, GridUnitType.Star); imagegrid.RowDefinitions.Add(iRow1); RowDefinition iRow2 = new RowDefinition(); iRow2.Height = new GridLength(70, GridUnitType.Star); imagegrid.RowDefinitions.Add(iRow2); ScrollViewer sv = new ScrollViewer { CanContentScroll = true, HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden, VerticalScrollBarVisibility = ScrollBarVisibility.Disabled }; for (int i = 0; i < images.Length; i++) { ColumnDefinition columns = new ColumnDefinition(); columns.MinWidth = 100; columns.Width = new GridLength(100, GridUnitType.Star); imagegrid.ColumnDefinitions.Add(columns); BitmapImage bmp = new BitmapImage(); bmp.BeginInit(); bmp.UriSource = new Uri(currentDirectory + "//Media//Images//" + selectedFolder + "//" + System.IO.Path.GetFileName(images[i].ToString()), UriKind.Relative); bmp.CacheOption = BitmapCacheOption.OnLoad; Debug.WriteLine("Loading: " + currentDirectory + "//Media//Images//" + selectedFolder + "//" + System.IO.Path.GetFileName(images[i].ToString())); bmp.EndInit(); Image img = new Image(); img.Name = System.IO.Path.GetFileNameWithoutExtension(images[i].ToString()); img.Source = bmp; img.VerticalAlignment = VerticalAlignment.Center; img.HorizontalAlignment = HorizontalAlignment.Center; img.TouchDown += addImagetoScreen; img.Width = 94; img.Stretch = Stretch.Uniform; img.SetValue(Canvas.ZIndexProperty, 0); this.RegisterName(img.Name, img); Border border = new Border(); border.SetResourceReference(Control.BackgroundProperty, "MenuSelected"); border.SetValue(Canvas.ZIndexProperty, 0); Grid.SetRow(border, 0); Grid.SetColumn(border, i); Grid.SetRow(img, 1); Grid.SetColumn(img, i); imagegrid.Children.Add(border); imagegrid.Children.Add(img); } sv.Content = imagegrid; sv.SetValue(Canvas.ZIndexProperty, 0); hostgrid.Children.Add(sv); mainGrid.Children.Add(hostgrid); } 

De acuerdo. Borre todo su código y comience de nuevo.

Así es como lo haces en WPF:

                            

Código detrás:

 public partial class ItemsControlSample2 : Window { public ItemsControlSample2() { InitializeComponent(); //Make sure you change this path to a valid path in your PC where you have JPG files var path = @"F:\Media\Images\My Drums"; var images = Directory.GetFiles(path,"*.jpg") .Select(x => new ImageViewModel() { Path = x, }); DataContext = images.ToList(); } } 

Elemento de datos:

 public class ImageViewModel : INotifyPropertyChanged { private bool _isLoading; public bool IsLoading { get { return _isLoading; } set { _isLoading = value; OnPropertyChanged("IsLoading"); } } private ImageSource _imageSource; public ImageSource ImageSource { get { return _imageSource; } set { _imageSource = value; OnPropertyChanged("ImageSource"); } } private string _path; public string Path { get { return _path; } set { _path = value; OnPropertyChanged("Path"); LoadImageAsync(); } } private void LoadImageAsync() { IsLoading = true; var UIScheduler = TaskScheduler.FromCurrentSynchronizationContext(); Task.Factory.StartNew(() => { var bmp = new BitmapImage(); bmp.BeginInit(); bmp.UriSource = new Uri(Path, UriKind.Relative); bmp.CacheOption = BitmapCacheOption.OnLoad; bmp.EndInit(); bmp.Freeze(); return bmp; }).ContinueWith(x => { ImageSource = x.Result; IsLoading = false; },UIScheduler); } #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } #endregion } 

Resultado:

enter image description here

  • Observe cómo estoy declarativamente definiendo la UI en XAML en lugar de crearla procesalmente en el código C #. Este es un enfoque mucho más limpio porque permite a WPF hacer su trabajo

  • Estoy usando ItemsControl que es el enfoque ItemsControl para todas las ItemsControl basadas en “elementos” en WPF. Independientemente de su apariencia visual.

  • También observe cómo estoy aprovechando DataBinding para llenar la interfaz de usuario con datos reales y también DataTriggers para crear un comportamiento básico con estado.

    • Mientras se carga la imagen ( IsLoading == true ), Image.Source es nulo y se muestra TextBlock .
    • Cuando la imagen terminó de cargarse ( IsLoading == false ), Image.Source está vinculada a los datos de ViewModel y TextBlock está oculto.
  • Vea cómo estoy usando un WrapPanel para el diseño en lugar de colocar los elementos manualmente. Esto le da un comportamiento “tipo Explorador”. Intente cambiar el tamaño de la ventana para ver los resultados.

  • Le sugiero que lea la documentación vinculada anterior, principalmente las cosas de ItemsControl y también la publicación Rachel’s WPF Mentality .

  • Rocas WPF. Simplemente copie y pegue mi código en un File -> New Project -> WPF Application y vea los resultados usted mismo.

  • Estoy usando C # 4.0 y .Net 4.0, así que no tengo async/await . Debería poder eliminar todo el código basado en Task y reemplazarlo por este nuevo enfoque más limpio y asincrónico.

  • Avíseme si necesita ayuda adicional.

No veo ningún trabajo pesado allí, aparte de quizás cargar imágenes.

Idealmente, debe crear todos los elementos de la interfaz de usuario (border / grid / etc) en el hilo de interfaz de usuario, y solo cargar la imagen en otro hilo.

O mejor aún, en lugar de crear toda esa interfaz de usuario mediante progtwigción, debe utilizar alguna forma de ItemsControl y DataTemplate para generar toda la interfaz de ItemsControl , y cargar todas sus imágenes como sincronización en algún tipo de modelo de vista como una cosa.