Silverlight 4 – Accessing System Devices with Com Interop, such as a Scanner.

Yesterday I gave a presentation on some new features in Silverlight 4.  During this presentation I mentioned the newly available ability to interoperate with Office applications such as Outlook and Excel though the new ComAutomationFactory that is in Silverlight 4.  Someone in the audience asked the question, “Can I access my scanner”, and of course I said yes; then he said, “how?”, and I said, “I will get back to you”.  Well here I am, with code!

A couple of things to note; this feature requires your application to run as an Out Of Browser (OOB) with elevated permissions. Also, there is no IntelliSense for your COM objects. So make sure you have the documentation to the API you are trying to use. So lets get started.

Creating an OOB Application

First thing you need to do is crack open VS 2010 Beta 2 and create a new Silverlight project. Make sure you are targeting the .NET Framework 4 it is a Silverlight 4 application.

image

For this test project we don’t need to host this in a website.

image

The next thing you need to do is right-click on the project and choose “Properties”.  Check the “Enable Running Application out of browser”

image

Now click the Out of Browser Settings button and set “Require Elevated Trust when running outside the browser.”

image

Next add a button to the application to install our OOB application.

<Button x:Name="btnInstall" Content="Install Me" Click="btnInstall_Click" />

if (Application.Current.InstallState == InstallState.NotInstalled)
               Application.Current.Install();

Now run the application and install it by clicking the install button you just created.  When you start the installation, you will be prompted to install the app, and whether or not you want to create some shortcuts. Just say yes, we trust ourselves, sort of.

image

The next thing we want to do is enable debugging our OOB application.  So right click your project and chose properties –> Debug –> Installed out of browser application –> YourApplication

image

The final step is to add a reference to Microsoft.CSharp.dll so we can use the dynamic keyword.  Look for it in C:\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\.  Now on to fun stuff.

Send an Email with Outlook

First create a form that will take your user input for the “To” and “Message” data, and a button to send the message.

<StackPanel>
    <StackPanel Margin="5">
        <TextBlock Text="To:" />
        <TextBox x:Name="txtTo" />
    </StackPanel>
    <StackPanel Margin="5">
        <TextBlock Text="Message" />
        <TextBox x:Name="txtMessage" Height="200" />
    </StackPanel>            
    <Button x:Name="btnSend" Content="Send Message" Click="btnSend_Click" />
</StackPanel>

Then handle the button click event as follows.

private void btnSend_Click(object sender, RoutedEventArgs e)
{
    using (dynamic outlook = ComAutomationFactory.CreateObject("Outlook.Application"))
    {
        dynamic mail = outlook.CreateItem(0);            
        mail.To = txtTo.Text;
        mail.Subject = "Hello, from Silverlight";
        mail.HTMLBody = txtMessage.Text;
        mail.Display();
    }
}

And you done.  Of course this will only display the message, but just call mail.Send() to actually send it; one thing to mention is to make sure you have Outlook open when you hit send or bad things will happen.

Send Data to Excel, edit it, and update Silverlight

Now this little trick is cool. We will have a data source, send it to excel for display and editing, then send the updated data back to our Silverlight application and update the UI.  Now this is a poor mans implementation for demo reasons.

First lets create our UI and populate it with data.  This is what mine looks like.

image

Lets code up the Launch Excel button.

bool firstTime = true;
private void LaunchExcel(object sender, RoutedEventArgs e)
{
   // create an instance of excel
   dynamic excel = ComAutomationFactory.CreateObject("Excel.Application");
   excel.Visible = true;  // make it visible to the user.
 
   // add a workbook to the instance 
   dynamic workbook = excel.workbooks;
   workbook.Add();
 
   dynamic sheet = excel.ActiveSheet; // get the active sheet
 
   dynamic cell = null;
 
   int i = 1;
 
   // iterate through our data source and populate the excel spreadsheet
   foreach (Entity item in CustomerList.ItemsSource)
   {
       cell = sheet.Cells[i, 1]; // row, column
       cell.Value = item.CustomerName;
       cell.ColumnWidth = 25;
 
       cell = sheet.Cells[i, 2];
       cell.Value = item.UnitSales;
       i++;
   }
 
   // add a chart
   dynamic sheetShapes = sheet.Shapes;
   sheetShapes.AddChart(-4100, 200, 2, 400, 300);
 
   // wire up an event handler to the Excel SheetChanged event
   if (firstTime)
   {
       excel.SheetChange += new SheetChangedDelegate(SheetChangedEventHandler);
       string sheetName = sheet.Name;
 
       firstTime = false;
   }
 
   ExcelButton.IsEnabled = true;
}

As you can see we are hooking into the Sheet changed event so we can respond to when the data is update in excel. So here is the code for that.

delegate void SheetChangedDelegate(dynamic excelSheet, dynamic rangeArgs);
 
// event handler for the sheet changed event
// looks at the data and creates a new items source to rebind to the datagrid
private void SheetChangedEventHandler(dynamic excelSheet, dynamic rangeArgs)
{
    dynamic sheet = excelSheet;
 
    string sheetName = sheet.Name;
 
    dynamic range = rangeArgs;
 
    dynamic rowValue = range.Row;
 
    Entity[] entities = CustomerList.ItemsSource as Entity[];
    Entity[] newEntities = new Entity[10];
 
    dynamic col2range = sheet.Range("B1:B10");
 
    for (int i = 0; i < 10; i++)
    {
        Entity newEntity = new Entity();
        newEntity.CustomerName = entities[i].CustomerName;
        newEntity.PhoneNumber = entities[i].PhoneNumber;
 
        dynamic item = col2range.Item(i + 1);
        newEntity.UnitSales = Convert.ToInt32(item.Value);
 
        newEntities[i] = newEntity;
    }
 
    CustomerList.ItemsSource = newEntities;
    CustomerList.SelectedIndex = Convert.ToInt32(rowValue) - 1;
 
    UpdateNotification.Text = "Data updated from Excel spreadsheet";
}

Now, I will click the Launch Excel button, and edit my data.

image

Now if I look back in my Silverlight application, I will now see that all of my data in the grid has been updated.

image

Lets Open a Program

Using the WScript.Shell API we can execute any command and open any program.  In this example lets open Notepad and write some text to it.  So first I am going to create a simple UI to allow a user to enter some text, then click a button to send it to Notepad.

<StackPanel>
   <TextBlock Text="Enter text to send to NotePad." />
   <TextBox x:Name="txtTextToSend" />
   <Button x:Name="txtOpenProgram" Content="Open NotePad" Click="txtOpenProgram_Click"  />
</StackPanel>

private void txtOpenProgram_Click(object sender, RoutedEventArgs e)
{
    using (dynamic shell = ComAutomationFactory.CreateObject("WScript.Shell"))
    {
        shell.Run(@"C:\windows\notepad.exe"); //you can open anything
        shell.SendKeys(txtTextToSend.Text);
    }
}

Now lets enter some text and click that button.

image

image

Now that’s pretty cool. I am sure your mind is thinking of all the cool stuff you can do with this.

Well, this stuff is cool and all, but where is the really cool stuff? Wait no more!

Text to Speech

That’s right, I said it.  I am going to use the SAPI.SpVoice API to tap into the power of text to speech. So lets build a UI that will allow a user to enter some text. Heck lets let them control the rate and pitch of the speech.

<StackPanel>
    <TextBlock Text="Enter text to say: " />
    <TextBox x:Name="txtTextToSay" Margin="0,0,0,20" />
    <TextBlock Text="Pitch" />
    <Slider x:Name="sldrPitch" Minimum="-10" Maximum="10" Value="0" />
    <TextBlock Text="Rate" />
    <Slider x:Name="sldrRate" Minimum="-10" Maximum="10" Value="0" />
    <Button x:Name="btnSpeak" Content="Speak" Click="btnSpeak_Click" Margin="20" />
</StackPanel>

This should look something like this.

image

Lets hook up our button’s click event and make some noise.

private void btnSpeak_Click(object sender, RoutedEventArgs e)
{
   using (dynamic ISpeechVoice = ComAutomationFactory.CreateObject("SAPI.SpVoice"))
   {
       ISpeechVoice.Volume = 100;
       ISpeechVoice.Speak(string.Format("<rate speed=\"{0}\"><pitch middle=\"{1}\">{2}", Math.Round(sldrRate.Value), Math.Round(sldrPitch.Value), txtTextToSay.Text));
   }
}

If you wanted to you could create a volume control for it as well.

What? This still isn’t cool enough for you? Well I think I can satisfy your need for coolness. Enter:

Acquire an Image from your Scanner/Camera

That’s right I said it.  You can access your scanner, scan an image, and then save it to your hard drive.  Here we are using the WIA (Windows Image Acquisition). “WIA is a full-featured image manipulation component that provides end-to-end image processing capabilities.  The WIA Automation Layer makes it easy to acquire images on digital cameras, scanners, or Web cameras, and to rotate, scale, and annotate your image files.” So, enough with the talking, lets get coding.

First all I need to do it create a button that will initiate the process.

<Button x:Name="btnAquireImage" Content="Aquire Image from Scanner/Camera" Click="btnAquireImage_Click" />

Now we need to handle the button’s click event and do all the complicated code.

private void btnAquireImage_Click(object sender, RoutedEventArgs e)
{
   using (dynamic CommonDialog = ComAutomationFactory.CreateObject("WIA.CommonDialog"))
   {
       dynamic imageFile = CommonDialog.ShowAcquireImage();
       if (imageFile != null)
       {
           string filePath = string.Format("D:\\{0}.jpg", Guid.NewGuid());
           imageFile.SaveFile(filePath);
           MessageBox.Show(string.Format("Saved {0}", filePath));
       }
   }
}

So what does this actually do?  Well lets push the button and find out:

image

Well the first thing it does is asks me which device I want to get my images from.  Lets pick my scanner.

image

Well, holly crap, that’s my scanner. And I can preview and crop my image before I even get the image. Yes, that is my baby picture, it was the only thing I could find OKAY!  All my current pictures are digital.  Anyways, so once I like my settings I click scan, the scanner will do its thing and my image will be saved.

image

image

image

And there it is right where I told it to save.  How fun is that? I did you a favor and already coded all this up and packed it up in a nice little zip file.  So download the code, play with it, and write some kick ass applications!

I almost forgot to mention, when you open the source code, go into Properties –> Debug –> and select “Dynamically create the page”, then run the application, install it, and go back and change the option back to “Installed out of browser application ”.

11 thoughts on “Silverlight 4 – Accessing System Devices with Com Interop, such as a Scanner.”

  1. While I think these examples you gave are pretty nice, we could definitely use the Excel support in our current Silverlight app at work; it seems that starting with Silverlight 4 Microsoft is adding features that will only work on the Windows platform. Silverlight was supposed to be this competitor to Flash for creating RIAs that run on Windows and Mac, and Linux through Moonlight. Once they add COM support, they are starting down a road of platform specific features.

  2. @punkcoder

    You are correct, there is no COM+ support for Macs in SL 4 Beta 1, but there is a good reason for that; Mac doesn’t have COM+ . Macs do have similar access to COM using AppleScript. There is a rumor in the air that Microsoft has stated that they will try to support COM interop in Macs with SL 4 RTM. In order to do this I suspect they will either allow executing AppleScript, or extend the ComAutomationFactory class to return ApplScript objects. Even if they don’t support COM for Mac, doesn’t it make sense to at least support the OS and applications they make? Chances are if your doing Microsoft Office interop, your not on a Mac.

  3. Hi,very good work, I downloaded your code and change (ComAutomationFactory)
    to (AutomationFactory)and evrything just fine,I wonder How to use
    (AutomationFactory)in Windows Forms Apps wihtout the error
    (The type initializer for ‘MS.Internal.JoltHelper’ threw an exception.) that i get ?.

  4. @Wajid

    That is because the AutomationFactory class is a Silverlight class library. If you want to use WIA in WinForms just add a refernce to wiaaut.dll. You have to download the WIA dll from Microsoft.com and then browse to the dll and add it to your project. I am assuming that you are specifically talking about the scanner/camera functionality. Otherwise you can use other methods to accomplish the other functionality.

    1. ComAutomationFactory was renamed to AutomationFactory and lives in System.Runtime.InteropServices.Automation

  5. “one thing to mention is to make sure you have Outlook open when you hit send or bad things will happen.”
    like what?

Comments are closed.