- Introduction
- Creating the Sample Project
- Running the Application
- Conclusion
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:
- Select File > New Project.
- Open the Silverlight subfolder under the Visual Basic Projects folder.
- Select the Silverlight Application template, as shown in Figure 1.
- 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).
- 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.
Figure 1 Creating a new Silverlight project with VB 2010.
Figure 2 Setting project properties.
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 1XAML 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 2The 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 3Declaring 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 4Retrieving 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:
- GetAvailableAudioCaptureDevices returns a collection of type AudioCaptureDeviceCollection.
- GetAvailableVideoCaptureDevices returns a collection of type VideoCaptureDeviceCollection.
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 5Starting 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:
- Check whether a device has already been set for capturing. If so, stop the capture.
- 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.
- 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).
- 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.
- 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 6Stopping 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 7This 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.