Category: Microsoft .NET

Writing a game using TurboSprite (2/2)

Continued from here.

Now I want to enable shooting. When the user presses the Control key, I create a bullet, sets its properties and adds it to the engine. This code is added to the KeyDown event. I have to modify the KeyUp event, because in the previous version, it unconditionally made the ship stop. Now when more keys are involved, I only want the ship to stop if it is the up or down buttons are released.

This is the new version of the KeyDown event:

   Private Sub Form1_KeyDown(ByVal _
sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
      If e.KeyData = Keys.A Then

         'If user presses "a", move up!
         Me.PlayerMover.DestY = 0
         Me.PlayerMover.SpeedY = -2

      ElseIf e.KeyData = Keys.Z Then

         'If user presses "z", move down!
         Me.PlayerMover.DestY = SpriteSurface1.Height
         Me.PlayerMover.SpeedY = 2

      End If

      If (e.KeyData And Keys.ControlKey) = Keys.ControlKey Then

         'If user presses Control, fire!
         Dim Bullet As New SCG.TurboSprite.PolygonSprite(2, 0, -2, 1, -2, -1)
         Bullet.Position = New Point(Player.Position.X + 15, Player.Position.Y + 9)
         Bullet.Color = Color.Cyan
         SpriteEngineDestination1.AddSprite(Bullet)
         Dim BulletMover As SCG.TurboSprite.DestinationMover = _
SpriteEngineDestination1.GetMover(Bullet)
         BulletMover.Destination = New Point(SpriteSurface1.Width + 5, _
Bullet.Height)
         BulletMover.SpeedX = 4

      End If
   End Sub

This is the new version of the KeyUp event:

   Private Sub Form1_KeyUp(ByVal sender As _
Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp

      'Stop moving, but only if "a" or "z" is released.
      If e.KeyData = Keys.A Or e.KeyCode = Keys.Z Then
         Me.PlayerMover.SpeedY = 0
      End If

   End Sub

Finally, when a bullet reaches its destination at the right end of the screen, I want it deleted. Check the SpriteReachedDestination event in the final code listing below.

When this is all working, I want to add an enemy and use the collision detection to see if it was killed. I did not subclass the PolygonSprite, and this is really something you should do to be able to tag the sprites. The “ugly hack” of this post, must be that I use color to determine if the sprite is a bullet, or the enemy. This is the complete code:

Public Class Form1

   'A reference to the player (created in the Shown event) and the mover.
   Private Player As SCG.TurboSprite.PolygonSprite
   Private PlayerMover As SCG.TurboSprite.DestinationMover

   Private Sub Form1_Shown(ByVal sender _
As Object, ByVal e As System.EventArgs) Handles Me.Shown

      'Set the desired number of frames per second and activate the surface.
      SpriteSurface1.DesiredFPS = 40
      SpriteSurface1.Active = True

      'Create the player and save the reference.
      Me.Player = New SCG.TurboSprite.PolygonSprite(-20, -10, 20, 10, -20, 10)

      'Set player original position.
      Me.Player.Position = New Point(100, 100)

      'Set player color.
      Me.Player.Color = Color.Yellow

      'Add the player to the sprite engine.
      SpriteEngineDestination1.AddSprite(Me.Player)

      'Save a reference to the mover.
      Me.PlayerMover = SpriteEngineDestination1.GetMover(Me.Player)

      'Now, add an ENEMY!
      Dim Enemy As New SCG.TurboSprite.PolygonSprite(-30, 20, 0, -20, 30, 20)
      Enemy.Position = New Point(SpriteSurface1.Width, SpriteSurface1.Height)
      Enemy.Color = Color.Red
      SpriteEngineDestination1.AddSprite(Enemy)
      Dim EnenyMover As SCG.TurboSprite.DestinationMover = _
SpriteEngineDestination1.GetMover(Enemy)
      EnenyMover.Speed = 1
      EnenyMover.Destination = New Point(-50, 100)

   End Sub

   Private Sub Form1_KeyDown(ByVal sender As _
Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
      If e.KeyData = Keys.A Then

         'If user presses "a", move up!
         Me.PlayerMover.DestY = 0
         Me.PlayerMover.SpeedY = -2

      ElseIf e.KeyData = Keys.Z Then

         'If user presses "z", move down!
         Me.PlayerMover.DestY = SpriteSurface1.Height
         Me.PlayerMover.SpeedY = 2

      End If

      If (e.KeyData And Keys.ControlKey) = Keys.ControlKey Then

         'If user presses Control, fire!
         Dim Bullet As New SCG.TurboSprite.PolygonSprite(2, 0, -2, 1, -2, -1)
         Bullet.Position = New Point(Player.Position.X + 15, Player.Position.Y + 9)
         Bullet.Color = Color.Cyan
         SpriteEngineDestination1.AddSprite(Bullet)
         Dim BulletMover As SCG.TurboSprite.DestinationMover = _
SpriteEngineDestination1.GetMover(Bullet)
         BulletMover.Destination = New Point(SpriteSurface1.Width + 5, _
Bullet.Height)
         BulletMover.SpeedX = 4

      End If
   End Sub

   Private Sub Form1_KeyUp(ByVal sender As Object, ByVal _
e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp

      'Stop moving, but only if "a" or "z" is released.
      If e.KeyData = Keys.A Or e.KeyCode = Keys.Z Then
         Me.PlayerMover.SpeedY = 0
      End If

   End Sub

   Private Sub SpriteEngineDestination1_SpriteReachedDestination(ByVal sender As _
Object, ByVal e As SCG.TurboSprite.SpriteEventArgs) Handles SpriteEngineDestination1.SpriteReachedDestination
      'You really should subclass the bullet to be able to
      'tag it as a bullet. I use color. Hack!!!
      Dim P As SCG.TurboSprite.PolygonSprite = CType(e.Sprite, SCG.TurboSprite.PolygonSprite)
      If P.Color = Color.Cyan Then
         P.Kill()
         SpriteEngineDestination1.RemoveSprite(P)
      End If
   End Sub

   Private Sub SpriteSurface1_SpriteCollision(ByVal sender As _
Object, ByVal e As SCG.TurboSprite.SpriteCollisionEventArgs) Handles SpriteSurface1.SpriteCollision
      'Did the player shoot the enemy? Is one a bullet and one the enemy?
      'Again, I use color. Hack!!!
      Dim FirstSprite As SCG.TurboSprite.PolygonSprite = CType(e.Sprite1, _
SCG.TurboSprite.PolygonSprite)
      Dim SecondSprite As SCG.TurboSprite.PolygonSprite = CType(e.Sprite2, _
SCG.TurboSprite.PolygonSprite)
      If FirstSprite.Color = Color.Cyan Then
         'We have a bullet.
         If SecondSprite.Color = Color.Red Then
            'And the enemy.
            SecondSprite.Kill()
            SpriteEngineDestination1.RemoveSprite(SecondSprite)
         End If
      ElseIf FirstSprite.Color = Color.Red Then
         'We have the enemy.
         If SecondSprite.Color = Color.Cyan Then
            'And a bullet.
            FirstSprite.Kill()
            SpriteEngineDestination1.RemoveSprite(FirstSprite)
         End If
      End If
   End Sub

End Class

Again, subclass and add your own properties! Do not use color to determine what sprite you are dealing with. This is the result:

Thank you, TurboSprite!

Writing a game using TurboSprite (1/2)

There are two things to take away from this post. The ease of TurboSprite and the power and speed of GDI+. Of course, you are still supposed to write games using hardware acceleration, but you can do decent desktop games without DirectX, XNA or OpenGL.

In this first part, I will create a player (a spaceship) that can respond to keyboard control. In the second part, the player will be able to shoot and kill enemies.

To start off, I create a project, I add a sprite surface and an sprite engine destination. I connect them to each other by setting the Surface property of the engine. Also, I intend to use collision detection, so I set the DetectCollisionSelf property to True.

On the surface, I set the AutoBlank property to True and the AutoBlankColor property to Black. On the form, I set KeyPreview to True.

Note that so far, all the magic is happening in KeyDown, KeyUp (input feedback) and Shown (initialization).

Public Class Form1

   'A reference to the player (created in the Shown event) and the mover.
   Private Player As SCG.TurboSprite.PolygonSprite
   Private PlayerMover As SCG.TurboSprite.DestinationMover

   Private Sub Form1_Shown(ByVal sender As Object, ByVal e As _
System.EventArgs) Handles Me.Shown

      'Set the desired number of frames per second and activate the surface.
      SpriteSurface1.DesiredFPS = 40
      SpriteSurface1.Active = True

      'Create the player and save the reference.
      Me.Player = New SCG.TurboSprite.PolygonSprite(-20, -10, 20, 10, -20, 10)

      'Set player original position.
      Me.Player.Position = New Point(100, 100)

      'Set player color.
      Me.Player.Color = Color.Yellow

      'Add the player to the sprite engine.
      SpriteEngineDestination1.AddSprite(Me.Player)

      'Save a reference to the mover.
      Me.PlayerMover = SpriteEngineDestination1.GetMover(Me.Player)

   End Sub

   Private Sub Form1_KeyDown(ByVal sender As Object, ByVal _
e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
      If e.KeyData = Keys.A Then

         'If user presses "a", move up!
         Me.PlayerMover.DestY = 0
         Me.PlayerMover.SpeedY = -2

      ElseIf e.KeyData = Keys.Z Then

         'If user presses "z", move down!
         Me.PlayerMover.DestY = SpriteSurface1.Height
         Me.PlayerMover.SpeedY = 2

      End If
   End Sub

   Private Sub Form1_KeyUp(ByVal sender As Object, ByVal _
e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp

      'Stop moving!
      Me.PlayerMover.SpeedY = 0

   End Sub

End Class

Continued here.

TurboSprite

TurboSprite is a software driven (GDI+) sprite engine written by Dion Kurczek in C#. The reason that I’m picking this up, is that it shows (like I have mentioned before) the amazing capabilities and performance of the GDI+ library in .NET. TurboSprite provides a sprite container, bitmap sprites, vector sprites and collision detection.

To be able to use TurboSprite, download and compile it, add the components to your toolbox. Then, add a surface and an engine to a form. Note that you have two engines. One is called SpriteEngine and another is called SpriteEngineDestination. Use the SpriteEngineDestination object, because it extends the SpriteEngine class with the ability to move sprites. You might want to set a few properties to connect the engine to the surface, to tell the engine if you want collision detection and so on. Collision detection is a reasonable cause for using a sprite engine. I use the AutoBlank feature of the surface.

About the moving of the sprites, you can acquire the sprite mover using the GetMover function for the engine (if you use the SpriteEngineDestination), and set the X and Y speed of a sprite. Or you can do as I do in this example below. That is, just set a speed and a destination position. The destination position determines the sprite movement direction.

This code does two thinks (written in the Show event of a regular form). The first line activates the sprite engine so that sprites will move. The rest of the code initializes one vector sprite.

'When the form is loaded, activate the surface
SpriteSurface1.Active = True

'Create a space ship. These points makes a ship.
Dim S As New SCG.TurboSprite.PolygonSprite(0, -10, 5, 10, 0, 5, -5, 10)

'Tell the sprite to rotate.
S.Spin = SCG.TurboSprite.SpinType.Clockwise
S.SpinSpeed = 3

'Start position.
S.Position = New Point(30, 30)

'Add the sprite to the engine, and acquire the sprite mover from the engine.
SpriteEngineDestination1.AddSprite(S)
Dim Mover As SCG.TurboSprite.DestinationMover = SpriteEngineDestination1.GetMover(S)

'Tell the mover about speed and direction.
Mover.Speed = 5
Mover.Destination = New Point(50, 40)

'Tell the mover that the destination really isn't a destination,
'it's just used as a direction.
Mover.StopAtDestination = False

This is the sprite:

Download the engine from the Code Project web site.

Tuples

Tuples in F# is a list of objects with different types, and the tuple itself is strongly typed. You can imagine a simple class or a structure without having to declare the type. This code creates a tuple called myTuple that contains a string, and integer, another string and finally a boolean.

let myTuple=("A", 10, "B", true)

The string representation of a tuple is a string representation of the individual values within parenthesis. This code…

let myTuple=("A", 10, "B", true)
System.Console.WriteLine(myTuple)

…gives you…

(A, 10, B, True)

This code will extract the individual values from the tuple. The output from this code will be 10.

let myTuple=("A", 10, "B", true)
let v1, v2, v3, v4 = myTuple
System.Console.WriteLine(v2)

The types of the values will be the .NET types from the original values in the tuple, v1 is a string, v2 is a 32 bit integer, v3 is also a string and v4 is a boolean. Tuples can be used in many ways, as allowing a single function to return multiple values.

Decompressing text

This post shows how to compress a String to reduce the amount memory it consumes, and this post shows how to use the CompressText function. To be able to read the content of the string, it must be decompressed (or inflated) again. The DecompressText function is one way to do this.

Private Function DecompressText(ByVal B() As Byte) As String
   Dim Result As New System.Text.StringBuilder()
   Using MemStream As New System.IO.MemoryStream(B)
      Using GZStream As New System.IO.Compression.GZipStream(MemStream, _
         IO.Compression.CompressionMode.Decompress)
      Do
         'Note that this makes 1024 bytes in VB.
         Dim Buffer(1023) As Byte
         Dim BytesRead As Integer = GZStream.Read(Buffer, 0, 1024)
         If BytesRead > 0 Then
            Result.Append( _
               System.Text.Encoding.UTF8.GetString(Buffer, 0, BytesRead))
         End If
         If BytesRead < 1024 Then
            Exit Do
         End If
      Loop
      GZStream.Close()
      Return Result.ToString()
      End Using
   End Using
End Function

Now, imagine that B is a byte array returned from the CompressText function. B holds the bytes of a compressed text string. B is passed to the DecompressText function and the function returns the inflated string again. Example:

'Create some text.
Dim S As String = "This is some text that I want to compress. Preferably it's " & _
"a long string loaded from a text file or some XML document."

'Assign the compressed version to the variable B.
Dim B() As Byte = CompressText(S.ToString())

'Decompress it, and display the result.
Dim Decompressed As String = DecompressText(B)
Console.WriteLine(Decompressed)

Have you seen a more elegant way to handle strings in memory than what the .NET Framework offers?

Inserting a value in an identity column

In SQL Server 2008, when a column is set to auto increment (Identity Specification), you are normally not allowed to give that column a value. If you are copying data from one database to another in Visual Basic, perhaps by loading a DataSet object using one connection, and inserting it’s data using another, this can be a problem.

The table option you must set, to be allowed to insert a value in an identity column is called IDENTITY_INSERT. So add this line to your command object to make it work:

SET IDENTITY_INSERT dbo.Products ON;

The following INSERT statement can give a value to the identity column, if the value complies with the rules for that column. The line of code could look something like this:

Using X As New SqlClient.SqlCommand( _
    "SET IDENTITY_INSERT dbo.Products ON; INSERT...

.NET controls in Microsoft Access

Creating ActiveX objects in Visual Basic .NET is easy from Visual Studio 2008. There are a few checkboxes to tick, and a class attribute to add. But if you are doing an ActiveX control for use in let’s say an Microsoft Access application, there are some requirements that must be met, otherwise Access will not list your control. Of course, Microsoft gives you a template for this, and then you´re off. Thanks to Håkan Ståhl for pointing out this template to me.

Create a VB6 Interop UserControl project. The project that is shown on the screenshot below uses the standard name InteropUserControlLibrary1. I already hold a control. To rename the control, rename all three files (the vb file, the manifest and the bitmap). When the vb file is renamed, you will be asked if you want to rename the references too. Do that. Finally, fix the broken references in the manifest file (enter the new name in the progid attribute and the name attribute). Now you´re on your way. You can add a test project to the solution and start coding. The control is registered when you compile the project.

Easy handeling of command line arguments

The CommandLineArguments class is a useful tool when you are building an application that accepts different arguments from the command line, and argument with parameters. The class lets you check if the program was started using a specific argument, and also read the argument to the right, as if it was a parameter.

The ArgumentExists function checks for the presence of an argument and the GetIndexOfArgument function gives you the index of an argument, or negative one if not present. The GetArgumentParameter returns the argument next to a given argument.

Let’s say that you are writing an application called test.exe, and it is started like this:

test.exe -f hello -qq "hello again"

If you pass “-qq” to the GetArgumentParameter function, it will return “hello again“. If you pass “-f” to the ArgumentExists function, it returns true. And if you pass “yeah” to the GetIndexOfArgument function, it returns -1.

Example:

Dim Args As New CommandLineArguments()
Console.WriteLine("Param: " & Args.ArgumentExists("-f").ToString())
Console.ReadLine()

This is the source code for the class:

Public Class CommandLineArguments
    Inherits CollectionBase

    Private mIgnoreCase As Boolean

    Public Sub New()
        Me.New(True)
    End Sub

    Public Sub New(ByVal IgnoreCase As Boolean)
        Dim S() As String = System.Environment.GetCommandLineArgs()
        If S.Length > 1 Then
            For I As Integer = 1 To S.Length - 1
                List.Add(S(I))
            Next
        End If
        Me.mIgnoreCase = IgnoreCase
    End Sub

    Default Public ReadOnly Property Item(ByVal Index As Integer) As String
        Get
            Return CType(List(Index), String)
        End Get
    End Property

    Public Function ArgumentExists(ByVal Arg As String) As Boolean
        For Each S As String In Me
            If String.Compare(S, Arg, Me.mIgnoreCase) = 0 Then
                Return True
            End If
        Next
        Return False
    End Function

    Public Function GetIndexOfArgument(ByVal Arg As String) As Integer
        If Me.Count > 0 Then
            For I As Integer = 0 To Me.Count - 1
                If String.Compare(Me(I), Arg, Me.mIgnoreCase) = 0 Then
                    Return I
                End If
            Next
            Return -1
        Else
            Return -1
        End If
    End Function

    Public Function GetArgumentParameter(ByVal Arg As String) As String
        Dim Index As Integer = Me.GetIndexOfArgument(Arg)
        If Index > -1 Then
            If Index < (Me.Count - 1) Then
                Return Me(Index + 1)
            Else
                Return ""
            End If
        Else
            Return ""
        End If
    End Function

End Class

Monkeybone: The Bar instruction

The Bar instruction, as it appears in a Monkeybone Image (a text file with .mob ending) is a line with at least five parameters. The color of the bar, the X and Y position of the bar, the width and height of the bar. The following lines in a .mob file creates a green rectangular image with a centered yellow box in it:

Clear 200x200 #337733
Bar #ffff00 X:50 Y:50 W:100 H:100

Optionally, you can set the percentage of the fill in either horizontal or vertical direction using HFill or VFill, followed by a percent value. The following code will give a 50 pixel high box. The specified height is 100 (pixels) but VFill:50% tells Monkeybone Viewer to only display half height of the box:

Clear 200x200 #337733
Bar #ffff00 X:50 Y:50 W:100 H:100 VFill:50%

One way of producing an image like this, is to use Notepad to create a text file that ends with .mob instead of .txt. Using Notepad is not the easiest way, and rather impossible if you want to generate dynamic images. For dynamically generate Monkeybone Images, the Monkeybone .NET library or the Monkeybone .NET control can be used.

The Monkeybone .NET library is a class library that contains a writer with the capability of streaming Monkeybone instructions to a file. The following Visual Basic code requires a reference to Monkeybone.dll, and produces a file called “Bars.mob” that can be opened using the Monkeybone Viewer.

Using Sw As New Monkeybone.MonkeyboneWriter("C:\Bars.mob", 200, 200, _
      System.Drawing.Color.FromArgb(51, 119, 51))
   Dim Bar As New Monkeybone.Instructions.Bar(50, 50, 100, 100)
   Bar.BarFillPercent = 50
   Bar.Color = System.Drawing.Color.FromArgb(255, 255, 0)
   Bar.Orientation = Monkeybone.Instructions.Bar.BarOrientation.Vertical
   Sw.WriteLine(Bar)
End Using

If you want your image to display on a form, you can use the Monkeybox control. The Monkeybox control displays your image directly on screen, with an option to save it as a .mob file that can be opened in Monkeybone Viewer. The following code is written in the Shown event of a form that has a Monkeybox control on it, called MBox1. Here, I do not actually save the image, just displays the source in a messagebox. The messagebox will contain exactly the same two instructions as the last example produces. Also, on the screen, you will see a green Monkeybone image with a yellow box in it.

Public Class Form1

    Private Sub Form1_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown
        'Size is normaly set in the designer, but here I set it manually.
        MBox1.Width = 200
        MBox1.Height = 200

        'Set the background color.
        MBox1.BackColor = System.Drawing.Color.FromArgb(51, 119, 51)

        'Add a bar.
        MBox1.AddBar(Color.FromArgb(255, 255, 0), 50, 50, 100, 100, 50, _
           Monkeybox.Bar.BarOrientation.Vertical)

        'Redraw is not trigged by adding and instruction.
        MBox1.Invalidate()

        MessageBox.Show(MBox1.GetSource())
    End Sub

End Class

Filtersearching a Data Grid View

Some applications have that cool search feature that filters on what you search for, and displays the result in real time. The data that you are searching in is loaded, and then the record(s) that matches your search from that set of data is display. Usually, you would do this if you are handling fairly little data, and you would do this on a separate thread from the GUI thread, to prevent that your application get poor responsiveness. This whole process works incredible well with the Data Grid View of the .NET Framework.

The following example requires a form with a textbox (TextBox1) and a data grid view (DataGridView1). To use the application, just type something in the textbox, and watch the grid filtering its records. The Load event of the form populates the grid. The TextChanged of the textbox filters the grid.

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

    'Configure the data grid view.
    DataGridView1.ReadOnly = True
    DataGridView1.Columns.Add("ColA", "Some column")
    DataGridView1.Columns.Add("ColB", "Some other column")
    DataGridView1.AllowUserToAddRows = False
    DataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect
    DataGridView1.Rows.Clear()

    'Add lots of rows.
    For A As Integer = 500 To 999
        Dim Index As Integer = DataGridView1.Rows.Add()
        DataGridView1.Rows(Index).Cells(0).Value = A.ToString("000")
        DataGridView1.Rows(Index).Cells(1).Value = "Hello"
    Next

    'Add two rows with some names in them.
    Dim Temp As Integer = DataGridView1.Rows.Add()
    DataGridView1.Rows(Temp).Cells(0).Value = "Anders"
    DataGridView1.Rows(Temp).Cells(1).Value = "Bengt"

    Temp = DataGridView1.Rows.Add()
    DataGridView1.Rows(Temp).Cells(0).Value = "Calle"
    DataGridView1.Rows(Temp).Cells(1).Value = "David"

    'Again, add lots of rows.
    For A As Integer = 300 To 999
        Dim Index As Integer = DataGridView1.Rows.Add()
        DataGridView1.Rows(Index).Cells(0).Value = A.ToString("000")
        DataGridView1.Rows(Index).Cells(1).Value = "Good bye"
    Next

    'Now we have 1.202 rows in the grid. Do some resizing of the columns.
    DataGridView1.AutoResizeColumns()

End Sub

Private Sub TextBox1_TextChanged(ByVal _
     sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
    If DataGridView1.RowCount > 0 Then

        'What is the user searching for? Remove case information.
        Dim SearchFor As String = TextBox1.Text.ToLower().Trim()

        'If the user has cleared the search box, show all rows.
        If SearchFor = "" Then
            For I As Integer = 0 To DataGridView1.Rows.Count - 1
                DataGridView1.Rows(I).Visible = True
            Next
        Else

            'When the user types something in the textbox, search for
            'rows with cells that holds that value.
            For I As Integer = 0 To DataGridView1.Rows.Count - 1

                'Extract the values from the grid. Remove case information.
                Dim ColA As String = CType(DataGridView1.Rows(I).Cells(0).Value, _
String).ToLower()
                Dim ColB As String = CType(DataGridView1.Rows(I).Cells(1).Value, _
String).ToLower()

                'Show only matching rows.
                If ColA.IndexOf(SearchFor) > -1 OrElse ColB.IndexOf(SearchFor) > -1 Then
                    DataGridView1.Rows(I).Visible = True
                Else
                    DataGridView1.Rows(I).Visible = False
                End If

            Next
        End If

        'Select the first visible row.
        For I As Integer = 0 To DataGridView1.Rows.Count - 1
            If DataGridView1.Rows(I).Visible Then
                DataGridView1.CurrentCell = DataGridView1.Rows(I).Cells(0)
                Exit For
            End If
        Next

    End If
End Sub