Ejemplo de como se actualiza un control ProgressBar de una ventana WPF con BackgrounWorker

Descripción general:

Ejemplo de como se actualiza un control ProgressBar de un formulario WPF usando un hilo con la clase [BackgroundWorker]

En el moderno XAML, el control ProgressBar no funciona como lo hacia en los formularios. el problema es que el código que mueve el valor del control ProgressBar esta en otro hilo, y por razones de seguridad no permite su actualización, lo que ocurre es que aparentemente no pasa nada hasta que acaba el proceso y entonces aparece el control ProgressBar totalmente lleno

Este comportamiento se puede solucionar de varias formas pero todas ellas implican trabajar con hilos.

En este ejemplo se resuelve ese problema usando la clase BackgroundWorker, pero aunque funciona, esta un poco obsoleta y, tal y como recomienda MSDN hay que usar la clase Task


↑↑↑

Imagen del programa funcionando

Imagen del programa funcionando

↑↑↑

Código XAML de la ventana


<Window  x:Class="WindowPruebaProgressBarConBackgroundWorker"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="Window Prueba ProgressBar Con BackgroundWorker" 
        Height="150" Width="500"   
        ResizeMode="CanResizeWithGrip">

    <Window.Resources>

        <!-- ++++++++++++++++++++++++++++++++++++++++++  -->
        <!-- Comandos personalizados para los botones inferiores [Ejecutar], [Cancelar], [Terminar]   -->

        <!--El comando personalizado para el botón  [Aceptar] -->
        <RoutedUICommand  x:Key="RCBotonEjecutarCmd" 
                         Text="Acciones que se producen al pulsar el botón [Aceptar] del Grupo de botones [Ejecutar], [Cancelar], [Terminar]">
        </RoutedUICommand>
        <!--El comando personalizado para el botón  [Cancelar] -->
        <RoutedUICommand  x:Key="RCBotonCancelarCmd" 
                         Text="Acciones que se producen al pulsar el botón [Cancelar] del Grupo de botones [Ejecutar], [Cancelar], [Terminar]">
        </RoutedUICommand>
    </Window.Resources>

    <Window.CommandBindings>

        <!-- ++++++++++++++++++++++++++++++++++++++++++  -->
        <!-- Los botones inferiores  / Ejecutar / Cancelar / Salir -->

        <!--El comando personalizado para el botón  [Ejecutar] -->
        <CommandBinding  Command="{StaticResource RCBotonEjecutarCmd}"  
                        Executed="BotonEjecutarCommandBinding_Executed"  
                        CanExecute="BotonEjecutarCommandBinding_CanExecute"/>

        <!--El comando personalizado para el botón  [Cancelar] -->
        <CommandBinding  Command="{StaticResource RCBotonCancelarCmd}"  
                        Executed="BotonCancelarCommandBinding_Executed"  
                        CanExecute="BotonCancelarCommandBinding_CanExecute"/>

        <!--El comando personalizado para el botón  [Salir / Terminar] -->
        <CommandBinding  Command="ApplicationCommands.Close"  
                        Executed="BotonCloseCommandBinding_Executed"  
                        CanExecute="BotonCloseCommandBinding_CanExecute"/>

    </Window.CommandBindings>

    <Window.InputBindings>

          <!-- Atajo de teclado para que se ejecute  Botón [Salir]  -->
        <KeyBinding  Key="Esc"  Modifiers=""      Command="ApplicationCommands.Close"  />
        <KeyBinding  Key="F4"   Modifiers="Alt"   Command="ApplicationCommands.Close"  />

        <!-- Atajo de teclado para que se ejecute  Botón [Ejecutar] / [Cancelar] -->
        <KeyBinding  Key="F5"   Modifiers=""      Command="{StaticResource RCBotonEjecutarCmd}"  />
        <KeyBinding  Key="F5"   Modifiers="Ctrl"  Command="{StaticResource RCBotonCancelarCmd}"  />

    </Window.InputBindings>

    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition  Height="auto"/>
            <RowDefinition  Height="*"/>
            <RowDefinition  Height="auto"/>
        </Grid.RowDefinitions>

        
        
        
           <Grid  Grid.Row="0" 
                 x:Name="GridProgressBarConPorcentaje" 
                 HorizontalAlignment="Stretch" VerticalAlignment="Top"  
                 Height="25" 
                 Margin="0,10,0,0" >

            <ProgressBar  x:Name="pbStatus"                                  
                     HorizontalAlignment="Stretch" VerticalAlignment="Stretch"  
                         
                     Value="{Binding Value, 
                             RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" Foreground="#FF09F5BA" >
                <ProgressBar.Background>
                    <ImageBrush/>
                </ProgressBar.Background>
            </ProgressBar>

            <!--<TextBlock   HorizontalAlignment="Center" VerticalAlignment="Center" 
                     TextWrapping="NoWrap" 
                     FontFamily="Consolas"
                     Text="{Binding Value, ElementName=pbStatus, StringFormat=\{0:00.00\} %}" />-->

            <TextBlock  x:Name="TextBoxCronometro" HorizontalAlignment="Center"  VerticalAlignment="Center" 
                     FontFamily="Consolas"
                     TextWrapping="Wrap" Text="Cancelado!"  Width="75" Opacity="0.5"/>

        </Grid>

        <!-- Grid con los tres botones [Ejecutar], [Cancelar],[ Terminar]  -->
        <Grid   Grid.Row="2" 
               Name="GridPanelTresBotonesOkCancelSalirComImagenes" 
               HorizontalAlignment="Center"  
               VerticalAlignment="Bottom" 
               Grid.IsSharedSizeScope="True"
               Margin="0,10,10,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition  x:Uid="ColumnDefinition1"  SharedSizeGroup="ButtonsAceptarCancelar"  Width="120"/>
                <ColumnDefinition  x:Uid="ColumnDefinition3"  SharedSizeGroup="ButtonsAceptarCancelar" />
                <ColumnDefinition  x:Uid="ColumnDefinition3"  SharedSizeGroup="ButtonsAceptarCancelar" />
            </Grid.ColumnDefinitions>

            <Button  Grid.Column="0"
                Name="ButtonEjecutar"  
                Command="{StaticResource RCBotonEjecutarCmd}"  
                IsCancel="False"
                IsEnabled="true" >
                <Button.ToolTip>
                    <StackPanel  Orientation="Vertical">
                        <TextBlock  Text=" Ejecutar" />
                        <TextBlock  Text=" Ejecuta (pone en marcha) el proceso" />
                        <TextBlock  Text=" Atajo de teclado [F5] " />
                    </StackPanel>
                </Button.ToolTip>
                <StackPanel   Orientation="Horizontal"  HorizontalAlignment="Left" VerticalAlignment="Center">
                    <!-- VS2019 Image Library / Status Run -->
                    <Viewbox  Width="16" Height="16" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
                        <Rectangle  Width="16" Height="16">
                            <Rectangle.Fill>
                                <DrawingBrush>
                                    <DrawingBrush.Drawing>
                                        <DrawingGroup>
                                            <DrawingGroup.Children>
                                                <GeometryDrawing  Brush="#FFF6F6F6" Geometry="F1M0,8C0,3.582 3.582,0 8,0 12.418,0 16,3.582 16,8 16,12.418 12.418,16 8,16 3.582,16 0,12.418 0,8" />
                                                <GeometryDrawing  Brush="#FF329932" Geometry="F1M6,12L6,4 12,8z M8,1C4.135,1 1,4.134 1,8 1,11.865 4.135,15 8,15 11.865,15 15,11.865 15,8 15,4.134 11.865,1 8,1" />
                                                <GeometryDrawing  Brush="#FFFFFFFF" Geometry="F1M6,4L12,8 6,12z" />
                                            </DrawingGroup.Children>
                                        </DrawingGroup>
                                    </DrawingBrush.Drawing>
                                </DrawingBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                    </Viewbox>
                    <Label   Content="Ejecutar"    />
                </StackPanel>
            </Button>

            <Button  Grid.Column="1"
                Name="ButtonCancelar"
                Command="{StaticResource RCBotonCancelarCmd}"
                IsCancel="False"
                IsEnabled="True">
                <Button.ToolTip>
                    <StackPanel  Orientation="Vertical">
                        <TextBlock  Text=" Cancelar" />
                        <TextBlock  Text=" Detiene el proceso y vuelve a la situación inicial " />
                        <TextBlock  Text=" Los datos internos de trabajo se pierden" />
                        <TextBlock  Text=" Para detener la ejecución del programa primero" />
                        <TextBlock  Text=" se pulsa el botón [Cancelar] y a continuación [Salir]" />
                        <TextBlock  Text=" Atajo de teclado  [Ctrl + F5] " />
                    </StackPanel>
                </Button.ToolTip>
                <StackPanel   Orientation="Horizontal"  HorizontalAlignment="Left" VerticalAlignment="Center">
                    <!-- VS2019 Image Library / Status Stop -->
                    <Viewbox  Width="16" Height="16" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
                        <Rectangle  Width="16" Height="16">
                            <Rectangle.Fill>
                                <DrawingBrush>
                                    <DrawingBrush.Drawing>
                                        <DrawingGroup>
                                            <DrawingGroup.Children>
                                                <GeometryDrawing  Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
                                                <GeometryDrawing  Brush="#FFF6F6F6" Geometry="F1M0,8C0,3.582 3.582,0 8,0 12.418,0 16,3.582 16,8 16,12.418 12.418,16 8,16 3.582,16 0,12.418 0,8" />
                                                <GeometryDrawing  Brush="#FFE41400" Geometry="F1M11,11L5,11 5,5 11,5z M8,1C4.135,1 1,4.134 1,8 1,11.865 4.135,15 8,15 11.865,15 15,11.865 15,8 15,4.134 11.865,1 8,1" />
                                                <GeometryDrawing  Brush="#FFFFFFFF" Geometry="F1M11,11L5,11 5,5 11,5z" />
                                            </DrawingGroup.Children>
                                        </DrawingGroup>
                                    </DrawingBrush.Drawing>
                                </DrawingBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                    </Viewbox>
                    <Label   Content="Cancelar"    />
                </StackPanel>
            </Button>

            <Button  Grid.Column="2"
                Name="ButtonTerminar"
                Command="ApplicationCommands.Close"  
                IsCancel="True"
                IsEnabled="True" >
                <Button.ToolTip>
                    <StackPanel  Orientation="Vertical">
                        <TextBlock  Text=" Salir" />
                        <TextBlock  Text=" Termina la ejecución del programa " />
                        <TextBlock  Text=" y cierra esta ventana" />
                        <TextBlock  Text=" Atajo de teclado [Alt + F4] " />
                        <TextBlock  Text=" Atajo de teclado [Esc] " />
                    </StackPanel>
                </Button.ToolTip>
                <StackPanel   Orientation="Horizontal"  HorizontalAlignment="Left" VerticalAlignment="Center">
                    <!-- VS2019 Image Library / Exit -->
                    <Viewbox  Width="16" Height="16" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
                        <Rectangle  Width="16" Height="16">
                            <Rectangle.Fill>
                                <DrawingBrush>
                                    <DrawingBrush.Drawing>
                                        <DrawingGroup>
                                            <DrawingGroup.Children>
                                                <GeometryDrawing  Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
                                                <GeometryDrawing  Brush="#FFF6F6F6" Geometry="F1M9.5859,10L8.9999,10.586 8.9999,11.445C8.4099,11.789 7.7319,12 6.9999,12 4.7909,12 2.9999,10.209 2.9999,8 2.9999,5.791 4.7909,4 6.9999,4 7.7319,4 8.4099,4.211 8.9999,4.555L8.9999,5.414 9.5859,6 7.9999,6 7.9999,10z M13.2279,4.813C12.0669,2.551 9.7169,1 6.9999,1 3.1339,1 -9.99999999997669E-05,4.134 -9.99999999997669E-05,8 -9.99999999997669E-05,11.866 3.1339,15 6.9999,15 9.7169,15 12.0669,13.449 13.2279,11.187L15.9999,8.414 15.9999,7.586z" />
                                                <GeometryDrawing  Brush="#FF414141" Geometry="F1M7,13C4.238,13 2,10.762 2,8 2,5.238 4.238,3 7,3 8.118,3 9.14,3.38 9.973,4L11.463,4C10.365,2.775 8.775,2 7,2 3.686,2 1,4.687 1,8 1,11.313 3.686,14 7,14 8.775,14 10.365,13.225 11.463,12L9.973,12C9.14,12.62,8.118,13,7,13" />
                                                <GeometryDrawing  Brush="#FF414141" Geometry="F1M12,5L10,5 12,7 9,7 9,9 12,9 10,11 12,11 15,8z" />
                                            </DrawingGroup.Children>
                                        </DrawingGroup>
                                    </DrawingBrush.Drawing>
                                </DrawingBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                    </Viewbox>
                    <Label   Content="Salir"    />
                </StackPanel>
            </Button>

        </Grid>
        <!-- /Eof Grid con los tres botones [Ejecutar], [Cancelar],[ Terminar]  -->

    </Grid>
</Window>


↑↑↑

Código VB De la calse VO (Value Object)

El código Visual Basic de esta clase esta en este enlace


↑↑↑

Código VB


'--------------------------------------------------------------------------------------------------------------- 
' Este formulario (WPF) es un estudio de como se puede usar un control [Barra de progreso (ProgressBar)] 
' en un formulario WPF y que el control se actualice durante el proceso sin que la barra deje de funcionar 
' modificando el comportamiento básico. 
' En este ejemplo suponemos que representamos un contador de tiempo descendente 
' para ello usamos una [Barra de progreso (ProgressBar)] y un [Cuadro de texto (TextBox)] que indicara con un texto cuanto tiempo falta , 
' y que Para hacerlo mas bonito, hemos incluido dentro del control  (ProgressBar) 
' 
' Para evitar que el formulario se bloquee usamos la clase [System.ComponentModel.BackgroundWorker] 
' Para simular el paso del tiempo usamos un bucle For-Next que contiene el numero de segundos que queremos que dure el proceso 
' Para simular los segundos, dentro de cada iteración del bucle For-Next esperamos exactamente 1 segundo entre cada iteración 
' y para ello usamos la instrucción [System.Threading.Thread.Sleep(1000)] 
' 
' Ademas la función [CalculoTextoCronometro] recibe un numero de segundos (en formato integer) 
' y devuelve una cadena formateada con los minutos y segundos correspondientes 
'  Por ejemplo recibe 200 segundos y devuelve la cadena "03:20" 
'  Es decir 200 segundos son 3 minutos y 20 segundos 
'  Esta cadena se emplea como texto dentro del ProgressBar 
' 
' 
' Eel bucle que cuenta el tiempo, lo cuenta al revés, es decir quedan 50 segundos 49, 48, etc 
' de forma que la representación de los valores en los controles no es igual, 
' la barra de progreso cuenta a delante, (han pasado un segundo, 2,3,etc 
' y el texto muestra el tiempo que queda ( quedan 20 segundos, 19, 18, etc) 
' lo que obliga a algún cambio de valores iniciales. en realidad no es ningún problema y se ve fácilmente si estas avisado 
' 
' El código esta muy (excesivamente) documentado, para que cuando vuelva a necesitarlo, sepa lo que hace cada parte y por que 
'--------------------------------------------------------------------------------------------------------------- 

' necesario para usar [BackgroundWorker] 
Imports System.ComponentModel 

Class WindowPruebaProgressBarConBackgroundWorker 

#Region 

    ''' <summary> 
    ''' El numero de segundos que estará activa la ventana 
    ''' </summary> 
    ''' <value> 
    ''' Un valor integer que representa el numero de segundos que se mostrara la ventana 
    ''' </value> 
    Public Property DuracionSegundos As Integer = 70I 

    ''' <summary> 
    ''' Tiempo que se espera en el bucle para simular un segundo 
    ''' El valor que tiene que tener esta contaste es de 1000 milisegundos 
    ''' (O sea 1 segundo) 
    '''  pongo un valor mas pequeño para pruebas 
    '''  Cambiar a 1000 para esperar un segundo entre iteraciones 
    ''' </summary> 
    Private Const MILISEGUNDOS_DE_ESPERA As Integer = 100 

#End Region 

#Region "Eventos Command de los botones [Aceptar], [Cancelar], [Terminar]" 

    Private localBotonEjecurarActivado As Boolean = True 
    Private localBotonCancelarActivado As Boolean = False 
    Private localBotonTerminarActivado As Boolean = True 

    Private Sub BotonEjecutarCommandBinding_Executed(sender As Object, e As ExecutedRoutedEventArgs) 
        '---------------------------------------------------- 
        'Arrancar la operación asincrónica. 
        Call ArrancarTareaAsicrona() 
    End Sub 

    Private Sub BotonEjecutarCommandBinding_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs) 
        '  e.CanExecute = True 
        e.CanExecute = localBotonEjecurarActivado 
    End Sub 

    Private Sub BotonCancelarCommandBinding_Executed(sender As Object, e As ExecutedRoutedEventArgs) 
        '---------------------------------------------------- 
        'Cancelar la operación asincrónica. 
        Call CancelarTareaAsincrona() 
    End Sub 

    Private Sub BotonCancelarCommandBinding_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs) 
        '  e.CanExecute = True 
        e.CanExecute = localBotonCancelarActivado 
    End Sub 

    Private Sub BotonCloseCommandBinding_Executed(sender As Object, e As ExecutedRoutedEventArgs) 
        '---------------------------------------------------- 
        'cerrar la ventana 
        '---------------------------------------------------- 
        ' Primero destruimos los objetos usados 
        If Not (MyWorker Is Nothing) Then 
            MyWorker.Dispose() 
            MyWorker = Nothing 
        End If 

        '---------------------------------------------------- 
        ' Después cerramos la ventana 
        Me.Close() 
    End Sub 

    Private Sub BotonCloseCommandBinding_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs) 
        '  e.CanExecute = True 
        e.CanExecute = localBotonTerminarActivado 
    End Sub 

#End Region 

#Region "[BackgroundWorker]" 

    ''' <summary> 
    ''' El objeto [BackgroundWorker] 
    ''' </summary> 
    ''' <remarks> 
    '''  Se declara [WithEvents] para poder usar sus eventos 
    '''   - DoWork: realiza el trabajo real de la aplicación 
    '''   - ProgressChanged: actualiza el valor real de la barra de progreso. 
    '''   - RunWorkerCompleted: se dispara al terminar  el progreso, 
    ''' </remarks> 
    Private WithEvents MyWorker As New System.ComponentModel.BackgroundWorker() 

    ''' <summary> 
    ''' Función que coordina los valores de los botones del 
    ''' formulario y arranca la tarea [BackgroundWorker] 
    ''' </summary> 
    Private Sub ArrancarTareaAsicrona() 
        '---------------------------------------------------- 
        ' valores para el progress bar que mueve este código 
        pbStatus.Maximum = DuracionSegundos 
        pbStatus.Minimum = 0 
        pbStatus.Value = 0 

        '-------------------------- 
        ' Situación de los botones 
        localBotonEjecurarActivado = False 
        localBotonCancelarActivado = True 
        localBotonTerminarActivado = False 

        '---------------------------------------------------- 
        ' Establece el valor que indica que BackgroundWorker 
        ' SI puede crear informes sobre las actualizaciones de progreso. 
        MyWorker.WorkerReportsProgress = True 

        '---------------------------------------------------- 
        'Establece el valor que indica BackgroundWorker 
        'SI admite la cancelación asincrónica. 
        MyWorker.WorkerSupportsCancellation = True 

        '---------------------------------------------------- 
        'Inicia la ejecución de una operación en segundo plano. 
        Call MyWorker.RunWorkerAsync() 

    End Sub 

    ''' <summary> 
    ''' Cancela la tarea asíncrona 
    ''' </summary> 
    Private Sub CancelarTareaAsincrona() 
        '---------------------------------------------------- 
        'Cancelar la operación asincrónica. 
        Me.MyWorker.CancelAsync() 

        '---------------------------------------------------- 
        ' Situación de los botones 
        localBotonEjecurarActivado = True 
        localBotonCancelarActivado = False 
        localBotonTerminarActivado = True 

    End Sub 

    '-------------------------------------------------------------------------- 
    ' 
    '  DoWork: realiza el trabajo real de la aplicación (como cargar datos) y, 
    '  siempre que sea posible, informa del progreso a la barra de progreso 
    '  mediante su método ReportProgress. 
    '  Esto desencadenará el evento ProgressChanged. 
    ' 
    'ProgressChanged: actualiza el valor real de la barra de progreso. 
    '  Este evento acepta un parámetro para que pueda pasar 
    '  cantidades variables, dependiendo de la carga de trabajo. 
    ' 
    'RunWorkerCompleted: termina con el progreso, 
    '  suele ocultar la barra de progreso o mostrar un mensaje al usuario. 
    '-------------------------------------------------------------------------- 

    ''' <summary> 
    '''  DoWork: realiza el trabajo real de la aplicación (como cargar datos) y, 
    '''  siempre que sea posible, informa del progreso a la barra de progreso 
    '''  mediante su método ReportProgress. 
    '''  Esto desencadenará el evento ProgressChanged. 
    ''' </summary> 
    Private Sub MyWorker_DoWork(sender As Object, e As DoWorkEventArgs) Handles MyWorker.DoWork 

        For i As Integer = DuracionSegundos To 0 Step -1 

            '------------------------------------------------------ 
            ' Cancela la operación si el usuario la ha cancelado. 
            ' Tenga en cuenta que una llamada a CancelAsync puede haber configurado 
            ' CancelaciónPendiente a verdadero justo después del 
            ' La última invocación de este método sale, por lo que esto 
            ' el código no tendrá la oportunidad de configurar el 
            ' DoWorkEventArgs.Cancel marca a verdadero. Esto significa 
            ' que RunWorkerCompletedEventArgs.Cancelled 
            ' no debe establecerse en verdadero en su RunWorkerCompleted 
            ' controlador de eventos. Esta es una condición de carrera. 
            '------------------------------------------------------ 
            If MyWorker.CancellationPending Then 
                e.Cancel = True 
            Else 
                TryCast(sender, System.ComponentModel.BackgroundWorker).ReportProgress(i) 
                ' -------------------------------------------- 
                ' Esperar un segundo 
                ' System.Threading.Thread.Sleep(1000) 
                ' refactorizado 
                ' -------------------------------------------- 
                System.Threading.Thread.Sleep(MILISEGUNDOS_DE_ESPERA) 
            End If 
        Next 
    End Sub 

    ''' <summary> 
    '''  ProgressChanged: actualiza el valor real de la barra de progreso. 
    '''  Este evento acepta un parámetro para que pueda pasar 
    '''  cantidades variables, dependiendo de la carga de trabajo. 
    ''' </summary> 
    ''' <param name = "sender"></param> 
    ''' <param name = "e"> 
    '''  contiene el valor numérico del numero de segundos con los que se trabaja en este momento 
    '''  este valor se recibe del valor de bucle [For-Next] que esta en el evento [DoWork] 
    ''' </param> 
    ''' <remarks> 
    ''' En este programa en concreto, se calculan dos valores diferentes, 
    ''' y es en esta función donde se calculan los valores auxiliares y 
    ''' donde se actualizan en los controles del formulario XAML 
    ''' ----------------------- 
    ''' A) Por un lado un valor numérico (Obtenido a través del parámetro [e.ProgressPercentage]) 
    '''    que se supone que son el numero de segundos que faltan para que la operación se complete 
    '''    y que es el que se usa para actualizar el [progress bar] con su propiedad Value 
    ''' B) En segundo lugar, ese numero que actualiza la barra de progreso, 
    '''    se pasan a la función [CalculoTextoCronometro] cuyo trabajo es convertir ese número en 
    '''    una cadena de tiempo formateada. 
    '''    Por ejemplo: la función [CalculoTextoCronometro] 
    '''    recibe por parámetros 200 (segundos) y devuelve la cadena "03:20" 
    ''' 
    '''    Esta cadena se utiliza en el texto que esta dentro de la [barra de progreso] 
    '''    y que indica cuanto tiempo falta para que termine la tarea 
    ''' ----------------------- 
    '''    </remarks> 
    Private Sub MyWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles MyWorker.ProgressChanged 
        '------------------------------------------------ 
        ' El objeto VO que mueve la información entre el hilo y el formulario 
        ' No es necesario usar una clase, el código puede incluirse en el formulario 
        ' pero como lo uso también para Task, he decidido incluirlo en una clase 
        Dim ObjDatos As New DatosDevueltos(e.ProgressPercentage) 
        ' A) Valor [Value] de la [barra de progreso] 
        pbStatus.Value = ObjDatos.NumeroDeSegundos 
        ' B) Texto que se muestra con la  [barra de progreso] 
        Me.TextBoxCronometro.Text = ObjDatos.TextoTiempoRestante 
        ' 
    End Sub 

    ''' <summary> 
    '''  RunWorkerCompleted: termina con el progreso, 
    '''  Suele usarse para ocultar la barra de progreso o para mostrar un mensaje al usuario. 
    ''' </summary> 
    Private Sub MyWorker_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles MyWorker.RunWorkerCompleted 

        If e.Cancelled = True Then 
            TextBoxCronometro.Text = "Cancelado!" 
            pbStatus.Value = 0 

        ElseIf e.Error IsNot Nothing Then 
            TextBoxCronometro.Text = "Error: 
        Else 
            TextBoxCronometro.Text = "Hecho!" 
        End If 

        '---------------------------------------------------- 
        ' Situación de los botones 
        localBotonEjecurarActivado = True 
        localBotonCancelarActivado = False 
        localBotonTerminarActivado = True 
    End Sub 

#End Region 
End Class