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.
For this test project we don’t need to host this in a website.
The next thing you need to do is right-click on the project and choose “Properties”. Check the “Enable Running Application out of browser”
Now click the Out of Browser Settings button and set “Require Elevated Trust when running outside the browser.”
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.
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
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 FilesMicrosoft SDKsSilverlightv4.0LibrariesClient. 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.
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.
Now if I look back in my Silverlight application, I will now see that all of my data in the grid has been updated.
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:windowsnotepad.exe"); //you can open anything
shell.SendKeys(txtTextToSend.Text);
}
}
Now lets enter some text and click that button.
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.
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:
Well the first thing it does is asks me which device I want to get my images from. Lets pick my scanner.
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.
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 ”.
[…] in February, I wrote a blog post showing you how to, using Silverlight 4 OOB (out of browser) with elevated trust, access system […]
[…] I also have a blog post that steps you through the sample code here. […]
[…] I also have a blog post that steps you through the sample code here. […]
Great stuff!
Any idea if it is possible to use the scanner without user interaction (e.g. without showing the dialog box etc.)?
Thanks,
Assaf
Yes it is, but you must become intimate with the Windows Image Acquisition (WIA) API.
You can start here:
http://msdn.microsoft.com/en-us/library/ms630490(VS.85).aspx
There are also some good examples here:
http://msdn.microsoft.com/en-us/library/ms630826(VS.85).aspx
Hi. What if I would like to use other way to acquire Image, E.G. Aquire from TWAIN devices, How shoul I proceed?
I am confused on your question. Cameras and scanners are TWAIN devices. Can you be a little more specific?
In the example you aquired the image from a WIA device using: ComAutomationFactory.CreateObject(“WIA.CommonDialog”)
Do you have any idea on how aquire from a TWAIN Device?
Actually, WIA is not a device. WIA (Windows Image Acquisition) is a Microsoft driver model and API that enables imaging/graphic applications to interact with imaging hardware such as scanners and camera (TWAIN devices). You could use the WIA API to interact with your TWAIN devices. Check out this link for more detailed information:
http://msdn.microsoft.com/en-us/library/ms630368(VS.85).aspx
I totally understands Davids question: A lot of scanners and cameras have WIA Drivers (some specific, some more general). Unfortunately, there are also a lot of scanners (Canon as an example) without WIA drivers. They only provide TWAIN and ISIS drivers.
In the Microsoft link above, you can read that there exists a “transparant compatibility layer” which allows TWAIN applications to use WIA-driver-based devices: But this is the other way around!
We should need a WIA-driver that can communicate with TWAIN drivers, so our TWAIN drivers can communicate with the scanner …
The chance that Microsoft develops such a drivers is small, since they see TWAIN as an “older” solution for talking to external devices.
You may try some TWAIN scanning control, such as Dynamic Web TWAIN, to achieve that.
the example
why ?message rror: The name ‘ComAutomationFactory’ does not exist in the current context
The example was built with the beta version of silverlight 4 and ComAutomoationFactory has been renamed to AutomationFactory.
now ….
on this line: using (dynamic dialog = AutomationFactory.CreateObject(“WIA.CommonDialog”))
This error message showed up: No object was found registered for specified ProgID.
thanks, I found the solution
Hi Gbetus and Experts,
What is the solution that u have found? I am getting the same error.
BR
Abethan
Help me ,I had met the same problem
hi,
Have you got any examples of creating your own simple com object and instantiating and calling methods for that?
Thanks
Sorry, but I don’t have any examples for your request, but it is easy to do. First create your COM objects, and make sure you register them. After that, you should be good to go.
Hi Gbetus and Experts,
What was the solution that u have found? I am getting the same error.
BR
Abethan
Please help me on this.
is it possible to have a interprocess communication between out of browser SL application and other .NET application in the similar way ?
I Failed to interact with Web Cameras, on Windows 7 Professional, 64 bit .. But the Scanner is working well. The Same Application working well on windows XP. Has anybody some Idea to fix it??
Hi
It works on win7 bud dose not work on xp
is there idea about it?