lunes, 24 de octubre de 2011

Multimedia

Estos últimos días he estado haciendo algunas pruebas con MCI (Multimedia Control Interface), algo nuevo para mi. A pesar de algunos contratiempos iniciales, que me hicieron incluso pensar en utilizar el control Windows Media Player, al final ha merecido la pena perder un poco de tiempo peleando con el MCI. Esta interfaz permite un amplísimo control sobre archivos multimedia (sonido y audio). A continuación, muestro el código y la vista de diseño de un simple reproductor de archivos de sonido o video realizado utilizando el MCI.

Imports System.Text

Public Class Form1
    'Declaramos la función que nos permitirá utilizar los comandos de MCI
    Private Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" _
       (ByVal lpstrCommand As String, ByVal lpstrReturnString As String, ByVal _
       uReturnLength As Integer, ByVal hwndCallback As Integer) As Integer


    Private Sub btnSelCarpeta_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSelFichero.Click
        Dim ofd As OpenFileDialog = New OpenFileDialog()
        Dim fichero As String, longitud As String = "                "
        Dim minutos As Integer, segundos As Integer

        'Obtenemos el fichero que queremos abrir (mostramos sólo archivos de video y audio)
        ofd.Filter = "Audio|*.wav;*.mp3|Video|*.avi;*.wmv;*.mpg"
        If ofd.ShowDialog() = DialogResult.OK Then
            'Establecemos el volumen por defecto
            tbVolumen.Value = 500
            fichero = Chr(34) & ofd.FileName & Chr(34)

            'Nos aseguramos de que el fichero está cerrado antes de abrirlo
            mciSendString("close miFichero", Nothing, 0, 0)
            mciSendString("open " & fichero & " type mpegvideo alias miFichero parent " & pnlPantalla.Handle.ToInt32 & " style child", Nothing, 0, 0)
            mciSendString("set miFichero time format ms", Nothing, 0, 0)

            'Obtenemos la duración de la reproducción en ms e inicializamos el control trackBar y la etiqueta con el tiempo total de reproducción
            mciSendString("status miFichero length", longitud, 128, 0)
            tPosicion.Enabled = True
            tbPosicion.Minimum = 0
            tbPosicion.Maximum = Val(longitud)
            minutos = Math.Floor(Val(longitud) / 60000)
            segundos = (Val(longitud) \ 1000) Mod 60
            lblTiempoTotal.Text = minutos.ToString & ":" & String.Format("{0:00}", segundos).Substring(0, 2)

            'Inicializamos el control de volumen y mostramos el video (si existe) en el panel correspondiente
            mciSendString("setaudio miFichero volume to " & tbVolumen.Value.ToString, Nothing, 0, 0)
            mciSendString("put miFichero window at 0 0 " & pnlPantalla.Width & " " & pnlPantalla.Height, Nothing, 0, 0)

        End If

    End Sub

    'Operaciones a realizar cuando se pulsa el botón reproducir
    Private Sub btnPlay_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPlay.Click
        'Se ajusta el volumen al valor seleccionado en el control correspondiente
        mciSendString("setaudio miFichero volume to " & tbVolumen.Value.ToString, Nothing, 0, 0)
        'Habilitamos el temporizador por si la reproducción había sido pausada antes de pulsar el botón reproducir
        tPosicion.Enabled = True
        'Se reproduce el archivo desde el inicio
        mciSendString("play miFichero from 0", Nothing, 0, 0)

    End Sub

    'Operaciones a realizar cuando se pulsa el botón salir
    Private Sub btnSalir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSalir.Click
        'Cerramos el fichero
        mciSendString("close miFichero", Nothing, 0, 0)
        'Cerramos la aplicación
        Application.Exit()
    End Sub

    'Operaciones a realizar cuando se pulsa el botón pausar
    Private Sub btnPause_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPause.Click
        Dim longitud As String = "                ", formato As String = "                "
        Dim aCars() As Char = {"0"c}

        If tPosicion.Enabled = True Then
            'Si el fichero se está reproduciendo lo pausamos
            tPosicion.Enabled = False
            mciSendString("pause miFichero", Nothing, 0, 0)
        Else
            'Si el fichero no se está reproduciendo lo continuamos reproduciendo
            tPosicion.Enabled = True
            mciSendString("resume miFichero", Nothing, 0, 0)
        End If

    End Sub

    'Operación realizada al variar el control de volumen
    Private Sub tbVolumen_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tbVolumen.Scroll
        mciSendString("setaudio miFichero volume to " & tbVolumen.Value.ToString, Nothing, 0, 0)
    End Sub

    'Operaciones realizadas cuando se pulsa el botón parar
    Private Sub btnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStop.Click
        Dim returnData As String = "                "

        mciSendString("stop miFichero", Nothing, 0, 0)
        lblTiempo.Text = "00:00"
    End Sub


    'Operaciones a realizar al arrancar la aplicación
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'Establecemos el volumen por defecto
        tbVolumen.Value = 500
        'Limpiamos el valor de los tiempos
        lblTiempo.Text = ""
        lblTiempoTotal.Text = ""
    End Sub

    'Operaciones realizadas cada vez que vence el temporizador que comprueba el estado de la reproducción
    Private Sub tPosicion_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tPosicion.Tick
        Dim returnData As String = "                "
        Dim minutos As Integer, segundos As Integer

        'Obtenemos la posición actual de la reproducción y convertimos los milisegundos en minutos y segundos
        mciSendString("status miFichero position", returnData, 128, 0)
        tbPosicion.Value = Val(returnData)
        minutos = Math.Floor(Val(returnData) \ 60000)
        segundos = (Val(returnData) \ 1000) Mod 60
        'Reflejamos la posición actual (en el formato mm:ss) en la etiqueta correspondiente
        lblTiempo.Text = minutos.ToString & ":" & String.Format("{0:00}", segundos).Substring(0, 2)

    End Sub

    'Operaciones que se realizan cuando el usuario varía la posición del control trackBar que refleja la posición actual
    'de la reproducción en curso
    Private Sub tbPosicion_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tbPosicion.Scroll
        Dim posicion As Integer
        Dim returnData As String = "                "

        'Obtenemos la posición seleccionada por el usuario mediante el trackBar (en ms)
        posicion = tbPosicion.Value
        'Indicamos que se inicie la reproducción desde la posición indicada (en ms)
        mciSendString("play miFichero from " & posicion, returnData, 128, 0)
    End Sub

End Class

miércoles, 19 de octubre de 2011

Movimiento

En este ejemplo he decidido mostrar como se pueden utilizar algunos eventos del ratón y del teclado para mover objetos en el formulario. Es una aplicación muy sencilla, y espero que resulte igualmente instructiva. Utilizando únicamente estos eventos se pueden realizar cosas bastante llamativas. También utilizo el objeto Rectangle para controlar el área que ocupan los objetos y saber en todo momento si se solapan entre ellos.


Imports System.Collections

Public Class Form1
    Private dentro As Boolean
    Private rect1 As Rectangle
    Private rect2 As Rectangle
    Private rect3 As Rectangle
    Private rect4 As Rectangle
    Dim x0 As Integer, y0 As Integer
    Dim listaObstaculos As ArrayList

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'Definimos el área ocupada por los obstáculos mostrados en el formulario (se corresponde con el área de los
        'controles label fijos en el formulario, los de color gris)
        rect2 = New Rectangle(300, 300, 50, 50)
        rect3 = New Rectangle(380, 175, 15, 215)
        rect4 = New Rectangle(230, 415, 200, 20)

        'Cargamos los obstaculos en la lista
        listaObstaculos = New ArrayList()
        listaObstaculos.Add(rect2)
        listaObstaculos.Add(rect3)
        listaObstaculos.Add(rect4)

        dentro = False
    End Sub

    Private Sub lbl1_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles lbl1.MouseDown
        'Cuando pulsamos el ratón sobre la etiqueta móvil activamos la variable que controla cuando se va a desplazar la etiqueta
        'siguiendo los movimientos del ratón
        dentro = True
    End Sub

    Private Sub lbl1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles lbl1.MouseUp
        'Desactivamos el seguimiento de los movimientos del ratón por parte de la etiqueta.
        dentro = False
    End Sub

    Private Sub lbl1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles lbl1.MouseMove
        'Si la variable ha sido activada (el ratón fue pulsado sobre la etiqueta), la etiqueta seguirá los movimientos del ratón hasta
        'que se deje de pulsar este (y la variable vuelva a tener valor false).
        If dentro = True Then
            'Obtenemos las coordenadas actuales del ratón (a las que hay que mover la etiqueta)
            x0 = e.X + CType(sender, Label).Left
            y0 = e.Y + CType(sender, Label).Top

            'Obtenemos el área que cubrirá la etiqueta después de desplazarla
            rect1 = New Rectangle(x0, y0, lbl1.Width, lbl1.Height)

            'Si desplazando la etiqueta no se solapa con ninguno de los obstáculos, cambiamos la posición de la etiqueta a la del ratón
            'Si se solapase el área ocupada por la etiqueta con algún obstáculo no se actualizaría la posición de la etiqueta
            If (Rectangle.Intersect(rect1, rect2).IsEmpty = True) And (Rectangle.Intersect(rect1, rect3).IsEmpty = True) And (Rectangle.Intersect(rect1, rect4).IsEmpty = True) Then
                lbl1.Top = e.Y + CType(sender, Label).Top
                lbl1.Left = e.X + CType(sender, Label).Left
            End If
        End If

    End Sub

    'Este evento controla cuando se pulsa una tecla (sólo tenemos en cuenta las pulsaciones de las flechas)
    Private Sub Form1_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
        'Flecha arriba
        If e.KeyCode = Keys.Up Then
            'Obtenemos la posición que tendría la etiqueta después del desplazamiento indicado por la flecha pulsada
            x0 = lbl1.Left
            'Comprobamos el margen inferior

            y0 = lbl1.Top - 1
            If lbl1.Top > 0 Then
                y0 = lbl1.Top - 1
            Else
                y0 = lbl1.Top
            End If

            'Obtenemos el área que ocuparía la etiqueta
            rect1 = New Rectangle(x0, y0, lbl1.Width, lbl1.Height)

            'Si no hay solape se actualiza la posición
            If (lbl1.Top > 0) And Not chocaObstaculo(rect1) Then
                lbl1.Top = y0
            End If

        End If

        'Flecha abajo
        If e.KeyCode = Keys.Down Then
            'Obtenemos la posición que tendría la etiqueta después del desplazamiento indicado por la flecha pulsada
            x0 = lbl1.Left
            'Comprobamos el margen superior (la altura del formulario, con un pequeño ajuste)
            If lbl1.Top + lbl1.Height + 34 < Me.Height Then
                y0 = lbl1.Top + 1
            Else
                y0 = lbl1.Top
            End If

            'Obtenemos el área que ocuparía la etiqueta
            rect1 = New Rectangle(x0, y0, lbl1.Width, lbl1.Height)

            'Si no hay solape se actualiza la posición
            If (lbl1.Top + lbl1.Height + 34 < Me.Height) And Not chocaObstaculo(rect1) Then
                lbl1.Top = y0
            End If

        End If

        'Flecha derecha
        If e.KeyCode = Keys.Right Then
            'Obtenemos la posición que tendría la etiqueta después del desplazamiento indicado por la flecha pulsada
            y0 = lbl1.Top
            'Comprobamos el margen superior (el ancho del formulario, con un pequeño ajuste)
            If lbl1.Left + lbl1.Width + 8 < Me.Width Then
                x0 = lbl1.Left + 1
            Else
                x0 = lbl1.Left
            End If

            'Obtenemos el área que ocuparía la etiqueta
            rect1 = New Rectangle(x0, y0, lbl1.Width, lbl1.Height)

            'Si no hay solape se actualiza la posición
            If (lbl1.Left + lbl1.Width + 8 < Me.Width) And Not chocaObstaculo(rect1) Then
                lbl1.Left = x0
            End If

        End If

        'Flecha izquierda
        If e.KeyCode = Keys.Left Then
            'Obtenemos la posición que tendría la etiqueta después del desplazamiento indicado por la flecha pulsada
            y0 = lbl1.Top
            'Comprobamos el margen inferior
            If lbl1.Left > 0 Then
                x0 = lbl1.Left - 1
            Else
                x0 = lbl1.Left
            End If


            'Obtenemos el área que ocuparía la etiqueta
            rect1 = New Rectangle(x0, y0, lbl1.Width, lbl1.Height)

            'Si no hay solape se actualiza la posición
            If (lbl1.Left > 0) And Not chocaObstaculo(rect1) Then
                lbl1.Left = x0
            End If

        End If
    End Sub

    Private Function chocaObstaculo(ByVal rect As Rectangle) As Boolean
        Dim obstaculo As Rectangle

        'Recorremos todos los obstaculos de la lista
        For Each obj As Object In listaObstaculos
            obstaculo = CType(obj, Rectangle)
            'Si alguno de los obstáculos se solapa con el área ocupada por nuestra etiqueta móvil, este método devolverá
            'un valor
            If Rectangle.Intersect(obstaculo, rect).IsEmpty = False Then
                Return True
            End If
        Next
        Return False
    End Function
End Class

sábado, 15 de octubre de 2011

Simon dice...

Ya ha pasado algún tiempo desde la última entrada. Durante este tiempo he estado programando cosas, aunque no me parecían demasiado adecuadas para el blog (por el tamaño fundamentalmente). Hoy, tenía algún tiempo libre después de finalizar un proyecto para el curso así que decidí realizar una pequeña aplicación expresamente para este blog, para no olvidarme de él. Y lo primero que se me ocurrió ha sido un juego sobradamente conocido, "Simón dice...". Lo conocéis, ¿verdad?. Pues programarlo no es nada complicado. Yo lo hice en la plataforma .NET con Visual Basic. A continuación, podéis ver el código y el diseño del formulario con los nombres de los controles sobreescritos.


Imports System.Threading

Public Class Form1
    Dim esperarJugador As Boolean
    Dim INTERVALO_COLORES As Integer
    Private Const INTERVALO_COLORES_DEFECTO As Integer = 750
    Private Const INTERVALO_PARPADEO As Integer = 100
    Dim NUMERO_ITERACIONES As Integer = 5

    'Dim coloresPulsados As New ArrayList()
    Dim coloresCorrectos As New ArrayList()

    Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
        Dim aleatorio As New Random()
        Dim color As Integer

        coloresCorrectos.Clear()
        For i As Integer = 1 To NUMERO_ITERACIONES
            color = aleatorio.Next(1, 4)
            Select Case color
                Case 1
                    lbl1.BackColor = Drawing.Color.Brown
                    Me.Refresh()
                    Thread.Sleep(INTERVALO_PARPADEO)
                    lbl1.BackColor = Drawing.Color.Red
                    coloresCorrectos.Add(1)
                Case 2
                    lbl2.BackColor = Drawing.Color.Peru
                    Me.Refresh()
                    Thread.Sleep(INTERVALO_PARPADEO)
                    lbl2.BackColor = Drawing.Color.Orange
                    coloresCorrectos.Add(2)
                Case 3
                    lbl3.BackColor = Drawing.Color.YellowGreen
                    Me.Refresh()
                    Thread.Sleep(INTERVALO_PARPADEO)
                    lbl3.BackColor = Drawing.Color.Olive
                    coloresCorrectos.Add(3)
                Case 4
                    lbl4.BackColor = Drawing.Color.Aqua
                    Me.Refresh()
                    Thread.Sleep(INTERVALO_PARPADEO)
                    lbl4.BackColor = Drawing.Color.CadetBlue
                    coloresCorrectos.Add(4)
                Case Else
            End Select

            Me.Refresh()
            Thread.Sleep(INTERVALO_COLORES)
        Next

        'Hay que detectar los colores pulsados por el usuario a partir de este momento, y en cuanto no coincida
        'con las mostradas anteriormente se indica el error.
        esperarJugador = True

    End Sub

    'Inicializamos las variables necesarias

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        esperarJugador = False
    End Sub

    Private Sub lbl1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lbl1.Click
        'Es el turno del jugador
        CType(sender, Label).BackColor = Color.Brown
        Me.Refresh()
        Thread.Sleep(50)
        CType(sender, Label).BackColor = Color.Red
        Me.Refresh()

        If esperarJugador = True Then
            If CType(coloresCorrectos(0), Integer) = 1 Then
                coloresCorrectos.RemoveAt(0)
                If coloresCorrectos.Count = 0 Then
                    esperarJugador = False
                    MessageBox.Show("CONGRATULATIONS!")
                    nudNivel.Value += 1
                End If
            Else
                esperarJugador = False
                MessageBox.Show("GAME OVER!")
            End If
        End If

    End Sub

    Private Sub lbl2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lbl2.Click
        'Es el turno del jugador
        CType(sender, Label).BackColor = Color.Peru
        Me.Refresh()
        Thread.Sleep(50)
        CType(sender, Label).BackColor = Color.Orange
        Me.Refresh()

        If esperarJugador = True Then
            If CType(coloresCorrectos(0), Integer) = 2 Then
                coloresCorrectos.RemoveAt(0)
                If coloresCorrectos.Count = 0 Then
                    esperarJugador = False
                    MessageBox.Show("CONGRATULATIONS!")
                    nudNivel.Value += 1
                End If
            Else
                esperarJugador = False
                MessageBox.Show("GAME OVER!")
            End If
        End If
    End Sub

    Private Sub lbl3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lbl3.Click
        'Es el turno del jugador
        CType(sender, Label).BackColor = Color.YellowGreen
        Me.Refresh()
        Thread.Sleep(50)
        CType(sender, Label).BackColor = Color.Olive
        Me.Refresh()

        If esperarJugador = True Then
            If CType(coloresCorrectos(0), Integer) = 3 Then
                coloresCorrectos.RemoveAt(0)
                If coloresCorrectos.Count = 0 Then
                    esperarJugador = False
                    MessageBox.Show("CONGRATULATIONS!")
                    nudNivel.Value += 1
                End If
            Else
                esperarJugador = False
                MessageBox.Show("GAME OVER!")
            End If
        End If

    End Sub

    Private Sub lbl4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lbl4.Click
        'Es el turno del jugador
        CType(sender, Label).BackColor = Color.Aqua
        Me.Refresh()
        Thread.Sleep(50)
        CType(sender, Label).BackColor = Color.CadetBlue
        Me.Refresh()

        If esperarJugador = True Then
            If CType(coloresCorrectos(0), Integer) = 4 Then
                coloresCorrectos.RemoveAt(0)
                If coloresCorrectos.Count = 0 Then
                    esperarJugador = False
                    MessageBox.Show("CONGRATULATIONS!")
                    nudNivel.Value += 1
                End If
            Else
                esperarJugador = False
                MessageBox.Show("GAME OVER!")
            End If
        End If

    End Sub

    Private Sub nudNivel_ValueChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles nudNivel.ValueChanged
        If esperarJugador = False Then
            NUMERO_ITERACIONES = CInt(nudNivel.Value)
            INTERVALO_COLORES = INTERVALO_COLORES_DEFECTO - NUMERO_ITERACIONES * 50
        End If
    End Sub
End Class