WinSoft.se

About Visual Basic and .NET

Browsing Posts in Microsoft .NET

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.203 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

If it is in your control, stay with SQL Server! If it isn’t, this is how you can do it.

When you install the .NET Connector for MySQL, a class library called MySql.Data is installed. You can add a reference to it from the Add Reference dialog. This gives you a new set of classes for accessing a MySQL database. This is what a connection to a database can look like:

Dim ConnectionString As String = "Server=XXX;Port=3306;Database=XXX;Uid=XXX;Pwd=XXX"
Using Cn As New MySql.Data.MySqlClient.MySqlConnection(ConnectionString)
  Cn.Open()

  Console.WriteLine(Cn.State.ToString())

  Cn.Close()
End Using

If you know you ADO.NET, the rest should be a stroll in the park. This code lists keywords in MySQL:

Dim ConnectionString As String = "Server=XXX;Port=3306;Database=mysql;Uid=XXX;Pwd=XXX"
Using Cn As New MySql.Data.MySqlClient.MySqlConnection(ConnectionString)
  Cn.Open()

  Using Cmd As New MySql.Data.MySqlClient.MySqlCommand("SELECT * FROM help_keyword", Cn)
    Dim R As MySql.Data.MySqlClient.MySqlDataReader = Cmd.ExecuteReader()
    While R.Read()
      Console.WriteLine(R.GetString(1))
    End While
    R.Close()
  End Using

  Cn.Close()
End Using

While working on my new Monkeybone control, I was on my way to create an abstract base class that declared a function called Copy for duplicating a drawing instruction. My plan was that each instruction (such as a Line or a Bar) had to provide an implementation of the Copy method, so that each instruction could be duplicated and modified as needed. A quick Google search made me change strategy. I found this function for C# by James Crowley that I quickly rewrote for VB:

Public Function Copy() As Instruction
    Using memStream As New System.IO.MemoryStream()
        Dim C As New System.Runtime.Serialization.StreamingContext( _
            System.Runtime.Serialization.StreamingContextStates.Clone)
        Dim Bf _
           As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(Nothing, C)
        Bf.Serialize(memStream, Me)
        memStream.Seek(0, IO.SeekOrigin.Begin)
        Return CType(Bf.Deserialize(memStream), Instruction)
    End Using
End Function

With this function completely implemented in the in the abstract base class, any serializable object that inherits from the base class, has a function called Copy that creates a new instance if the object and initializes it in the same way as the object that the Copy function is called on. Really cool. Now check this: The Line class inherits from my abstract base class. The base does not contain any data (the line coordinates are stored in the Line class), but the Line class does not contain any hints on how copying is done. But still:

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles MyBase.Load
    Dim L1 As New Monkeybox.Line(10, 20, 30, 40)
    Dim L2 As Monkeybox.Line = CType(L1.Copy(), Monkeybox.Line)
    L1.X1 += 1
    MessageBox.Show(L2.X1.ToString())
End Sub

Yep! L1.X1 is 11 but L2.X1 is still 10. Woow!

This just don’t look right to me:

For Each X As SomeType In ComboBox1.Items
   ...some code...
Next

Items in a combo box is an ObjectCollection, so I usually do the extra step of using an Object as interator in my For Each iterators to avoid the feeling of implisive unboxing, because implisive unboxing is something we should avoid.

There are several times that you need to master bitwise operations in Visual Basic. Imagine the following code in a form. Whenever you move the mouse, it checks if the right mouse button is held down.

Private Sub M(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles Me.MouseDown, Me.MouseMove, Me.MouseUp
	If e.Button = Windows.Forms.MouseButtons.Right Then
		Me.Text = "Yes"
	Else
		Me.Text = "No"
	End If
End Sub

Hold down the right mouse button, and move the mouse around on the form, and see that the text of the form title says “Yes”. But this code fails when you hold down the right button at the same time as another button. Checking the state of the mouse buttons is one situation you need to know bitwise operations. Checking file attributes is another. Also, you might want to develop a function that takes one or more options in one argument. This is the basics:

Imagine a bit pattern. The value 3 has a pattern that ends with 0011. Let’s say that I want to test the last bit in this pattern, then I need a number with the pattern 0001, that is, a bit pattern with zeros on all position except for the position I want to test. The value 1 has a pattern that ends with 0001. The bitwise operator And returns 1 if we have a hit. So 0011 And 0001 equals 0001 (3 And 1 = 1) and 0011 And 0010 equals 0010 (3 and 2 = 2) but 0011 And 0100 equals 0000 (3 And 4 = 0) because the one in 0100 does not hit a one in 0011. So the bug free version of my program could look like this:

Private Sub M(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles Me.MouseDown, Me.MouseMove, Me.MouseUp
	If (e.Button And Windows.Forms.MouseButtons.Right) = _
Windows.Forms.MouseButtons.Right Then
		Me.Text = "Yes"
	Else
		Me.Text = "No"
	End If
End Sub

Or is a bitwise operator that can be used to set bits in a pattern. 0011 Or 0001 equals 0011 because the last bit in the pattern was already set. 0011 Or 1000 equals 1011 (3 Or 8 = 11) because we tell the first bit to become 1.

For this example (also in a form), I have created an enumeration with 8 flags. All values has a bit pattern with one 1 and 0 at all other positions.

Public Enum Flags
	Flag1 = 1
	Flag2 = 2
	Flag3 = 4
	Flag4 = 8
	Flag5 = 16
	Flag6 = 32
	Flag7 = 64
	Flag8 = 128
End Enum

This function checks what flags has been passed in.

Private Sub CheckFlags(ByVal Arg As Flags)
	If (Arg And Flags.Flag1) = Flags.Flag1 Then
		MessageBox.Show("Flag 1 is set")
	End If
	If (Arg And Flags.Flag2) = Flags.Flag2 Then
		MessageBox.Show("Flag 2 is set")
	End If
	If (Arg And Flags.Flag3) = Flags.Flag3 Then
		MessageBox.Show("Flag 3 is set")
	End If
	If (Arg And Flags.Flag4) = Flags.Flag4 Then
		MessageBox.Show("Flag 4 is set")
	End If
	If (Arg And Flags.Flag5) = Flags.Flag5 Then
		MessageBox.Show("Flag 5 is set")
	End If
	If (Arg And Flags.Flag6) = Flags.Flag6 Then
		MessageBox.Show("Flag 6 is set")
	End If
	If (Arg And Flags.Flag7) = Flags.Flag7 Then
		MessageBox.Show("Flag 7 is set")
	End If
	If (Arg And Flags.Flag8) = Flags.Flag8 Then
		MessageBox.Show("Flag 8 is set")
	End If
End Sub

When called like this, the fourth, fifth and eighth message box will be shown:

Me.CheckFlags(Flags.Flag4 Or Flags.Flag5 Or Flags.Flag8)

Today I had to be able to query XML files using XPath and view the result clean and structured. Doing this from within Visual Basic is very easy, so making a tool was an easy option. This is the user interface:

The top pane is used for writing XPath queries. Below, there are three panes. One displays the result as a tree structure, the next displays the attributes of the selected node in the tree view and the last display the source of the selected node.

This is the Click event of the query button:

Private Sub DoExecute(ByVal XPath As String)

    'Store the last executed query in case the user wants to refresh the query.
    Me.LastExecutedQuery = XPath

    'Clear the output textbox.
    txtOutput.Text = ""

    'Clear the elements tree view.
    tvElements.Nodes.Clear()

    'Clear the attribute list.
    lvAttributes.Items.Clear()

    If XPath.Trim() = "" Then

        'If no query is sent in, display the root element.

        'Display it in the tree (using a custom function not shown here).
        Me.AddNode(Dom.DocumentElement, tvElements.Nodes)

        'This is the status bar of the window.
        lblResult.Text = "Elements in result: 1"

    Else

        'The query might fail.
        Try

            Dim Result As Xml.XmlNodeList = Dom.DocumentElement.SelectNodes(XPath)
            If Result Is Nothing Then
                lblResult.Text = "Elements in result: 0"
            Else
                If Result.Count = 0 Then
                    lblResult.Text = "Elements in result: 0"
                Else
                    Me.ResultList = Result
                    lblResult.Text = "Elements in result: " & Result.Count.ToString()

                    Dim ResultNode As TreeNode = tvElements.Nodes.Add(XPath.Trim())
                    ResultNode.Tag = "Query"
                    ResultNode.ImageIndex = 3
                    ResultNode.SelectedImageIndex = 3

                    'Populate the tree structure (using a custom function not shown here).
                    tvElements.BeginUpdate()
                    For Each Xn As Xml.XmlNode In Result
                        Me.AddNode(Xn, ResultNode.Nodes)
                    Next
                    tvElements.EndUpdate()

                    'Expand the root node that contains the result.
                    ResultNode.Expand()

                End If
            End If

        Catch ex As Exception

            'If the query failed, extract the reason from the exception and display it.
            lblResult.Text = "Elements in result: [Error]"
            txtOutput.Text = "XPath failed." & ControlChars.CrLf & ControlChars.CrLf & ex.Message

        End Try
    End If

    'Select the first node. Other panes will be updated in the AfterSelect event of the treeview.
    If tvElements.Nodes.Count > 0 Then
        tvElements.SelectedNode = tvElements.Nodes(0)
        tvElements.SelectedNode.EnsureVisible()
    End If

End Sub

To use this, just open an XML file and click away in the tree view. You can also write XPath queries to browse a subset of the data. If you want to download this early version, the exe file (.NET Framework 3.5) is located here Since 17/1 2010, the program is available from the Programs page.

The BigInteger structure becomes available if you add a reference to the System.Numerics namespace. BigInteger represents a positive or negative integer of any size.  This is great for doing arithmetic calculations with very large numbers, and is one of the problems you had to solve on your own in previous versions of .NET Framework.

After the reference is added, you can create a BigInteger using the New keyword, and an initial value can be passed to the constructor, like so:

Dim X As New System.Numerics.BigInteger(Long.MaxValue)
Console.WriteLine(X.ToString())

To do arithmetic operations, create the BigIntegers you need for the operation, and then call the static (shared) functions of the BigInteger structure to do the calculations. In this case, I call the static function Multiply.

Dim X As New System.Numerics.BigInteger(Long.MaxValue)
Dim Y As New System.Numerics.BigInteger(Long.MaxValue)
Dim Z As System.Numerics.BigInteger = _
     System.Numerics.BigInteger.Multiply(X, Y)
Console.WriteLine(Z.ToString())

Just by adding a few of these lines to the above code, will give you one insanely large number.

Z = System.Numerics.BigInteger.Multiply(Z, Z)
Z = System.Numerics.BigInteger.Multiply(Z, Z)
Z = System.Numerics.BigInteger.Multiply(Z, Z)
Z = System.Numerics.BigInteger.Multiply(Z, Z)

One way to serialize this number in SQL Server could be to store the underlying bytes of the number that the BigInteger instance represents. The BigInteger structure has a member function that returns these bytes as a byte array called GetByteArray. An existing byte array can be passed to the constructor of the BigInteger to reconstruct the number.

The last time I did this, I needed to build a custom Visual Basic entity generator. After two hours of coding, I had a tiny Windows Forms application that took some connection information and a table name, and returned an entity class with some properties and methods, and a collection class. With initialization, database write-back and all.

From the Management Studio, a call to the sp_help procedure and pass the name of the table you want to receive meta data about, like this:

EXEC sp_help 'dbo.spt_values'

(I still haven’t created any databases on the computer that I am writing this from, so I am grabbing the meta data from the spt_values table in the master database.

The procedure returns a few sets, and the second one contains a list of the table columns.

To call this from Visual Basic, you should know that the procedure is located in the sys namespace, and the parameter that it expects is called @objname. So, if you are using a dataset, the table with index 1 contains the column information. If you are using a data reader, you can call the NextResult function, like so (in a console application):

Using Cn As New SqlClient.SqlConnection("Data Source=.;Initial Catalog=master;Integrated Security=True")
    Cn.Open()
    Using Cmd As New SqlClient.SqlCommand("[sys].[sp_help]", Cn)
        Cmd.CommandType = CommandType.StoredProcedure
        Cmd.Parameters.AddWithValue("@objname", "dbo.spt_values")
        Dim R As SqlClient.SqlDataReader = Cmd.ExecuteReader()
        R.NextResult()
        Dim ColumnNameColumn As Integer = R.GetOrdinal("Column_name")
        Dim ColumnTypeColumn As Integer = R.GetOrdinal("Type")
        Dim ColumnLength As Integer = R.GetOrdinal("Length")
        While R.Read()
            Console.WriteLine(R.GetString(ColumnNameColumn))
            Console.WriteLine(R.GetString(ColumnTypeColumn))
            Console.WriteLine(R.GetInt32(ColumnLength).ToString())
        End While
        R.Close()
    End Using
    Cn.Close()
End Using

If you are using this to create something like a code generator, remember that Length column holds the column size in bytes. This means that a nvarchar (Unicode string columns) with the length set to 10, only can hold 5 characters.

The above code targets .NET Framework 3.5, but it would look exactly the same in the version above and below. It is written in Visual Basic 10 (VBx).

There are some features of the XNA Framework that is unavailable from Visual Basic, but this should not stop you from writing descent games in Visual Basic. On my machine, I have installed XNA Game Studio 3.1 (a game developing environment from Microsoft) and I also have a beta of Visual Studio 2010 that I am going to use. This example will just contain the code necessary to get something on the screen, a sprite floating across.

From VS2010, I am using a regular console application and the target platform for the project is .NET Framework 3.5.

Now I must add two references: Microsoft.Xna.Framework and Microsoft.Xna.Framework.Game. I use version 3.1, the version that got installed when I installed XNA Game Studio 3.1.

The next step is to create the game class. I call my class TestGame. TestGame should inherit from the Microsoft.Xna.Framework.Game class. In here I create a Main method to get the program started, and I select that method to be the starting point for the program in the Project Settings window. This is the code so far:

Public Class TestGame
    Inherits Microsoft.Xna.Framework.Game

    Public Shared Sub Main()

    End Sub

End Class

In the Main method, I create my game (the TestGame class) and from the constructor, a graphics device manager for the game. I use the graphics device manager to set my preferred resolution (800×600) and to switch to fullscreen mode. Note that I want to keep the reference to the graphics device manager as a member of my game class.

Public Class TestGame
    Inherits Microsoft.Xna.Framework.Game

    Private Gfx As Microsoft.Xna.Framework.GraphicsDeviceManager

    Public Shared Sub Main()
        Dim Game As New TestGame()
        Game.Run()
    End Sub

    Public Sub New()
        Me.Gfx = New Microsoft.Xna.Framework.GraphicsDeviceManager(Me)
        Me.Gfx.PreferredBackBufferWidth = 800
        Me.Gfx.PreferredBackBufferHeight = 600
        If Not Me.Gfx.IsFullScreen Then
            Me.Gfx.ToggleFullScreen()
        End If
    End Sub

End Class

The next thing to do is some overrides from the base class. These methods will be overloaded:

Protected Overrides Sub Initialize()
    MyBase.Initialize()
End Sub

Protected Overrides Sub LoadContent()
    MyBase.LoadContent()
End Sub

Protected Overrides Sub UnloadContent()
    MyBase.UnloadContent()
End Sub

Protected Overrides Sub Update(ByVal gameTime As Microsoft.Xna.Framework.GameTime)
    MyBase.Update(gameTime)
End Sub

Protected Overrides Sub Draw(ByVal gameTime As Microsoft.Xna.Framework.GameTime)
    MyBase.Draw(gameTime)
End Sub

Just to make something happen on the screen, I am adding these members:

Private Sb As Microsoft.Xna.Framework.Graphics.SpriteBatch
Private SpriteTexture As Microsoft.Xna.Framework.Graphics.Texture2D
Private SpriteX As Integer = 0
Private SpriteY As Integer = 0

The SpriteBatch will manage my sprites and the Texture2D is the sprite graphics. In the LoadContent function, I will load a sprite from my hard drive.

Protected Overrides Sub LoadContent()
    Me.Sb = New Microsoft.Xna.Framework.Graphics.SpriteBatch(Me.Gfx.GraphicsDevice)
    Me.SpriteTexture = Microsoft.Xna.Framework.Graphics.Texture2D.FromFile(Me.Gfx.GraphicsDevice, _
         "mysprite.png")
    MyBase.LoadContent()
End Sub

The Update function is for changing the game scenery.

Protected Overrides Sub Update(ByVal gameTime As Microsoft.Xna.Framework.GameTime)
    SpriteX += 1
    SpriteY += 1
    MyBase.Update(gameTime)
End Sub

And the Draw function is for screen rendering.

Protected Overrides Sub Draw(ByVal gameTime As Microsoft.Xna.Framework.GameTime)
    Me.Gfx.GraphicsDevice.Clear(Microsoft.Xna.Framework.Graphics.Color.Black)
    Me.Sb.Begin(Microsoft.Xna.Framework.Graphics.SpriteBlendMode.AlphaBlend)
    Me.Sb.Draw(Me.SpriteTexture, New Microsoft.Xna.Framework.Rectangle(Me.SpriteX, Me.SpriteY, 32, 32), _
         Microsoft.Xna.Framework.Graphics.Color.Red)
    Me.Sb.End()
    MyBase.Draw(gameTime)
End Sub

This is the complete code that produces a sprite that floats over the screen in Visual Basic using XNA:

Public Class TestGame
    Inherits Microsoft.Xna.Framework.Game

    Private Gfx As Microsoft.Xna.Framework.GraphicsDeviceManager

    Private Sb As Microsoft.Xna.Framework.Graphics.SpriteBatch
    Private SpriteTexture As Microsoft.Xna.Framework.Graphics.Texture2D
    Private SpriteX As Integer = 0
    Private SpriteY As Integer = 0

    Public Shared Sub Main()
        Dim Game As New TestGame()
        Game.Run()
    End Sub

    Public Sub New()
        Me.Gfx = New Microsoft.Xna.Framework.GraphicsDeviceManager(Me)
        Me.Gfx.PreferredBackBufferWidth = 800
        Me.Gfx.PreferredBackBufferHeight = 600
        If Not Me.Gfx.IsFullScreen Then
            Me.Gfx.ToggleFullScreen()
        End If
    End Sub

    Protected Overrides Sub Initialize()
        MyBase.Initialize()
    End Sub

    Protected Overrides Sub LoadContent()
        Me.Sb = New Microsoft.Xna.Framework.Graphics.SpriteBatch(Me.Gfx.GraphicsDevice)
        Me.SpriteTexture = Microsoft.Xna.Framework.Graphics.Texture2D.FromFile(Me.Gfx.GraphicsDevice, _
              "mysprite.png")
        MyBase.LoadContent()
    End Sub

    Protected Overrides Sub UnloadContent()
        MyBase.UnloadContent()
    End Sub

    Protected Overrides Sub Update(ByVal gameTime As Microsoft.Xna.Framework.GameTime)
        SpriteX += 1
        SpriteY += 1
        MyBase.Update(gameTime)
    End Sub

    Protected Overrides Sub Draw(ByVal gameTime As Microsoft.Xna.Framework.GameTime)
        Me.Gfx.GraphicsDevice.Clear(Microsoft.Xna.Framework.Graphics.Color.Black)
        Me.Sb.Begin(Microsoft.Xna.Framework.Graphics.SpriteBlendMode.AlphaBlend)
        Me.Sb.Draw(Me.SpriteTexture, New Microsoft.Xna.Framework.Rectangle(Me.SpriteX, Me.SpriteY, 32, 32), _
                Microsoft.Xna.Framework.Graphics.Color.Red)
        Me.Sb.End()
        MyBase.Draw(gameTime)
    End Sub

End Class

In Visual Studio 2010, the auto list members feature is much more intelligent than in previous versions. For example, assuming that X is an object with a visible function called DoSomeWork, in earlier versions of Visual Studio, you would have to know that the method started with Do to find it in the list. If you didn’t know that, you would have to scroll through the list of members and try to recall what you are looking for, or search in the object browser. Now, if you type X.Work, Visual Studio lists DoSomeWork as a suggestion, because matching is done within the name, not only from the beginning of the name. Also, you type an acronym of the function name. DSW would match DoSomeWork.

Previous versions of Visual Studio applied a “suggest and complete” policy, which is the default in Visual Studio 2010. This is usually the most effected mode. If you are aiming to call the DoSomeWork function, you can type something like X.DoSo and then what ever character you was going to type thereafter, likely an opening parenthesis. The problem with the “suggest and complete” policy shows up when you’re trying to call a function that isn’t implemented yet. The text editor will force your typing to be a call to something other than you are aiming at, something that already exists, at least if the not-yet-existing member has a name that is a subset of an existing name. I always end up grabbing my computer mouse when this happens. This is one example where the “suggest only” policy can be useful. This mode lets you type whatever you want, because it will not force you to choose from something in the list.

To toggle completion mode, press Ctrl+Alt+Space or look at the IntelliSense sub menu under the Edit menu. Just as the call hierarchy feature, this feature is available no matter what language you’re using, but (at least in the Beta) it only works in the C# editor, not the Visual Basic editor.

Now, there is no reason to have a mouse connected to the computer anymore. Thank you, Microsoft!