2010-09-27 18 views
7

De acuerdo, tengo una animación de carga que se ejecuta mientras se llena una DataTable grande para que el usuario sepa que el programa no se ha congelado. Tengo la animación funcionando bien, pero se congela mientras DataTable se actualiza también. ¿Hay alguna manera de tener múltiples subprocesos de interfaz de usuario, para que la animación continúe ejecutándose mientras DataTable está cargando información?WPF cargando animación en un subproceso de interfaz de usuario por separado? (C#)

EDITAR: El código actual está debajo.

private void CreateFileTable() 
{ 
    file_data = new DataSet(); 
    data_table = new DataTable(); 
    file_data.Tables.Add(data_table); 

    DataColumn tempCol = new DataColumn("File Name", typeof(string)); 
    data_table.Columns.Add(tempCol); 

    tempCol = new DataColumn("Ext", typeof(string)); 
    data_table.Columns.Add(tempCol); 

    tempCol = new DataColumn("Size", typeof(string)); 
    data_table.Columns.Add(tempCol); 

    tempCol = new DataColumn("Created", typeof(Label)); 
    data_table.Columns.Add(tempCol); 

    tempCol = new DataColumn("Modified", typeof(Label)); 
    data_table.Columns.Add(tempCol); 

    tempCol = new DataColumn("Accessed", typeof(Label)); 
    data_table.Columns.Add(tempCol); 

    tempCol = new DataColumn("Location", typeof(string)); 
    data_table.Columns.Add(tempCol); 

    File_List.ItemsSource = file_data.Tables[0].DefaultView; 
} 

private void PopulateDirectories(string[] directories) 
{ 
    for (int i = 0; i < directories.Length; i++) 
    { 
     DirectoryInfo tempDirInfo = new DirectoryInfo(directories[i]); 

     bool isSystem = ((tempDirInfo.Attributes & FileAttributes.System) == FileAttributes.System); 

     if (!isSystem) 
     { 
      DataRow tempRow = data_table.NewRow(); 
      tempRow["File Name"] = tempDirInfo.Name; 
      tempRow["Ext"] = ""; 
      tempRow["Size"] = ""; 

      tempLabel = new Label(); 
      tempLabel.Padding = new Thickness(2, 0, 2, 0); 
      tempLabel.Content = tempDirInfo.CreationTime.ToLongDateString() + ", " + tempDirInfo.CreationTime.ToLongTimeString(); 

      tempRow["Created"] = tempLabel; 

      tempLabel = new Label(); 
      tempLabel.Padding = new Thickness(2, 0, 2, 0); 
      tempLabel.Content = tempDirInfo.LastWriteTime.ToLongDateString() + ", " + tempDirInfo.LastWriteTime.ToLongTimeString(); 

      tempRow["Modified"] = tempLabel; 

      tempLabel = new Label(); 
      tempLabel.Padding = new Thickness(2, 0, 2, 0); 
      tempLabel.Content = tempDirInfo.LastAccessTime.ToLongDateString() + ", " + tempDirInfo.LastAccessTime.ToLongTimeString(); 

      tempRow["Accessed"] = tempLabel; 
      tempRow["Location"] = tempDirInfo.FullName; 

      data_table.Rows.Add(tempRow); 
     } 
    } 
} 

private void PopulateFiles(string[] files) 
{ 
    for (int i = 0; i < files.Length; i++) 
    { 
     FileInfo tempFileInfo = new FileInfo(files[i]); 

     bool isSystem = ((File.GetAttributes(files[i]) & FileAttributes.System) == FileAttributes.System); 

     if (!isSystem) 
     { 
      DataRow tempRow = data_table.NewRow(); 
      tempRow["File Name"] = tempFileInfo.Name; 
      tempRow["Ext"] = tempFileInfo.Extension; 

      int fileSize = (int)tempFileInfo.Length; 

      if (fileSize > 1048576) 
      { 
       tempRow["Size"] = "" + fileSize/1048576 + " MB"; 
      } 
      else if (fileSize > 1024) 
      { 
       tempRow["Size"] = "" + fileSize/1024 + " KB"; 
      } 
      else 
      { 
       tempRow["Size"] = "" + fileSize + " B"; 
      } 

      tempLabel = new Label(); 
      tempLabel.Padding = new Thickness(2, 0, 2, 0); 
      tempLabel.Content = tempFileInfo.CreationTime.ToLongDateString() + ", " + tempFileInfo.CreationTime.ToLongTimeString(); 

      tempRow["Created"] = tempLabel; 

      tempLabel = new Label(); 
      tempLabel.Padding = new Thickness(2, 0, 2, 0); 
      tempLabel.Content = tempFileInfo.LastWriteTime.ToLongDateString() + ", " + tempFileInfo.LastWriteTime.ToLongTimeString(); 

      tempRow["Modified"] = tempLabel; 

      tempLabel = new Label(); 
      tempLabel.Padding = new Thickness(2, 0, 2, 0); 
      tempLabel.Content = tempFileInfo.LastAccessTime.ToLongDateString() + ", " + tempFileInfo.LastAccessTime.ToLongTimeString(); 

      tempRow["Accessed"] = tempLabel; 
      tempRow["Location"] = tempFileInfo.DirectoryName; 

      data_table.Rows.Add(tempRow); 
     } 
    } 
} 

private string GetSelectedPath(TreeViewItem selectedNode) 
{ 
    return selectedNode.Tag as string; 
} 

private void PopulateFileList() 
{ 
    PopulateDirectories(Directory.GetDirectories(GetSelectedPath((TreeViewItem)Dir_Tree.SelectedItem))); 
    PopulateFiles(Directory.GetFiles(GetSelectedPath((TreeViewItem)Dir_Tree.SelectedItem))); 
} 

private void UpdateFileList() 
{ 
    LoadingWheel.Visibility = System.Windows.Visibility.Visible; 

    CreateFileTable(); 
    PopulateFileList(); 
    TxtFoundCount.Text = "Files/Folders Found: " + File_List.Items.Count; 

    LoadingWheel.Visibility = System.Windows.Visibility.Hidden; 
} 

He intentado usar el BackgroundWorker y llamando al método UpdateFileList() en su interior, pero no estoy teniendo suerte.

EDITAR: A continuación es mi código para el BackgroundWorker.

private BackgroundWorker bgWorker1; 

private void InitializeBackgroundWorker() 
{ 
    bgWorker1 = new BackgroundWorker(); 
    bgWorker1.DoWork += new DoWorkEventHandler(bgWorker1_DoWork); 
    bgWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker1_RunWorkerCompleted); 
} 

private void bgWorker1_DoWork(object sender, DoWorkEventArgs e) 
{ 
    if (Dispatcher.CheckAccess()) 
    { 
     PopulateFileList(); 
    } 
    else 
    { 
     Dispatcher.Invoke(new Action(() => PopulateFileList())); 
    } 
} 

private void bgWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    TxtFoundCount.Text = "Files/Folders Found: " + File_List.Items.Count; 
    LoadingWheel.Visibility = System.Windows.Visibility.Hidden; 
} 

private void UpdateFileList() 
{ 
    LoadingWheel.Visibility = System.Windows.Visibility.Visible; 
    CreateFileTable(); 
    bgWorker1.RunWorkerAsync(); 
} 

que tienen un evento SelectionChanged en un TreeView que llama InitializeBackgroundWorker() y UpdateFileList(). La lista se carga aún y no aparece ningún error, pero la animación de carga nunca se vuelve visible.

Cualquier ayuda sería muy apreciada.

Gracias.

+0

¿Dónde está su trabajador de fondo? –

+0

Retiré el código de BackgroundWorker porque era más código y no ayudaba. Puedo volver a ponerlo, pero estoy bastante seguro de que lo hice mal. –

+0

En este caso, probablemente llamarías a la función RunWorkerAsync del trabajador en segundo plano de UpdateFileList, después de "encender" la rueda de carga. Mueva las dos llamadas a función a bgWorker_DoWork, la actualización al TextBlock a bgWorker_ProgressChanged, y apague la LoadingWheel a bgWorker_RunWorkerCompleted. Ver respuesta actualizada. –

Respuesta

10

Solo hay un subproceso de interfaz de usuario. Lo que debe hacer es cargar los datos en la DataTable en un hilo diferente.

Si desea mostrar el progreso de la carga de DataTable en el camino (ya sea directamente, a través de un ProgressBar u otro mecanismo), el BackgroundWorker es una manera bastante sencilla de hacerlo.

ACTUALIZACIÓN: ejemplo muy simple trabajador Antecedentes
Aquí es un ejemplo bastante simple. Agrega 100 números aleatorios a una colección, pausando el hilo por un corto tiempo entre cada uno para simular un largo proceso de carga. Simplemente puede cortar y pegar esto en un proyecto de prueba propio para ver si funciona.

Lo que hay que notar es que el trabajo pesado (lo que lleva un tiempo) se realiza en DoWork, mientras que todas las actualizaciones de UI se realizan en ProgressChanged y RunWorkerCompleted. De hecho, se crea una lista separada (números) en el controlador DoWork porque la colección global mNumbers está en el hilo de la interfaz de usuario, y no puede interactuar en el controlador DoWork.

XAML

<Button x:Name="btnGenerateNumbers" 
     Grid.Row="1" 
     HorizontalAlignment="Center" 
     VerticalAlignment="Center" 
     Content="Generate Numbers" /> 

C# de código subyacente

BackgroundWorker bgWorker = new BackgroundWorker(); 
ObservableCollection<int> mNumbers = new ObservableCollection<int>(); 

public Window1() 
{ 
    InitializeComponent(); 
    bgWorker.DoWork += 
     new DoWorkEventHandler(bgWorker_DoWork); 
    bgWorker.ProgressChanged += 
     new ProgressChangedEventHandler(bgWorker_ProgressChanged); 
    bgWorker.RunWorkerCompleted += 
     new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted); 
    bgWorker.WorkerReportsProgress = true; 

    btnGenerateNumbers.Click += (s, e) => UpdateNumbers(); 

    this.DataContext = this; 
} 

void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    progress.Visibility = Visibility.Collapsed; 
    lstItems.Opacity = 1d; 
    btnGenerateNumbers.IsEnabled = true; 
} 

void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
{ 
    List<int> numbers = (List<int>)e.UserState; 
    foreach (int number in numbers) 
    { 
     mNumbers.Add(number); 
    } 

    progress.Value = e.ProgressPercentage; 
} 

void bgWorker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    Random rnd = new Random(); 
    List<int> numbers = new List<int>(10); 

    for (int i = 1; i <= 100; i++) 
    { 
     // Add a random number 
     numbers.Add(rnd.Next());    

     // Sleep from 1/8 of a second to 1 second 
     Thread.Sleep(rnd.Next(125, 1000)); 

     // Every 10 iterations, report progress 
     if ((i % 10) == 0) 
     { 
      bgWorker.ReportProgress(i, numbers.ToList<int>()); 
      numbers.Clear(); 
     } 
    } 
} 

public ObservableCollection<int> NumberItems 
{ 
    get { return mNumbers; } 
} 

private void UpdateNumbers() 
{ 
    btnGenerateNumbers.IsEnabled = false; 
    mNumbers.Clear(); 
    progress.Value = 0; 
    progress.Visibility = Visibility.Visible; 
    lstItems.Opacity = 0.5; 

    bgWorker.RunWorkerAsync(); 
} 
+0

Publiqué mi código actual más arriba. Intenté implementar el BackgroundWorker con él, pero no tuve suerte. Todavía congeló la interfaz de usuario como lo hizo antes. –

+1

Mi respuesta se actualiza con un ejemplo. –

+0

¡¡¡¡¡¡¡imagen perfecta !!! ... Buen trabajo ... – iamCR

3

Escribí un pequeño programa de prueba que muestra el uso de la clase Dispatcher. Solo requiere una ventana WPF y un ListBox con nombre "listBox". Debería ser fácil aplicar esta solución a su problema.

public void Populate() { 
     // for comparison, freezing the ui thread 
     for (int i = 0; i < 1000000; i++) { 
      listBox.Items.Add(i); 
     } 
    } 

    private delegate void AddItemDelegate(int item); 
    public void PopulateAsync() { 
     // create a new thread which is iterating the elements 
     new System.Threading.Thread(new System.Threading.ThreadStart(delegate() { 
      // inside the new thread: iterate the elements 
      for (int i = 0; i < 1000000; i++) { 
       // use the dispatcher to "queue" the insertion of elements into the UI-Thread 
       // DispatcherPriority.Background ensures Animations have a higher Priority and the UI does not freeze 
       // possible enhancement: group the "jobs" to small units to enhance the performance 
       listBox.Dispatcher.Invoke(new AddItemDelegate(delegate(int item) { 
        listBox.Items.Add(item); 
       }), System.Windows.Threading.DispatcherPriority.Background, i); 
      } 
     })).Start(); 
    } 
Cuestiones relacionadas