Timer en vez de Sleep.

Iniciado por Meta, 9 Febrero 2019, 11:10 AM

0 Miembros y 2 Visitantes están viendo este tema.

Meta

Hola:

Quiero hacer un parpadeo de un texto en modo consola. Por ahora solo me sale con Sleep, pero no me gusta este. Aún así dejo un ejemplo de lo que quiero pero está hecho en Sleep.



Código (csharp) [Seleccionar]
using System;
using System.Threading; // No olvidar.

namespace Parpadeo_texto_consola_01_cs
{
    class Program
    {
        static void Main(string[] args)
        {
            // Título de la ventana.
            Console.Title = "Blink";

            // Tamaño ventana consola.
            // X anchura.
            Console.WindowWidth = 16;

            // Y altura.
            Console.WindowHeight = 2;

            // Oculto el cursor.
            Console.CursorVisible = false;

            // Como estamos en un ejemplo, da igual en este caso
            // poner un bucle infinito. Quiero que se muestre el
            // resultado.
            while (true)
            {
                // Posición de la pantalla.
                Console.SetCursorPosition(0, 0);

                // Mostrar texto en pantalla.
                Console.Write("Hola mundo");

                // Retardo de 0.5 segundos. 1000 ml (mili segundos)
                // es igual a 1 segundo.
                Thread.Sleep(500);

                // Posición de la pantalla.
                Console.SetCursorPosition(0, 0);

                // Mostrar espaciones en blanco para borrar texto anterior.
                Console.Write("          ");

                // Retardo 0.3 seg.
                Thread.Sleep(300);
            }
        }
    }
}


Quiero hacer lo mismo, pero con el Timer en modo consola, lo que me cuesta hacerlo. Ya qu el timer pude interrumpir el programa cuando está dentro del temporizador pero con el Sleep, hasta que no acabe el tiempo, el programa se queda como en esclavo, sobre todo en tiempos muy largos.

¿Hay alguna forma de hacerlo en modo consola?

Si se puede hacer el parpadeo en una función y lo llamo cuando quiera, mejor que mejor.

Saludos.
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/

Eleкtro

#1
Cita de: Meta en  9 Febrero 2019, 11:10 AMSi se puede hacer el parpadeo en una función y lo llamo cuando quiera, mejor que mejor.

Mucho pedir en tus posts, pero poco agradecer cuando te ofrecen ayuda...

Lo que pides se puede hacer, pero puede resultarte algo complejo por la necesidad de usar funciones nativas de Windows para obtener la información de los caracteres del búfer de salida de la consola, incluyendo el color de texto; pues si vamos a hacer esto, hagámoslo de la forma más sofisticada posible cubriendo detalles como la preservación del color de texto, ¿no?.
Si prefieres algo más básico (léase: imperfecto) usando un timer y conociendo con antelación el texto exacto y la posición de este, también se puede hacer, tienes muchos ejemplos en Internet sobre como utilizar un timer en una aplicación de consola... y ya sabes asignar la posición del cursor y escribir texto en el búfer, por lo que sinceramente no veo necesario más explicaciones, busca y encontrarás.

En fin, esto de aquí abajo lo hice hace un rato, se trata de una función de uso genérico o reutilizable, es decir, se puede llamar cuando se desee y para parpadear cualquier rango de "celdas" en una fila específica...



En esa imagen se puede apreciar dos parpadeos activos distintos, el primero en la posición 0,0 con un intervalo de 500 ms, y el segundo en la posición 0,1 con un intervalo de 100 ms. Ambos parpadeos se detienen tras 5 segundos haciendo uso de un objeto de tipo CancellationTokenSource que es devuelto al llamar a esta función. Por cierto, y como también se puede apreciar, los colores actuales se preservan al parpadear.

Todo lo necesario lo tienes explicado aquí:

...tan solo es necesario realizar algunas modificaciones a ese código para adaptarlo a la función reutilizable que pides y así conseguir hacer lo mismo que te he mostrado en ese gif. Pero como ya dije, siempre puedes optar por hacer algo más básico y sencillo con el uso de un timer. Es cosa tuya.

Un saludo.








Meta

Muchas gracias mi muy distinguido amigo. ;)

Código (csharp) [Seleccionar]
using System;

namespace Parpadeo_texto_consola_01_cs
{
    class Program
    {
        static void Main(string[] args)
        {
            // Título de la ventana.
            Console.Title = "Blink";

            // Tamaño ventana consola.
            // X anchura.
            Console.WindowWidth = 16;

            // Y altura.
            Console.WindowHeight = 2;

            // Oculto el cursor.
            Console.CursorVisible = false;

            // Como estamos en un ejemplo, da igual en este caso
            // poner un bucle infinito. Quiero que se muestre el
            // resultado.

            int t = 0;
            System.Timers.Timer blink = new System.Timers.Timer(100);
            blink.Enabled = true;
            bool mostrar = true;
            blink.Elapsed += (a, b) =>
            {
                switch (t++)
                {
                    case 0:
                        mostrar = true;
                        break;
                    case 5:
                        mostrar = false;
                        break;
                    case 6:
                        t = -1;
                        break;
                }

                // Posición de la pantalla.
                Console.SetCursorPosition(0, 0);

                // Mostrar texto en pantalla.
                Console.Write(mostrar ? "Hola mundo" : "          ");
            };
            Console.ReadKey();
        }
    }
}


No me gusta el código de arriba, mejor el que dices. Sigo con ello y me alegro el ejemplo del gif que pusiste. ;)
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/

Eleкtro

#3
Cita de: Meta en  9 Febrero 2019, 17:12 PM
No me gusta el código de arriba, mejor el que dices.

Iba a tirar el código pero antes de tirarlo lo comparto aquí. El código está sin optimizar lo suficiente, incompleto (solo faltaría por implementar la preservación del color de fondo, el cual se especifica en los flags de estructura CHAR_INFO) y sin documentar...

Por cierto, lo hice en VB.NET, así que necesitarás usar cualquier conversor de código a C#.

Código (vbnet) [Seleccionar]
Friend NotInheritable Class NativeMethods

   <StructLayout(LayoutKind.Sequential)>
   Friend Structure CHAR_INFO
       <MarshalAs(UnmanagedType.ByValArray, SizeConst:=2)>
       Public charData() As Byte
       Public attributes As Short
   End Structure

   <StructLayout(LayoutKind.Sequential)>
   Friend Structure COORD
       Public X As Short
       Public Y As Short
   End Structure

   <StructLayout(LayoutKind.Sequential)>
   Friend Structure SMALL_RECT
       Public Left As Short
       Public Top As Short
       Public Right As Short
       Public Bottom As Short
   End Structure

   <StructLayout(LayoutKind.Sequential)>
   Friend Structure CONSOLE_SCREEN_BUFFER_INFO
       Public dwSize As COORD
       Public dwCursorPosition As COORD
       Public wAttributes As Short
       Public srWindow As SMALL_RECT
       Public dwMaximumWindowSize As COORD
   End Structure

   <DllImport("kernel32.dll", SetLastError:=True)>
   Friend Shared Function ReadConsoleOutput(ByVal consoleOutput As IntPtr,
                                            ByVal buffer As IntPtr,
                                            ByVal bufferSize As COORD,
                                            ByVal bufferCoord As COORD,
                                            ByRef refReadRegion As SMALL_RECT) As Boolean
   End Function

   <DllImport("kernel32.dll", SetLastError:=True)>
   Friend Shared Function GetStdHandle(ByVal stdHandle As Integer) As IntPtr
   End Function

End Class


Código (vbnet) [Seleccionar]
Public Shared Function ConsoleBlink(ByVal position As Point, ByVal length As Integer, ByVal interval As TimeSpan) As CancellationTokenSource

   Dim cts As New CancellationTokenSource()

   Const STD_OUTPUT_HANDLE As Integer = -11

   Dim t As New Task(
       Sub()

           Dim x As Short = CShort(position.X)
           Dim y As Short = CShort(position.Y)
           Dim width As Short = CShort(length)
           Dim height As Short = 1S
           Dim buffer As IntPtr = Marshal.AllocHGlobal(width * height * Marshal.SizeOf(GetType(CHAR_INFO)))

           Try
               Dim bufferCoord As New COORD()
               Dim bufferSize As New COORD With {
                   .X = width,
                   .Y = height
               }

               Dim rc As New SMALL_RECT With {
                   .Left = x,
                   .Top = y,
                   .Right = (x + width - 1S),
                   .Bottom = (y + height - 1S)
               }

               Dim stdOutHandle As IntPtr = NativeMethods.GetStdHandle(STD_OUTPUT_HANDLE)
               If Not NativeMethods.ReadConsoleOutput(stdOutHandle, buffer, bufferSize, bufferCoord, rc) Then
                   ' Not enough storage is available to process this command' may be raised for buffer size > 64K (see ReadConsoleOutput doc.)
                   Throw New Win32Exception(Marshal.GetLastWin32Error())
               End If

               Dim charInfoList As New List(Of CHAR_INFO)
               Dim ptr As IntPtr = buffer
               For h As Integer = 0 To (height - 1)
                   For w As Integer = 0 To (width - 1)
                       Dim ci As CHAR_INFO = DirectCast(Marshal.PtrToStructure(ptr, GetType(CHAR_INFO)), CHAR_INFO)
                       charInfoList.Add(ci)
                       ptr += Marshal.SizeOf(GetType(CHAR_INFO))
                   Next w
               Next h

               Do Until cts.Token.IsCancellationRequested
                   Dim oldCursorVisible As Boolean = Console.CursorVisible
                   Dim oldPos As New Point(Console.CursorLeft, Console.CursorTop)
                   Dim oldBackColor As ConsoleColor = Console.BackgroundColor
                   Dim oldForeColor As ConsoleColor = Console.ForegroundColor

                   Console.CursorVisible = False
                   Console.SetCursorPosition(position.X, position.Y)
                   Console.Write(New String(" "c, length))
                   Console.CursorVisible = oldCursorVisible
                   Console.SetCursorPosition(oldPos.X, oldPos.Y)
                   Thread.Sleep(interval)
                   Console.CursorVisible = False

                   For i As Integer = 0 To (charInfoList.Count - 1)
                       Dim ci As CHAR_INFO = charInfoList(i)
                       Dim chars As Char() = Console.OutputEncoding.GetChars(ci.charData)
                       Dim color As ConsoleColor = CType(ci.attributes, ConsoleColor)

                       Console.SetCursorPosition(i, position.Y)
                       Console.ForegroundColor = color
                       Console.Write(chars)
                   Next

                   Console.SetCursorPosition(oldPos.X, oldPos.Y)
                   Console.CursorVisible = oldCursorVisible
                   Console.ForegroundColor = oldForeColor
                   Thread.Sleep(interval)
               Loop

           Finally
               Marshal.FreeHGlobal(buffer)

           End Try

       End Sub, cts.Token)

   t.Start()
   Return cts

End Function


Modo de empleo:
Código (vbnet) [Seleccionar]
Public Module Module1

   Public Sub Main()

       Dim tt As New Timers.Timer

       Console.WindowWidth = 20
       Console.WindowHeight = 5
       Console.CursorVisible = False
       Console.ReadKey()

       Dim blinkStr1 As String = "Test"
       Dim blinkPos1 As New Point(0, 0)
       Dim blinkLen1 As Integer = blinkStr1.Length
       Dim blinkTime1 As TimeSpan = TimeSpan.FromMilliseconds(500)
       Dim blinkCt1 As CancellationTokenSource

       Dim blinkStr2 As String = "Test"
       Dim blinkPos2 As New Point(0, 1)
       Dim blinkLen2 As Integer = blinkStr2.Length
       Dim blinkTime2 As TimeSpan = TimeSpan.FromMilliseconds(100)
       Dim blinkCt2 As CancellationTokenSource

       ' Manually write the blink string at the first console row.
       Console.SetCursorPosition(blinkPos1.X, blinkPos1.Y)
       Console.ForegroundColor = ConsoleColor.Yellow
       Console.Write(blinkStr1(0))
       Console.ForegroundColor = ConsoleColor.Cyan
       Console.Write(blinkStr1(1))
       Console.ForegroundColor = ConsoleColor.Green
       Console.Write(blinkStr1(2))
       Console.ForegroundColor = ConsoleColor.Red
       Console.Write(blinkStr1(3))
       Console.ForegroundColor = ConsoleColor.White

       ' Manually write the blink string at the second console row.
       Console.SetCursorPosition(blinkPos2.X, blinkPos2.Y)
       Console.ForegroundColor = ConsoleColor.Green
       Console.Write(blinkStr2)

       ' Start blinking the text and return the task cancellation token sources.
       blinkCt1 = ConsoleBlink(blinkPos1, blinkLen1, blinkTime1)
       blinkCt2 = ConsoleBlink(blinkPos2, blinkLen2, blinkTime2)

       ' Write another string.
       Console.ForegroundColor = ConsoleColor.White
       Console.SetCursorPosition(0, 2)
       Console.Write("Hello World!")

       ' Wait 5 seconds and cancel text blinking.
       Thread.Sleep(TimeSpan.FromSeconds(5))
       Dim t As New Task(
           Sub()
               blinkCt1.Cancel()
               blinkCt2.Cancel()
           End Sub)
       t.Start()

       Console.Read()

   End Sub

End Module








Meta

Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/

Eleкtro

#5
Bueno, al final he decidido mejorarlo para extender la funcionalidad y crear un par de funciones reutilizables, aparte de que ahora el algoritmo preserva tanto el color de texto como el color de fondo, y he corregido un error que había en el primer código con respecto a la posición donde se escriben los caracteres...

Código (vbnet) [Seleccionar]
Public NotInheritable Class Native

   Private Sub New()
   End Sub

   <DllImport("kernel32.dll", SetLastError:=True)>
   Public Shared Function GetStdHandle(ByVal std As ConsoleStd
   ) As IntPtr
   End Function

   <DllImport("kernel32.dll", SetLastError:=True)>
   Public Shared Function ReadConsoleOutput(ByVal consoleOutput As IntPtr,
                                            ByVal buffer As IntPtr,
                                            ByVal bufferSize As ConsoleCoordinate,
                                            ByVal bufferCoord As ConsoleCoordinate,
                                            ByRef refReadRegion As NativeRectangleSmall
   ) As <MarshalAs(UnmanagedType.Bool)> Boolean
   End Function

   <StructLayout(LayoutKind.Sequential)>
   Public Structure CharInfo ' CHAR_INFO
       <MarshalAs(UnmanagedType.ByValArray, SizeConst:=2)>
       Public CharData As Byte()
       <MarshalAs(UnmanagedType.I2)>
       Public Attributes As CharInfoAttributes
   End Structure

   <StructLayout(LayoutKind.Sequential)>
   Public Structure ConsoleCoordinate ' COORD
       Public X As Short
       Public Y As Short
   End Structure

   <StructLayout(LayoutKind.Sequential)>
   Public Structure NativeRectangleSmall ' SMALL_RECT
       Public Left As Short
       Public Top As Short
       Public Right As Short
       Public Bottom As Short
   End Structure

   Public Enum ConsoleStd As Integer
       StandardInput = -10
       StandardOutput = -11
       StandardError = -12
   End Enum

   <Flags>
   Public Enum CharInfoAttributes As Short
       None = &H0S ' Black Color
       BlackColor = CharInfoAttributes.None
       ForeColorBlue = &H1S
       ForeColorGreen = &H2S
       ForeColorRed = &H4S
       ForeColorIntensity = &H8S
       BackColorBlue = &H10S
       BackColorGreen = &H20S
       BackColorRed = &H40S
       BackColorIntensity = &H80S
       LeadingByte = &H100S
       TrailingByte = &H200S
       GridHorizontal = &H400S
       GridVerticalLeft = &H800S
       GridVerticalRight = &H1000S
       ReverseVideo = &H4000S
       Underscore = &H8000S
       ForeColorMask = CharInfoAttributes.ForeColorBlue Or
                       CharInfoAttributes.ForeColorGreen Or
                       CharInfoAttributes.ForeColorRed Or
                       CharInfoAttributes.ForeColorIntensity
       BackColorMask = CharInfoAttributes.BackColorBlue Or
                       CharInfoAttributes.BackColorGreen Or
                       CharInfoAttributes.BackColorRed Or
                       CharInfoAttributes.BackColorIntensity
       ColorMask = CharInfoAttributes.ForeColorMask Or
                   CharInfoAttributes.BackColorMask
   End Enum

End Class


Código (vbnet) [Seleccionar]
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Blinks the specified text for the specified amount of times on the current attached console window.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code>
''' Dim pos As New Point(10, 2)
''' Dim str As String = "Hello World!"
'''
''' Console.SetCursorPosition(pos.X, pos.Y)
''' Console.Write(str)
'''
''' ' Start blinking the text written.
''' Dim len As Integer = str.Length
''' Dim interval As Integer = 500
''' Dim count As Integer = 10
''' ConsoleTextBlink(pos, len, interval, count)
'''
''' ' Terminate program.
''' Console.ReadKey(intercept:=False)
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <param name="position">
''' A <see cref="System.Drawing.Point"/> that indicates the start position of the text.
''' <para></para>
''' <see cref="System.Drawing.Point.X"/> specifies the column, <see cref="System.Drawing.Point.Y"/> the row.
''' </param>
'''
''' <param name="length">
''' The length of the text (or cells) to blink.
''' </param>
'''
''' <param name="interval">
''' The blink interval, in milliseconds.
''' </param>
'''
''' <param name="count">
''' The amount of times to blink the text.
''' </param>
''' ----------------------------------------------------------------------------------------------------
''' <returns>
''' A <see cref="CancellationTokenSource"/> object which you can use it to stop the blink at any time.
''' </returns>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Public Function ConsoleTextBlink(ByVal position As Point, ByVal length As Integer, ByVal interval As Integer, count As Integer) As CancellationTokenSource
    Return InternalConsoleTextBlink(position, length, TimeSpan.FromMilliseconds(interval), count)
End Function

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Blinks the specified text for the specified amount of times on the current attached console window.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code>
''' Dim pos As New Point(10, 2)
''' Dim str As String = "Hello World!"
'''
''' Console.SetCursorPosition(pos.X, pos.Y)
''' Console.Write(str)
'''
''' ' Start blinking the text written.
''' Dim len As Integer = str.Length
''' Dim interval As TimeSpan = TimeSpan.FromMilliseconds(500)
''' Dim count As Integer = 10
''' ConsoleTextBlink(pos, len, interval, count)
'''
''' ' Terminate program.
''' Console.ReadKey(intercept:=False)
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <param name="position">
''' A <see cref="System.Drawing.Point"/> that indicates the start position of the text.
''' <para></para>
''' <see cref="System.Drawing.Point.X"/> specifies the column, <see cref="System.Drawing.Point.Y"/> the row.
''' </param>
'''
''' <param name="length">
''' The length of the text (or cells) to blink.
''' </param>
'''
''' <param name="interval">
''' The blink interval.
''' </param>
'''
''' <param name="count">
''' The amount of times to blink the text.
''' </param>
''' ----------------------------------------------------------------------------------------------------
''' <returns>
''' A <see cref="CancellationTokenSource"/> object which you can use it to stop the blink at any time.
''' </returns>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Public Function ConsoleTextBlink(ByVal position As Point, ByVal length As Integer, ByVal interval As TimeSpan, count As Integer) As CancellationTokenSource
    Return InternalConsoleTextBlink(position, length, interval, count)
End Function

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Blinks the specified text for indefinitely time on the current attached console window.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code>
''' Dim pos As New Point(10, 2)
''' Dim str As String = "Hello World!"
'''
''' Console.SetCursorPosition(pos.X, pos.Y)
''' Console.Write(str)
'''
''' ' Start blinking the text written.
''' Dim len As Integer = str.Length
''' Dim interval As Integer = 500
''' Dim blinkCt As CancellationTokenSource = ConsoleTextBlink(pos, len, interval)
'''
''' ' Stop blinking after 5 seconds elapsed.
''' blinkCt.CancelAfter(5000)
'''
''' ' Terminate program.
''' Console.ReadKey(intercept:=False)
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <param name="position">
''' A <see cref="System.Drawing.Point"/> that indicates the start position of the text.
''' <para></para>
''' <see cref="System.Drawing.Point.X"/> specifies the column, <see cref="System.Drawing.Point.Y"/> the row.
''' </param>
'''
''' <param name="length">
''' The length of the text (or cells) to blink.
''' </param>
'''
''' <param name="interval">
''' The blink interval, in milliseconds.
''' </param>
''' ----------------------------------------------------------------------------------------------------
''' <returns>
''' A <see cref="CancellationTokenSource"/> object which you can use it to stop the blink at any time.
''' </returns>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Public Function ConsoleTextBlink(ByVal position As Point, ByVal length As Integer, ByVal interval As Integer) As CancellationTokenSource
    Return InternalConsoleTextBlink(position, length, TimeSpan.FromMilliseconds(interval), Integer.MaxValue)
End Function

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Blinks the specified text for indefinitely time on the current attached console window.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code>
''' Dim pos As New Point(10, 2)
''' Dim str As String = "Hello World!"
'''
''' Console.SetCursorPosition(pos.X, pos.Y)
''' Console.Write(str)
'''
''' ' Start blinking the text written.
''' Dim len As Integer = str.Length
''' Dim interval As TimeSpan = TimeSpan.FromMilliseconds(500)
''' Dim blinkCt As CancellationTokenSource = ConsoleTextBlink(pos, len, interval)
''' blinkCt.CancelAfter()
'''
''' ' Stop blinking after 5 seconds elapsed.
''' blinkCt.CancelAfter(5000)
'''
''' ' Terminate program.
''' Console.ReadKey(intercept:=False)
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <param name="position">
''' A <see cref="System.Drawing.Point"/> that indicates the start position of the text.
''' <para></para>
''' <see cref="System.Drawing.Point.X"/> specifies the column, <see cref="System.Drawing.Point.Y"/> the row.
''' </param>
'''
''' <param name="length">
''' The length of the text (or cells) to blink.
''' </param>
'''
''' <param name="interval">
''' The blink interval.
''' </param>
''' ----------------------------------------------------------------------------------------------------
''' <returns>
''' A <see cref="CancellationTokenSource"/> object which you can use it to stop the blink at any time.
''' </returns>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Public Function ConsoleTextBlink(ByVal position As Point, ByVal length As Integer, ByVal interval As TimeSpan) As CancellationTokenSource
    Return InternalConsoleTextBlink(position, length, interval, Integer.MaxValue)
End Function

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Blinks the specified text for the specified amount of times on the current attached console window.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <param name="position">
''' A <see cref="System.Drawing.Point"/> that indicates the start position of the text.
''' <para></para>
''' <see cref="System.Drawing.Point.X"/> specifies the column, <see cref="System.Drawing.Point.Y"/> the row.
''' </param>
'''
''' <param name="length">
''' The length of the text (or cells) to blink.
''' </param>
'''
''' <param name="interval">
''' The blink interval.
''' </param>
'''
''' <param name="count">
''' The amount of times to blink the text.
''' </param>
''' ----------------------------------------------------------------------------------------------------
''' <returns>
''' A <see cref="CancellationTokenSource"/> object which you can use it to stop the blink at any time.
''' </returns>
''' ----------------------------------------------------------------------------------------------------
<DebuggerStepThrough>
Private Function InternalConsoleTextBlink(ByVal position As Point, ByVal length As Integer, ByVal interval As TimeSpan, ByVal count As Integer) As CancellationTokenSource

    If (count <= 0) Then
        Throw New ArgumentException(paramName:=NameOf(count), message:="Value greater than 0 is required.")
    End If

    If (interval.TotalMilliseconds <= 0) Then
        Throw New ArgumentException(paramName:=NameOf(interval), message:="Value greater than 0 is required.")
    End If

    Dim cts As New CancellationTokenSource()

    Dim t As New Task(
        Sub()
            Dim x As Short = CShort(position.X)
            Dim y As Short = CShort(position.Y)
            Dim width As Short = CShort(length)
            Dim height As Short = 1S
            Dim buffer As IntPtr = Marshal.AllocHGlobal(width * height * Marshal.SizeOf(GetType(Native.CharInfo)))
            Dim blinkCount As Integer

            Try
                Dim bufferCoord As New Native.ConsoleCoordinate()
                Dim bufferSize As New Native.ConsoleCoordinate With {
                    .X = width,
                    .Y = height
                }

                Dim rc As New Native.NativeRectangleSmall With {
                    .Left = x,
                    .Top = y,
                    .Right = (x + width - 1S),
                    .Bottom = (y + height - 1S)
                }

                Dim stdOutHandle As IntPtr = Native.GetStdHandle(Native.ConsoleStd.StandardOutput)
                If Not Native.ReadConsoleOutput(stdOutHandle, buffer, bufferSize, bufferCoord, rc) Then
                    ' Not enough storage is available to process this command' may be raised for buffer size > 64K (see ReadConsoleOutput doc.)
                    Throw New Win32Exception(Marshal.GetLastWin32Error())
                End If

                Dim charInfoList As New List(Of Native.CharInfo)
                Dim ptr As IntPtr = buffer
                For heightIndex As Integer = 0 To (height - 1)
                    For widthIndex As Integer = 0 To (width - 1)
                        Dim ci As Native.CharInfo = DirectCast(Marshal.PtrToStructure(ptr, GetType(Native.CharInfo)), Native.CharInfo)
                        charInfoList.Add(ci)
                        ptr += Marshal.SizeOf(GetType(Native.CharInfo))
                    Next widthIndex
                Next heightIndex

                Do Until cts.Token.IsCancellationRequested
                    Dim oldCursorVisible As Boolean = Console.CursorVisible
                    Dim oldPos As New Point(Console.CursorLeft, Console.CursorTop)
                    Dim oldBackColor As ConsoleColor = Console.BackgroundColor
                    Dim oldForeColor As ConsoleColor = Console.ForegroundColor

                    Console.CursorVisible = False
                    Console.SetCursorPosition(position.X, position.Y)
                    Console.Write(New String(" "c, length))
                    Console.CursorVisible = oldCursorVisible
                    Console.SetCursorPosition(oldPos.X, oldPos.Y)
                    Thread.Sleep(interval)
                    Console.CursorVisible = False

                    For i As Integer = 0 To (charInfoList.Count - 1)
                        Dim ci As Native.CharInfo = charInfoList(i)
                        Dim chars As Char() = (From c As Char In Console.OutputEncoding.GetChars(ci.CharData)
                                               Where (c <> Nothing)).ToArray()

                        Dim foreColor As ConsoleColor
                        If ((ci.Attributes And Native.CharInfoAttributes.ForeColorMask) <> 0) Then
                            foreColor = CType((CInt(ci.Attributes)) And Not Native.CharInfoAttributes.BackColorMask, ConsoleColor)
                        End If

                        Dim backColor As ConsoleColor
                        If ((ci.Attributes And Native.CharInfoAttributes.BackColorMask) <> 0) Then
                            ' Turn background colors into foreground colors.
                            ' https://referencesource.microsoft.com/#mscorlib/system/console.cs,7a88edaade340cdb
                            backColor = CType((CInt(ci.Attributes)) >> 4, ConsoleColor)
                        End If

                        Console.SetCursorPosition((position.X + i), position.Y)
                        Console.ForegroundColor = foreColor
                        Console.BackgroundColor = backColor
                        Console.Write(chars)
                    Next i

                    Console.SetCursorPosition(oldPos.X, oldPos.Y)
                    Console.CursorVisible = oldCursorVisible
                    Console.ForegroundColor = oldForeColor
                    Console.BackgroundColor = oldBackColor

                    If Interlocked.Increment(blinkCount) = count Then
                        If (cts.Token.CanBeCanceled) Then
                            cts.Cancel()
                        End If
                        Exit Do
                    End If
                    Thread.Sleep(interval)
                Loop

            Finally
                Marshal.FreeHGlobal(buffer)

            End Try

        End Sub, cts.Token)

    t.Start()
    Return cts

End Function


Modo de empleo:

Código (vbnet) [Seleccionar]
Dim pos As New Point(10, 2)
Dim str As String = "Hello World!"

Console.SetCursorPosition(pos.X, pos.Y)
Console.Write(str)

' Start blinking the text written.
Dim len As Integer = str.Length
Dim interval As Integer = 500
Dim count As Integer = 10
ConsoleTextBlink(pos, len, interval, count)

' Terminate program.
Console.ReadKey(intercept:=False)


O bien...
Código (vbnet) [Seleccionar]
Dim pos As New Point(10, 2)
Dim str As String = "Hello World!"

Console.SetCursorPosition(pos.X, pos.Y)
Console.Write(str)

' Start blinking the text written.
Dim len As Integer = str.Length
Dim interval As TimeSpan = TimeSpan.FromMilliseconds(500)
Dim blinkCt As CancellationTokenSource = ConsoleTextBlink(pos, len, interval)

' Stop blinking after 5 seconds elapsed.
blinkCt.CancelAfter(5000)

' Terminate program.
Console.ReadKey(intercept:=False)









Meta

Buenas:

No esperaba que usaras el kernel32.dll.

Voy a examinar.

Muchas gracias. ;)
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/

Eleкtro

Cita de: Meta en 20 Febrero 2019, 21:22 PM
No esperaba que usaras el kernel32.dll.

...¿y eso lo dices por el último código que he compartido?. Sin comentarios. Si cuando digo que una persona no lee nada de lo que le muestran, es por que tengo razón...

Un saludo








Eleкtro

#8
La función InternalConsoleTextBlink que compartí arriba quedaría obsoleta, ya que llamando a la función nativa FillConsoleOutputCharacter se puede simplificar y mejorar mucho su rendimiento y también eliminar algunas imperfecciones visuales que había con el cursor de texto de la consola...

Código (vbnet) [Seleccionar]
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Shared Function FillConsoleOutputCharacter(ByVal hConsoleOutput As IntPtr,
                                                 ByVal character As Char,
                                                 ByVal length As UInteger,
                                                 ByVal writeCoord As ConsoleCoordinate,
                                         <[Out]> ByRef refNumberOfCharsWritten As UInteger
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Shared Function FillConsoleOutputAttribute(ByVal hConsoleOutput As IntPtr,
                                                 ByVal attribute As CharInfoAttributes,
                                                 ByVal length As UInteger,
                                                 ByVal writeCoord As ConsoleCoordinate,
                                         <[Out]> ByRef refNumberOfAttrsWritten As UInteger
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function


El algoritmo final sería este:
Código (vbnet) [Seleccionar]
<DebuggerStepThrough>
Private Function InternalConsoleTextBlink(ByVal position As System.Drawing.Point, ByVal length As Integer, ByVal interval As TimeSpan, ByVal count As Integer) As CancellationTokenSource

   If (count <= 0) Then
       Throw New ArgumentException(paramName:=NameOf(count), message:="Value greater than 0 is required.")
   End If

   If (interval.TotalMilliseconds <= 0) Then
       Throw New ArgumentException(paramName:=NameOf(interval), message:="Value greater than 0 is required.")
   End If

   Dim cts As New CancellationTokenSource()

   Dim t As New Task(
       Sub()
           Dim x As Short = CShort(position.X)
           Dim y As Short = CShort(position.Y)
           Dim width As Short = CShort(length)
           Dim height As Short = 1S
           Dim buffer As IntPtr = Marshal.AllocHGlobal(width * height * Marshal.SizeOf(GetType(CharInfo)))
           Dim blinkCount As Integer

           Try
               Dim bufferCoord As New ConsoleCoordinate()
               Dim bufferSize As New ConsoleCoordinate With {
                   .X = width,
                   .Y = height
               }

               Dim rc As New NativeRectangleSmall With {
                   .Left = x,
                   .Top = y,
                   .Right = (x + width - 1S),
                   .Bottom = (y + height - 1S)
               }

               Dim stdOutHandle As IntPtr = NativeMethods.GetStdHandle(ConsoleStd.StandardOutput)
               If Not NativeMethods.ReadConsoleOutput(stdOutHandle, buffer, bufferSize, bufferCoord, rc) Then
                   ' Not enough storage is available to process this command' may be raised for buffer size > 64K (see ReadConsoleOutput doc.)
                   Throw New Win32Exception(Marshal.GetLastWin32Error())
               End If

               Dim charDict As New Dictionary(Of CharInfo, ConsoleCoordinate)

               Dim ptr As IntPtr = buffer
               For heightIndex As Integer = 0 To (height - 1)
                   For widthIndex As Integer = 0 To (width - 1)
                       Dim ci As CharInfo = DirectCast(Marshal.PtrToStructure(ptr, GetType(CharInfo)), CharInfo)
                        charDict.Add(ci, New ConsoleCoordinate() With {
                                 .X = CShort(position.X + widthIndex),
                                 .Y = CShort(position.Y + heightIndex)})
                       ptr += Marshal.SizeOf(GetType(CharInfo))
                   Next widthIndex
               Next heightIndex

               Do Until cts.Token.IsCancellationRequested
                   ' Remove text.
                   NativeMethods.FillConsoleOutputCharacter(stdOutHandle, " "c, CUInt(length), charDict.Values(0), Nothing)
                   Thread.Sleep(interval)

                   ' Write Text.
                   For Each kv As KeyValuePair(Of CharInfo, ConsoleCoordinate) In charDict
                       Dim c As Char = System.Console.OutputEncoding.GetChars(kv.Key.CharData)(0)
                       NativeMethods.FillConsoleOutputAttribute(stdOutHandle, kv.Key.Attributes, 1, kv.Value, Nothing)
                       NativeMethods.FillConsoleOutputCharacter(stdOutHandle, c, 1, kv.Value, Nothing)
                   Next kv

                   If Interlocked.Increment(blinkCount) = count Then
                       If (cts.Token.CanBeCanceled) Then
                           cts.Cancel()
                       End If
                       Exit Do
                   End If
                   Thread.Sleep(interval)
               Loop

           Finally
               Marshal.FreeHGlobal(buffer)

           End Try

       End Sub, cts.Token)

   t.Start()
   Return cts

End Function


EDITO: código actualizado.

Saludos.








Meta

Buena explicación. ;)

Gracias.
Tutoriales Electrónica y PIC: http://electronica-pic.blogspot.com/