Home > Articles > Programming > ASP .NET

  • Print
  • + Share This
From the author of Creating the Sample Project

Creating the Sample Project

To create this sample application, you'll need Microsoft Visual Studio 2010. You can also take advantage of Microsoft Visual Web Developer 2010 Express Edition, which supports Silverlight development. Whatever development environment you choose, the first step is creating a new Visual Basic 2010 project for Silverlight 4:

  1. Select File > New Project.
  2. Open the Silverlight subfolder under the Visual Basic Projects folder.
  3. Select the Silverlight Application template, as shown in Figure 1.
  4. Figure 1 Creating a new Silverlight project with VB 2010.

  5. The name for the new project isn't important here, so I've left that to your choice. Once you click OK, Visual Studio will prompt for some settings such as the Silverlight version or the ASP.NET project type that will host the Silverlight application (see Figure 2 for details).
  6. Figure 2 Setting project properties.

  7. You can leave the default settings unchanged and click OK. After a few seconds, the new project is ready. Visual Studio 2010 should look like the content of Figure 3, running the Silverlight designer.
  8. Figure 3 The IDE shows the Silverlight designer.

The next step is designing the application's user interface, which I'll cover in the next section.

Creating the User Interface

The goal of the sample application is to allow the user to choose a webcam and a microphone from lists of available devices and then start using them inside an appropriate box. Figure 4 shows the final result that we want to reach with the design step.

Figure 4 The user interface.

Since the goal of this article is understanding how to work with devices, not with advanced design features, the layout of the interface is pretty simple. If you're familiar with Silverlight or Windows Presentation Foundation (WPF) programming, the XAML code that I used to create the interface in Figure 4 should be pretty easy to understand (see Listing 1). For the sake of convenience, I wrote useful comments directly inside the code.

Listing 1—XAML code that defines the user interface.

<UserControl x:Class="SLVBWebcamMicrophone.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="400" d:DesignWidth="800">
    <!-- DesignHeight and DesignWidth properties above
    have been increased in order to display correctly in the designer-->

    <!--Dividing the main grid into three columns-->
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200" />
            <ColumnDefinition Width="200" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <!-- This StackPanel is placed in the first column
             and contains controls for selecting an audio device-->
        <StackPanel Grid.Column="0" Orientation="Vertical">
            <TextBlock Text="Available audio devices"/>
            <ListBox Name="AudioDevicesListBox">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <!-- This is data bound to the FriendlyName property
                        of the collection of audio devices-->
                        <TextBlock Text="{Binding FriendlyName}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </StackPanel>

        <!-- This StackPanel is placed in the first column
             and contains controls for selecting a video device-->
        <StackPanel Grid.Column="1">
            <TextBlock Text="Available video devices" />
            <ListBox Name="VideoDevicesListBox" ItemsSource="{Binding}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <!-- This is data bound to the FriendlyName property
                        of the collection of video devices-->
                        <TextBlock Text="{Binding FriendlyName}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </StackPanel>

        <!--This last StackPanel nests the box for showing the
        webcam output and for showing the still images collection-->
        <StackPanel Grid.Column="2">
            <!--This rectangle will show the actual webcam output-->
            <Border BorderBrush="Black" BorderThickness="2">
                <Rectangle Width="320" Height="240" Name="WebcamBox"/>
            </Border>

            <StackPanel Orientation="Horizontal">
                <StackPanel.Resources>
                    <!--Defines a common set of properties for each button-->
                    <Style x:Key="ButtonStyle" TargetType="Button">
                        <Setter Property="Width" Value="80"/>
                        <Setter Property="Height" Value="30"/>
                        <Setter Property="Margin" Value="5"/>
                    </Style>
                </StackPanel.Resources>
                <Button Name="StartButton" Content="Start"  Style="{StaticResource ButtonStyle}" />
                <Button Name="StopButton" Content="Stop"  Style="{StaticResource ButtonStyle}" />
                <Button Name="ShotButton" Content="Get picture"  Style="{StaticResource ButtonStyle}" />
            </StackPanel>
            <!--Displays a list of captured still images, providing the picture and a time stamp
            Both properties are data bound to a collection defined in the StillImage.vb file-->
            <ListBox Name="PicturesBox">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="{Binding Path=CapturedImage}" Stretch="UniformToFill" Width="120" Height="80"/>
                            <TextBlock Text="{Binding Path=TimeStamp}"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </StackPanel>
    </Grid>
</UserControl>

Once the user interface is ready, we can begin writing code for interacting with multimedia devices and implementing some additional custom objects.

Implementing Custom Objects

One of the biggest benefits of the new APIs is the possibility of capturing "still images," which basically are snapshots taken from the webcam. Each snapshot is captured in the form of an object of type WriteableBitmap. This class can store images that can be written and/or updated. The sample application will be able to display captured snapshots inside a ListBox control. Showing only the image isn't very user-friendly, though. Instead, it's useful to provide additional information with the image, such as a time stamp. We'll add a new class to the Silverlight project, naming the class StillImage.vb (see Listing 2). This class will store both the image and the time stamp.

Listing 2—The StillImage custom class stores an image and a time stamp.

Imports System.Windows.Media.Imaging

Public Class StillImage

    Public Property CapturedImage As WriteableBitmap
    Public Property TimeStamp As String
End Class

The code uses a feature new in Visual Basic 2010: auto-implemented properties. The Imports directive allows shortening the call to the WriteableBitmap class, which is exposed by the System.Windows.Media.Imaging namespace.

Now we can write the code for enabling the devices.

Enabling Devices

Basically, we need to associate some actions to controls in the user interface (such as buttons), but we also need some places to store the results of capturing from devices. All this work will be done inside the MainPage.xaml.vb code-behind file. After opening the file, we need to add some code for importing namespaces and declaring two variables[md]one for working with devices, and one for storing a collection of still images (see Listing 3).

Listing 3—Declaring objects for storing information.

Imports System.Collections.ObjectModel
Imports System.Windows.Media.Imaging
Imports System.IO

Partial Public Class MainPage
    Inherits UserControl

    Private WithEvents capSource As CaptureSource
    Private capturedImages As New ObservableCollection(Of StillImage)

    Public Sub New()
        InitializeComponent()
    End Sub
End Class

The capSource variable of type CaptureSource will provide interaction with devices. (Later I'll explain why it has been marked as WithEvents.) The capturedImages variable will store a collection of still images taken from the webcam.

Now we need some code to retrieve the list of available devices on the machine. This can be accomplished inside the Page.Loaded event handler for the application's main page (see Listing 4).

Listing 4—Retrieving the list of available devices.

Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
    'Retrieves the list of available audio recording devices
    Me.AudioDevicesListBox.ItemsSource = CaptureDeviceConfiguration.GetAvailableAudioCaptureDevices()

    'Retrieves the list of available video devices
    Me.VideoDevicesListBox.ItemsSource = CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices()

    'Creates a new capture source
    Me.capSource = New CaptureSource()

    'Binds the "still images" collection to the appropriate ListBox
    Me.PicturesBox.ItemsSource = capturedImages
End Sub

Notice how the CaptureDeviceConfiguration class exposes two shared methods that return collections of available devices:

Both results are assigned to the ItemsSource property of the appropriate ListBox, so that they are data-bound. Then the code creates an instance of the CaptureSource class, and finally data-binds the collection of still images (which is empty at this point in the application's lifetime) to the PicturesBox control.

The next step is declaring event handlers for the buttons. The most complex button is the Start button. Listing 5 demonstrates how to handle the Click event for such a button and how to start capturing. Comments inside the code will help your understanding, but I'll explain some important concepts after you look at the code.

Listing 5—Starting video and audio capture.

Private Sub StartButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles StartButton.Click
    If Me.capSource IsNot Nothing Then
        'If a device is already capturing, then stop it
        Me.capSource.Stop()

        'Set capture devices taking selected items from ListBoxes
        Me.capSource.VideoCaptureDevice = DirectCast(VideoDevicesListBox.SelectedItem, VideoCaptureDevice)
        Me.capSource.AudioCaptureDevice = DirectCast(AudioDevicesListBox.SelectedItem, AudioCaptureDevice)

        'Creates a VideoBrush for showing video output
        Dim webcamBrush As New VideoBrush()
        webcamBrush.SetSource(Me.capSource)
        'Fills the rectangle with the video source
        WebcamBox.Fill = webcamBrush

        'It's a good idea requesting user permission before starting capture
        If CaptureDeviceConfiguration.AllowedDeviceAccess OrElse CaptureDeviceConfiguration.RequestDeviceAccess() Then
            Me.capSource.Start()
        End If
    End If
End Sub

The code in Listing 5 performs the following steps:

  1. Check whether a device has already been set for capturing. If so, stop the capture.
  2. Set the current video and audio capture devices by grabbing the selected items from the audio and video list boxes in the user interface, by casting from ListBox.SelectedItem into the appropriate type.
  3. Create a new instance of the VideoBrush class. The video source for such an object is simply the instance of the CaptureSource class (capSource variable).
  4. Set the new video brush as the filling object for the rectangle, meaning that the rectangle will display the output coming from the video source.
  5. Request permission from the user before starting a capture. When the user gives permission, start the capture via the CaptureSource.Start method.

Listing 6 shows how to handle the Click event for the Stop button. The code is really simple; it just invokes the CaptureSource.Stop method.

Listing 6—Stopping video and audio capture.

Private Sub StopButton_Click(ByVal sender As System.Object,
                             ByVal e As System.Windows.RoutedEventArgs) Handles StopButton.Click
    Me.capSource.Stop()
End Sub

The last step is to provide an event handler for the Get Picture button. Silverlight APIs allow capturing still images asynchronously by invoking a method named CaptureImageAsync. This method raises an event called CaptureImageCompleted that we need to handle in order to add a new still image to the capturedImages collection. The necessity of handling the CaptureImageCompleted event is the reason we declared the capSource variable as WithEvents. This technique keeps us from having to write an explicit AddHandler directive and takes benefits from the Visual Studio instrumentation.

An InvalidOperationException is thrown if you invoke CaptureImageAsync before you start capturing. The code in Listing 7 demonstrates how to handle this last scenario.

Listing 7—This code allows for getting "still images."

Private Sub ShotButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ShotButton.Click
    If Me.capSource IsNot Nothing Then
        Try
            Me.capSource.CaptureImageAsync()

        Catch ex As InvalidOperationException
            MessageBox.Show("You need to start capture first")
        Catch ex As Exception

        End Try

    End If
End Sub

Private Sub capSource_CaptureImageCompleted(ByVal sender As Object,
                                            ByVal e As System.Windows.Media.
                                            CaptureImageCompletedEventArgs) Handles capSource.CaptureImageCompleted
    Me.capturedImages.Add(New StillImage With {.CapturedImage = e.Result, .TimeStamp = Date.Now.ToString})
End Sub

Because the capturedImages collection is data-bound to the PicturesBox control, you'll automatically see the newly captured pictures displayed together with the time stamp. Notice that the event handler signature requires an argument of type CaptureImageCompletedEventArgs, exposing a property named Result that stores the captured image.

  • + Share This
  • 🔖 Save To Your Account