I cannot count how many times people have requested the source code to the ATAPI.NET or ITAPI3 projects. My response has always been that I was unable to release it due to ownership issues (it was developed under contract for a client). I am pleased to announce this morning that I have worked through those issues and have been granted permission to release the project in it’s entirety as source code on CodePlex! Here’s their new homes: http://atapi.codeplex.com/ and http://itapi3.codeplex.com I’ll open up discussion board access there as well for Q&A. If you would like to contribute to the project, shoot me an email and we’ll see about getting you access!
In the previous post, I provided a link to the project template you can use to start a new MVVM project using the JulMar MVVM library. Here's the two links in case you didn't get them before:
MVVM Helpers Distribution Project Template
Copy the project template into your Visual Studio 2008 templates directory located off your user documents - mine is at %UserProfile%\Documents\Visual Studio 2008\Templates\ProjectTemplates\Visual C#\Windows.
Ok, so once you have these installed, what can you do with them? Well, for starters, you can now generate a starter project that provides a nice template for an MVVM application. The starter template creates a simple "Explorer" like program - it looks like this:
Notice there are two sections - a TreeView listing all the directories, and a ListView showing all the files in the selected directory. Let's look a little closer at the project structure. There are four directories in the solution:
| Converters |
This has a simple ValueConverter that takes a filename and returns an icon as an ImageSource. |
| Dependencies |
This is the binary dependencies the project requires, specifically it contains the JulMar MVVM libraries and Blend action libraries. |
| ViewModels |
This directory contains all the business logic in the form of ViewModels. |
| Views |
Finally, this directory contains all the UI (XAML) for the application. |
Views
Let's start with the last folder - the Views. Views are the UI presentation of data - in the case of a WPF/Silverlight application this is most commonly the XAML and XAML code behind files (they are considered a single element together). We want to have UI-specific code and designer elements present in these files. Generally we prefer to place business logic and testable elements into the ViewModel area.
In this starter project, we have a single view MainWindow.xaml. This is what presents the main UI shown above. The code behind file contains the required boilerplate code (essentially a constructor and call to InitializeComponent).
If you open the view (note that the designer will choke on it until you compile the project), you will find fairly straightforward XAML that creates the UI, let’s break it down as we go:
<Window x:Class="WpfMVVMApplication1.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:julmar="http://www.julmar.com/wpfhelpers" xmlns:ViewModels="clr-namespace:WpfMVVMApplication1.ViewModels" xmlns:Converters="clr-namespace:WpfMVVMApplication1.Converters" DataContext="{julmar:ViewModelCreator {x:Type ViewModels:MainViewModel}}" Title="File Explorer" Height="400" Width="500">
First, notice how the DataContext is established – through a custom MarkupExtension called ViewModelCreator. This extension is responsible for creating an associated ViewModel for this view (where our testable business logic should go) and also wiring up a couple of special event handlers that will allow the ViewModel to activate the view and close the view respectively.
Next, let’s check out the resources:
<Window.Resources>
<!-- Bindable commands sit in resources and allow keyboard input to target ViewModel commands -->
<julmar:BindableCommand x:Key="CloseCommand" Command="{Binding CloseAppCommand}" />
<Converters:FilenameToIconConverter x:Key="iconConverter" />
<HierarchicalDataTemplate x:Key="DirectoryTemplate"
ItemsSource="{Binding Subdirectories}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Source="{Binding FullName,
Converter={StaticResource iconConverter}}" />
<TextBlock x:Name="tb" Margin="5,0" Text="{Binding Name}" />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="tb" Property="FontWeight" Value="Bold" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</Window.Resources>
Here we find three defined resources – first we have a JulMar MVVM BindableCommand. This is a special ICommand instance that can be data bound and forwards to the specified binding. We use this a bit later in the keyboard accelerator to close the application. Next, there is the converter that takes a filename and converts it to an icon – that’s the source code in the Converters folder mentioned earlier. I use a converter here because this is very UI-centric and not very testable – so the converter will data bind to a string (filename) property of the ViewModel and retrieve the icon for the UI to display. That way, my ViewModel sticks with base (non-WPF) types. This isn’t a hard rule – but it’s a good one to try to follow.
Finally, there is a DataTemplate that is used to display the directory structure – this is what the TreeView uses to display it’s data. Notice it data binds to a couple of properties – FullName, Name and IsSelected. All of these, as you will see, exist in our ViewModel definition. The View takes the data from the ViewModel and displays it onto the UI for the user to interact with.
Next in the XAML is the input bindings – this is where we define keyboard and mouse gesture accelerators, and it’s where I use the bindable command I defined earlier:
<Window.InputBindings>
<KeyBinding Key="X" Modifiers="ALT" Command="{StaticResource CloseCommand}" />
</Window.InputBindings>
Here you can see that instead of trying to bind to the command, we use {StaticResource} to get it from the resources. This is necessary under WPF 3.5 because the KeyBinding will not inherit the DataContext and so cannot bind directly to the command – but resources can, and specifically Freezable resources can – that’s what the BindableCommand provides – a Freezable resource that forwards the ICommand implementation onto a command defined in the DataContext ViewModel. This is not necessary under WPF4 – this is actually one of the new features they’ve added: to inherit the DataContext in your input bindings!
The remainder of the view is fairly traditional – we use bindings to connect the UI up to the ViewModel definitions, so let’s go look at what those are.
ViewModels
The ViewModel folder contains three source code files – DirectoryViewModel.cs, FileViewModel.cs, and MainViewModel.cs. If you recall from above, MainViewModel is the primary ViewModel that is data bound to the view. The other two are child view models used to represent the files and folders respectively. Let’s look at FileViewModel as an example:
public class FileViewModel : SimpleViewModel
{
/// <summary>
/// Marker file that signals expansion of the tree.
/// </summary>
internal static FileViewModel MarkerFile = new FileViewModel();
private readonly FileInfo _data;
/// <summary>
/// File name
/// </summary>
public string Name
{
get { return _data.Name; }
}
/// <summary>
/// Full path + filename
/// </summary>
public string FullPath
{
get { return _data.FullName; }
}
/// <summary>
/// Size of the file in bytes
/// </summary>
public long Size
{
get { return _data.Length; }
}
/// <summary>
/// Private constructor used to create marker file.
/// </summary>
public FileViewModel()
{
}
/// <summary>
/// Public constructor that captures a list of files.
/// </summary>
/// <param name="fi">FileInfo to grab file information from</param>
public FileViewModel(FileInfo fi)
{
_data = fi;
}
}
As you can see, it is very simple – it is simply a wrapper around a piece of data, a FileInfo that represents a file on disk. It exposes properties that are data bindable. There is one element (MarkerFile) that I want you to ignore for a moment – we’ll get to it in a second. Notice that it extends SimpleViewModel. This is one of three primary VM classes in the MVVM helper library. SimpleViewModel is intended for cases where you need the bare minimum support – specifically support for INotifyPropertyChanged. No other services are provided by this base class, and as such it is very light. Let’s look at the directory class next:
/// <summary>
/// Sample ViewModel that wraps a Directory.
/// </summary>
public class DirectoryViewModel : ViewModel
{
/// <summary>
/// String used to send message to main view model about directory selection.
/// </summary>
internal const string SelectedDirectoryChangedMessage = @"SelectedDirectoryChanged";
/// <summary>
/// Marker directory that signals expansion of the tree.
/// </summary>
internal static DirectoryViewModel MarkerDirectory = new DirectoryViewModel();
private bool _isSelected, _isExpanded;
private readonly DirectoryInfo _data;
private readonly ObservableCollection<FileViewModel> _files;
private readonly ObservableCollection<DirectoryViewModel> _subdirs;
/// <summary>
/// Name of the directory
/// </summary>
public string Name
{
get { return _data.Name; }
}
/// <summary>
/// Full path + name of the directory
/// </summary>
public string FullName
{
get { return _data.FullName; }
}
/// <summary>
/// True/False whether the directory is selected (i.e. current).
/// Selecting the directory causes it to populate it's file collection.
/// </summary>
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
if (_isSelected)
{
if (_files.Count == 1 && _files[0] == FileViewModel.MarkerFile)
{
_files.Clear();
_data.GetFiles()
.Where(f => (f.Attributes & (FileAttributes.Hidden | FileAttributes.System)) == 0)
.ForEach(f => _files.Add(new FileViewModel(f)));
OnPropertyChanged("TotalFiles", "TotalFileSize");
}
SendMessage(SelectedDirectoryChangedMessage, this);
}
else
{
_files.Clear();
_files.Add(FileViewModel.MarkerFile);
}
OnPropertyChanged("IsSelected");
}
}
}
/// <summary>
/// True/False if the directory is expanded. Expanding the directory causes it
/// to fill it's subdirectory collection.
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (_isExpanded != value)
{
_isExpanded = value;
if (_isExpanded)
{
if (_subdirs.Count == 1 && _subdirs[0] == DirectoryViewModel.MarkerDirectory)
{
_subdirs.Clear();
_data.GetDirectories()
.Where(d => (d.Attributes & (FileAttributes.Hidden | FileAttributes.System)) == 0)
.ForEach(d => _subdirs.Add(new DirectoryViewModel(d)));
}
}
// Throw them away to recollect later - implements a refresh.
else
{
_subdirs.Clear();
_subdirs.Add(DirectoryViewModel.MarkerDirectory);
}
}
OnPropertyChanged("IsExpanded");
}
}
/// <summary>
/// List of files in this directory.
/// </summary>
public IList<FileViewModel> Files { get { return _files; } }
/// <summary>
/// List of subdirectories in this directory.
/// </summary>
public IList<DirectoryViewModel> Subdirectories { get { return _subdirs; } }
/// <summary>
/// Count of files in this directory.
/// </summary>
public int TotalFiles { get { return _files.Count; } }
/// <summary>
/// Total size of all files in this directory.
/// </summary>
public long TotalFileSize { get { return _files.Sum(file => file.Size); } }
/// <summary>
/// Constructor for the marker directory. This is used to detect an expansion.
/// </summary>
private DirectoryViewModel()
{
_data = null;
}
/// <summary>
/// Public constructor
/// </summary>
/// <param name="di">DirectoryInfo to pull information from</param>
public DirectoryViewModel(DirectoryInfo di)
{
if (di == null)
throw new ArgumentNullException("di");
_data = di;
_files = new ObservableCollection<FileViewModel> { FileViewModel.MarkerFile };
_subdirs = new ObservableCollection<DirectoryViewModel> { DirectoryViewModel.MarkerDirectory };
}
}
This file is a bit more complicated – like the FileViewModel, this wraps a simple data object (a DirectoryInfo in this case). Notice that it too exposes properties to provide access to various bits of information. Here you can see that it is creating new properties such as TotalFileSize which is the sum of all the files sizes in this directory. That’s one of the jobs of the ViewModel – to provide easily bindable properties for the bits of information we want to display. In this case, the TotalFiles and TotalFileSize gets displayed in the StatusBar of the window when the directory has files.
Notice that the directory exposes files and subdirectories in ObservableCollections – but they are delay populated. This is done so that the TreeView comes up quickly and we don’t have to enumerate the entire disk to retrieve the directories and files! When you expand and collapse the nodes in the tree, it is populating the data. This is done through the IsExpanded property – if you look back in the View, you will see that the TreeView actually binds the TreeViewItem.IsExpanded to this property:
<TreeView Grid.Column="0" ItemsSource="{Binding RootDirectory}"
ItemTemplate="{StaticResource DirectoryTemplate}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Notice it also binds up the IsSelected property – this is when we populate the files collection. Since they are observable, they will force the UI to update when new items are added or removed and we see the Explorer effect we desire.
Lastly, before we leave this file, notice that when a directory is selected, it makes a method call to a function called SendMessage:
SendMessage(SelectedDirectoryChangedMessage, this);
It passes a string (the key) and an object (the data). This is a built-in service of the ViewModel base class and it’s the reason why this ViewModel does not derive from SimpleViewModel, but instead from ViewModel which is the full version. One of the services provided is a Message Mediator. This service basically allows you to loosely couple various objects together – we’ll see how the target registers, but for now, just notice the sender – it passes a string key and an object. Any target registered for the given key will receive the object. There are several overrides for the message mediator which I’ll detail in a later post.
Ok, let’s switch to the final file – the MainViewModel:
public class MainViewModel : ViewModel
{
private DirectoryViewModel _selectedDirectory;
/// <summary>
/// Root directory - can be bound to an ItemsControl on the UI.
/// </summary>
public DirectoryViewModel[] RootDirectory { get; private set; }
/// <summary>
/// Selected (active) directory
/// </summary>
public DirectoryViewModel SelectedDirectory
{
get { return _selectedDirectory; }
set { _selectedDirectory = value; OnPropertyChanged("SelectedDirectory"); }
}
/// <summary>
/// Command to display the About Box.
/// </summary>
public ICommand DisplayAboutCommand { get; private set; }
/// <summary>
/// Command to end the application
/// </summary>
public ICommand CloseAppCommand { get; private set; }
/// <summary>
/// Main constructor
/// </summary>
public MainViewModel()
{
// Register this instance with the message mediator so it can receive
// messages from other views/viewmodels.
RegisterWithMessageMediator();
// Create our commands
DisplayAboutCommand = new DelegatingCommand(OnShowAbout);
CloseAppCommand = new DelegatingCommand(OnCloseApp);
// Fill in the root directory from C:
RootDirectory = new[] { new DirectoryViewModel(new DirectoryInfo(@"C:\")) { IsSelected = true } };
}
/// <summary>
/// This method closes the application window.
/// </summary>
private void OnCloseApp()
{
// Ask the view to close.
RaiseCloseRequest();
}
/// <summary>
/// This method displays the About Box.
/// </summary>
private void OnShowAbout()
{
// Get the message visualizer service from the service resolver.
// All services can be replaced, so make sure to check if we have something
// registered.
IMessageVisualizer messageVisualizer = Resolve<IMessageVisualizer>();
if (messageVisualizer != null)
{
// Show a message box.
messageVisualizer.Show("About File Explorer Sample", "File Explorer Sample 1.0", MessageButtons.OK);
}
}
/// <summary>
/// This method is invoked by the message mediator when a DirectoryViewModel is selected.
/// </summary>
/// <param name="newDirectory">DirectoryViewModel that is now active</param>
[MessageMediatorTarget(DirectoryViewModel.SelectedDirectoryChangedMessage)]
private void OnCurrentDirectoryChanged(DirectoryViewModel newDirectory)
{
SelectedDirectory = newDirectory;
}
}
Here you can see the same basic principle – it exposes properties the UI binds to. In particular here we see Commands being exposed as properties. Commands is what drives a UI – it allows a UI to trigger actions in the ViewModel. We are using two basic commands here – CloseAppCommand and DisplayAboutCommand. If you look at the constructor, you will see they are backed by a DelegatingCommand object. This is a common pattern found in almost every MVVM framework out there, but it’s essentially a pair of delegates that are called when the command is checked and when it is invoked. In our cases here, we always allow the command to execute so we only provide the execution handler (a second parameter would define the typical CanExecute handler). There are a couple of overrides for this object as well – one that provides type safety for the parameter and one that always uses object and allows for any object as data. Again here we are being simple and not using any parameters so our bound methods are both no-parameter methods.
The CloseAppCommand command invokes the OnCloseApp method – which in turn calls RaiseCloseRequest. This JulMar ViewModel method will close the view associated with the ViewModel if you associated the two using the ViewModelCreator.
The OnShowAbout method is called by the DisplayAboutCommand. It uses another registered service in the library called IMessageVisualizer. The message visualizer is used to display a simple message box from the ViewModel. Here we use it to display an about box. There are several other services I’ll talk about in the next post.
Notice that the RootDirectory property which is data bound to the TreeView.ItemsSource is exposes as an array – this is because the TreeView always expects a collection of items even though we always have a single root item. So we wrap a single DirectoryViewModel into a collection and return it as the property.
If you look at the end of the file you will find our message mediator target – OnCurrentDirectoryChanged. We use this as a way to see when a new directory has been selected in the tree. For a ListBox, we could have just data bound the SelectedItem to the property, but TreeView isn’t a selector and doesn’t expose a SelectedItem property. Instead you either have to catch an event (I’ll show how you can do that in a future blog entry about the MVVM helpers library) or use this little mediator trick.
The [MessageMediatorTarget] attribute is the secret sauce here – it tells the message mediator to wire this method up to the passed string key. When that key is used in a SendMessage call and the parameter type is a DirectoryViewModel (or derived type), the mediator will invoke this method. This all happens without any direct linkage between the DirectoryViewModel and the MainViewModel. The delegate instance is held in a weak reference so there’s no concern for memory leaks.
The second part of the magic is in the constructor – the message mediator is an opt-in service, so notice the call to RegisterWithMessageMediator(). This is what causes this instance to be noticed by the mediator. There is a balancing UnregisterWithMessageMediator if you ever want to unhook the instance. You can also use methods to directly wire up handlers (without attributes). This is useful when you are dynamically linking things together at runtime vs. compile time.
Well, that covers the basics of the framework Views + ViewModels. In the next post, I will detail the service registration and basic service mechanism that’s built into the framework for you to use. Until then, ciao!
To start off this new series, I have created a MVVM Helpers project template - this is a Visual Studio 2008 template which will create the starter project I will be using as an example. It shows off the primary usage of the MVVM library and has directories and references to required assemblies already established so it's a nice starting point for any MVVM WPF app using the helpers. Download it from here and copy the zip file into your template directory. As an example, if your user name was "Mark", like me and you are running Windows 7 it would be:
"C:\Users\Mark\Documents\Visual Studio 2008\Templates\ProjectTemplates\Visual C#\Windows" Here's what my directory looks like:
With that in place you should be able to fire up Visual Studio 2008 and create a sample MVVM project:
Let it generate a project and the build it -- running the program will give you a simple file explorer using the MVVM design pattern and WPF:
In tomorrow's post, we'll break the sample down and see how I built it. See you then!
With this post I am starting a new series - I hope to be more consistent in posting at least once or twice a week. To that end, I am going to focus on the WPF/MVVM helper library I use daily. I released an earlier version of it onto the web and have gotten a lot of comments which has been great, so I am releasing the latest version which has a lot of changes. So, without further ado, here's the code for you to download and play with, this compiles with Visual Studio 2008 SP1, or with Visual Studio 2010 Beta 2.
mvvmhelpers.zip
Read on for the basics of the project structure and files.
Project Structure
The code is broken into two assemblies: Julmar.Wpf.Helpers and Julmar.Wpf.Behaviors. The helpers assembly contains core WPF helper classes and MVVM support and the behaviors assembly depends on the System.Windows.Interactivity support and provides Blend-based behaviors for a variety of situations.
Building and UsageThere are pre-built assemblies included in the distribution, but you are free to build the source on your own - I don't include the certificate file (so I can tell versions that I have built) but you are free to delete the certificate from the solution or replace it with your own and build your own binaries from the source code. Or you can take any of the source code, modify it however you like and add it directly to your project.
CreditsAs with most projects, this library has benefited significantly from the community. The WPF Disciples list on has been a particular source for ideas and even source code. There are some source files in the project that I did not author, or where I took a bit of code and modified it to suit my purposes. I have tried to make sure people get credit where appropriate in the source code itself, if I missed anyone I am truly sorry.
DocumentationAs I mentioned earlier, my goal is to produce a set of blog posts that detail how to use the library, but there is also some documentation on each class included in the distribution in the form of a .CHM (Windows Help File). I encourage anyone who wants to use this library to check that out. |
|
A recent series of blog entries at http://themechanicalbride.blogspot.com/ introduced the Rx framework (System.Reactive.dll) which is an assembly used in the Silverlight toolkit for UI testing purposes. It essentially provides a mechanism to do event driven programming through LINQ. I'll refer you to the blog referenced above for all the gory details - frankly I'm still trying to wrap my mind around it!
Silverlight isn't my favorite technology however, I much prefer working in WPF and so I spent some time rebuilding Rx for the desktop CLR! My original approach was to take my favorite exploration tool, reflector (available for free from www.redgate.com), and disassemble all the classes into C#, placing them into a project file. I found however that Reflector choked on some of the more complicated structures - requiring me to go and hand-edit a bunch of the code. I realized while I was doing this that there was a much easier way to convert a Silverlight assembly to a WPF assembly.
As you probably already know, Silverlight shares the same assembly format as the desktop CLR - there is no difference in the IL or structure of the assembly itself. The difference is in the dependency on mscorlib and other references. Specifically, when you add an assembly via VS2008, it looks at the version of the referenced mscorlib to determine whether it's a desktop CLR assembly or Silverlight assembly.
Here's the header of a Silverlight assembly examined through ILDASM:
// Metadata version: v2.0.50727 .assembly extern mscorlib { .publickeytoken = (7C EC 85 D7 BE A7 79 8E ) // |.....y. .ver 2:0:5:0 }
Here is a desktop assembly:
// Metadata version: v2.0.50727 .assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 2:0:0:0 }
Notice the version difference .. Silverlight uses 2.0.5.0 of mscorlib and the desktop CLR uses 2.0.0.0. The public key is also different, but this is really just used for loading purposes.
Assuming the assembly doesn't use something Silverlight specific, we can modify the assembly references and allow the asembly to be used in the desktop CLR fairly easily.
Here's how I did it for System.Reactive.dll:
ILDASM System.Reactive.dll /out:SR.il
change the assembly references in the resulting IL text file (for mscorlib, system and system.core)
ILASM SR.il /resource=sr.res /output=WPF/System.Reactive.dll
That gave me a desktop version of the assembly without modifying anything in the code itself - much easier than my original reflector route!
Next, I took a couple of the samples from the blog and ported them to WPF - specifically, I took the events sample listed here http://themechanicalbride.blogspot.com/2009/07/developing-with-rx-part-1-extension.html and ported it to WPF to test it. I made very few changes, primarily just switching Application.Current.MainWindow for Application.Current.RootVisual. Here is the resulting project and switched assembly for anyone who is interested in it.
ExtensionEvents.zip (44.62 KB)
I think Rx is a fascinating piece of code, although it will take me a bit of time to realize just what I can do with it I suspect. I'm looking forward to building more with this using WPF now that's for sure!
One of the coolest new features of Blend 3 is the inclusion of behaviors. This new feature formalizes the "attached behavior" model that has become so prevelant in WPF (and Silverlight) development. I won't go into details on the architecture - instead I'll refer you to a nice reference:
http://blogs.msdn.com/expression/archive/2009/05/19/link-round-up-behaviors-related-posts.aspx
To play with this new support, I built a WatermarkTextBehavior which places a watermark into a TextBox when it has no entered data. I've included this new behavior into the current build of my MVVM toolkit which I'll release soon, but for now, let's look at the behavior:
First, we derive from System.Windows.Interactivity.Behavior<T> - the placeholder parameter is the Visual type you want the behavior to act on. This can be UIElement for anything WPF, or more restrictive if necessary based on the events you intend to hook up. For our purposes here, we will set the restricted type to TextBox.
public class WatermarkTextBehavior : Behavior<TextBox>
Next, you override the OnAttached() and OnDetaching() methods to hook up your event behaviors you desire. Call the base implementation first, and then the AssociatedObject property will be the element you've been attached to (the TextBox in this case). In our case we want to hook the GotFocus and LostFocus events - this is where we trigger our behavior.
protected override void OnAttached() { base.OnAttached(); AssociatedObject.GotFocus += OnGotFocus; AssociatedObject.LostFocus += OnLostFocus; ... }
protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.GotFocus -= OnGotFocus; AssociatedObject.LostFocus -= OnLostFocus; }
Finally, we can provide any properties necessary to drive our behavior. These should be done in the form of Dependency Properties so they are bindable and interact nicely with WPF. The base Behavior<T> derives from Freezable and inherits the DataContext automatically to enable this support. In our implementation we will have a Text property to indicate the watermark, and an attached property which we will place onto the TextBox so it can be styled when the watermark is being used.
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof (string), typeof (WatermarkTextBehavior), new FrameworkPropertyMetadata(string.Empty));
Next we can hook it up in Blend through the Asset panel - all known assets are shown here (either registered, in the Blend directory, or project references). Drag our WatermarkTextBehavior onto any TextBox and set the Text property and it will generate the following XAML:
<TextBox> <TextBox.Style> <Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}"> <Style.Triggers> <Trigger Property="julmar:WatermarkTextBehavior.IsWatermarked" Value="True"> <Setter Property="Foreground" Value="Gray" /> <Setter Property="FontStyle" Value="Italic" /> </Trigger> </Style.Triggers> </Style> </TextBox.Style> <i:Interaction.Behaviors> <julmar:WatermarkTextBehavior Text="Enter a name here" /> </i:Interaction.Behaviors> </TextBox>
Here is the complete source code to the behavior: using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace JulMar.Windows.Interactivity
{
/// <summary>
/// This behavior associates a watermark onto a TextBox indicating what the user should
/// provide as input.
/// </summary>
public class WatermarkTextBehavior : Behavior<TextBox>
{
/// <summary>
/// The watermark text
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof (string), typeof (WatermarkTextBehavior),
new FrameworkPropertyMetadata(string.Empty));
static readonly DependencyPropertyKey IsWatermarkedPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("IsWatermarked", typeof(bool), typeof(WatermarkTextBehavior),
new FrameworkPropertyMetadata(false));
/// <summary>
/// This readonly property is applied to the TextBox and indicates whether the watermark
/// is currently being displayed. It allows a style to change the visual appearanve of the
/// TextBox.
/// </summary>
public static readonly DependencyProperty IsWatermarkedProperty = IsWatermarkedPropertyKey.DependencyProperty;
/// <summary>
/// Retrieves the current watermarked state of the TextBox.
/// </summary>
/// <param name="tb"></param>
/// <returns></returns>
public static bool GetIsWatermarked(TextBox tb)
{
return (bool) tb.GetValue(IsWatermarkedProperty);
}
/// <summary>
/// Retrieves the current watermarked state of the TextBox.
/// </summary>
public bool IsWatermarked
{
get { return GetIsWatermarked(AssociatedObject); }
private set { AssociatedObject.SetValue(IsWatermarkedPropertyKey, value);}
}
/// <summary>
/// The watermark text
/// </summary>
public string Text
{
get { return (string) base.GetValue(TextProperty); }
set { base.SetValue(TextProperty, value); }
}
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>
/// Override this to hook up functionality to the AssociatedObject.
/// </remarks>
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.GotFocus += OnGotFocus;
AssociatedObject.LostFocus += OnLostFocus;
OnLostFocus(null, null);
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
/// </summary>
/// <remarks>
/// Override this to unhook functionality from the AssociatedObject.
/// </remarks>
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.GotFocus -= OnGotFocus;
AssociatedObject.LostFocus -= OnLostFocus;
}
/// <summary>
/// This method is called when the textbox gains focus. It removes the watermark.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnGotFocus(object sender, RoutedEventArgs e)
{
if (string.Compare(AssociatedObject.Text, this.Text, StringComparison.OrdinalIgnoreCase) == 0)
{
AssociatedObject.Text = string.Empty;
IsWatermarked = false;
}
}
/// <summary>
/// This method is called when focus is lost from the TextBox. It puts the watermark
/// into place if no text is in the textbox.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnLostFocus(object sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(AssociatedObject.Text))
{
AssociatedObject.Text = this.Text;
IsWatermarked = true;
}
}
}
}
A project I've been working on for the last two months is finally online in beta form - check out
http://rcat.codeplex.com/
It's essentially a biological alignment viewer for RNA sequences. Here's a couple of screen shots:
Alignment Viewer

2D Structure Viewer

2D Circle relationship Viewer

You can download the source code from the above link to play with it - it's a MVVM implementation and has quite a bit of interesting optimizations in it for performance purposes, including a read-only data virtualization for large sequence manipulation. The next release will include a full read/write implementation so watch the project if you are interested!
Update
Microsoft has released the API code pack including Windows 7 support -- get it here: http://code.msdn.microsoft.com/WindowsAPICodePack
Well, it's been a while since I posted anything, I'm sorry!
I've been busy working with Windows 7 and Microsoft Surface touch-computing. To that end, I needed to get access to some of the new Windows 7 APIs in managed code ... which isn't supported yet (but is coming). So I built an interop library to access:
- Scenic Ribbon (native Win32 ribbon) in Windows Forms
- Native WM_TOUCH and WM_GESTURE messages
- Sensor API
- Shell APIs (jump lists, thumbnail buttons, libraries)
One note: I make no guarantee that everything is correct (it's hard to do that on a shifting beta with minimal docs, but in addition I'm not sure I've gotten 100% coverage with everything anyway).
Here's the interop library with source code:
Windows 7 Beta 1 Interop Library for .NET 2.0
I'll be posting more details and samples a bit later, here's a couple to get you started.
Here's a simple example of using the Scenic Ribbon and native touch support to create a (very) simple Windows Forms finger paint program:
Multi Touch example with Windows Forms
Here's a simple example of using the gestures and library support in a WPF application. It grabs all the directories in your Pictures Library and then shows you each picture and lets you use the swipe gesture to move between then, pinch to scale and of course, rotate.
Gesture example with WPF
Both of these samples work with the HP Touchmate (and multi-touch drivers) and Windows 7 Beta 1.
Have fun!
-mark
It's been a long time since I blogged anything specific on WPF -- I've been doing a lot of it lately, along with some Silverlight. Recently I was experimenting with dragging tabs around on a TabControl at runtime. My end goal is really to implement it with Silverlight 2, but I've found it's much easier to prototype things in WPF and then port them over because the debugging experience is easier with WPF. I didn't want to create derived implementations of any classes - I wanted something that was non-intrusive to my code so I decided to use an attached property. Attached properties are basically property values "attached" to a class at runtime - where the property itself isn't defined on the target but instead on some other type. The cool thing about attached properties is they can register a change notification handler which gives them a reference to the object they are being placed on -- this is how the Spell Checker works with the TextBox in WPF. All the code for the spell checking lives in the SpellChecker class and when you add the SpellCheck.IsEnabled property onto the TextBox, it adds handlers to the TextBlock's TextChanged property and adds all the nifty spell checking goodness without changing the code in TextBox. Back to my drag/drop prototype. So with this code, I can add the property to any TabControl and get a nice, simple drag/drop experience. It's far from complete - it would be cooler if the tabs moved around as you dragged (they don't), but I was just prototyping here. Here's the code:
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfApplication1
{
public class DragDropTabManager
{
private static readonly DependencyProperty ManagerProperty =
DependencyProperty.Register(typeof (DragDropTabManager).ToString(), typeof (DragDropTabManager),
typeof (DragDropTabManager));
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool),
typeof(DragDropTabManager),
new PropertyMetadata(false, DDTM_EnabledChanged));
private static void DDTM_EnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tc = d as TabControl;
if (tc != null)
{
var oldValue = (bool) e.OldValue;
var newValue = (bool) e.NewValue;
if (oldValue == true && newValue == false)
{
var ddtm = tc.GetValue(ManagerProperty) as DragDropTabManager;
if (ddtm != null)
{
tc.PreviewMouseDown -= ddtm.TabItem_PreviewMouseDown;
tc.SetValue(ManagerProperty, null);
}
}
else if (oldValue == false && newValue == true)
{
var ddtm = new DragDropTabManager();
tc.SetValue(ManagerProperty, ddtm);
tc.PreviewMouseDown += ddtm.TabItem_PreviewMouseDown;
}
}
}
public static bool GetEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(EnabledProperty);
}
public static void SetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(EnabledProperty, value);
}
private bool isMoving;
private TabItem movingTabItem;
private TabItem lastTab;
private Point ptStart;
void TabItem_PreviewMouseDown(object sender, MouseEventArgs e)
{
var ti = e.Source as TabItem;
if (ti != null && e.LeftButton == MouseButtonState.Pressed)
{
var tc = ti.Parent as TabControl;
if (tc != null)
{
tc.MouseMove += tc_MouseMove;
tc.MouseLeftButtonUp += tc_MouseLeftButtonUp;
ptStart = e.GetPosition(tc);
movingTabItem = ti;
}
}
}
void tc_MouseMove(object sender, MouseEventArgs e)
{
var tc = sender as TabControl;
if (tc == null)
return;
Point pt = e.GetPosition(tc);
if (isMoving == false)
{
if (Math.Abs(pt.X - ptStart.X) > 10)
{
movingTabItem.IsHitTestVisible = false;
movingTabItem.RenderTransformOrigin = new Point(.5, .5);
movingTabItem.RenderTransform = new TranslateTransform(0, 0);
tc.Cursor = Cursors.Hand;
Panel.SetZIndex(movingTabItem, 1);
isMoving = true;
tc.CaptureMouse();
}
return;
}
TabItem newPos = FindTabItem(tc, pt);
if (newPos == null)
tc.Cursor = Cursors.No;
else
{
lastTab = newPos;
var xform = movingTabItem.RenderTransform as TranslateTransform;
if (xform != null)
xform.X = pt.X - ptStart.X;
tc.Cursor = Cursors.Hand;
}
}
void tc_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var tc = sender as TabControl;
Debug.Assert(tc != null);
tc.ReleaseMouseCapture();
tc.MouseMove -= tc_MouseMove;
tc.MouseLeftButtonUp -= tc_MouseLeftButtonUp;
if (isMoving == true)
{
isMoving = false;
tc.Cursor = Cursors.Arrow;
movingTabItem.RenderTransform = null;
movingTabItem.IsHitTestVisible = true;
Panel.SetZIndex(movingTabItem, 0);
if (lastTab != null)
{
if (lastTab != null && movingTabItem != lastTab)
{
int targetIndex = tc.Items.IndexOf(lastTab);
tc.Items.Remove(movingTabItem);
tc.Items.Insert(targetIndex, movingTabItem);
movingTabItem.Focus();
}
}
}
movingTabItem = lastTab = null;
}
private static TabItem FindTabItem(UIElement parent, Point pt)
{
var fe = parent.InputHitTest(pt) as FrameworkElement;
while (fe != null && fe.GetType() != typeof(TabItem))
fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
return fe as TabItem;
}
}
}
Last month, my colleague, Pinku Surana,
wrote an article about .NET AppDomains and how they can be used to provide
component isolation and make your applications more reliable. If you missed it, you can read the Developments
archives on Developmentor’s website. This month, I’d like to continue exploring reliability
and extensibility by introducing you to a new framework included with .NET 3.5:
The Managed Add-in Framework (MAF), sometimes referred to as System.AddIn.
Developers (and managers) have long desired a way to easily
create extensible applications that allow new features to be added without
jeopardizing the stability of the existing code base. The .NET framework has provided the underlying support to
accomplish this from the very beginning through the reflection API and
AppDomain support Pinku examined last month. MAF builds on that fundamental support to provide a
higher-level service that allow you to dynamically discover, load, secure and interact
with external assemblies used to provide features for your application. Several common architectural
requirements are made trivial with MAF:
1)
Isolate aspects of your code for security
reasons or partial-trust scenarios.
2)
Allow business partners to extend your
application safely without access to the source code; Adobe Illustrator is a
great example of this style of application.
3)
Separate volatile sections of your application
out – where depending on the customer the application needs to execute
different sets of code.
4)
Add or change code without unloading the
application – for example pay-to-play scenarios, or where you need to
update an assembly but the application must continue to run.
5)
Develop and evolve different sections of the
application in parallel without any fear of destabilizing one based on the
other.
If any of these scenarios sound like something you could
utilize in your application then you should know about MAF!
At the center of the MAF-based application is the pipeline.
The Pipeline
Communication between add-ins and the host application is
strictly regulated by the pipeline.
The pipeline is dynamically created by MAF through a set of
loosely-coupled components which are used to version, marshal and transform the
data as it passes back and forth between the host and each loaded add-in.

Each section of the pipeline is contained in a separate
assembly, loaded as necessary to manage the specific add-in. MAF discovers each component and then
loads them on request using reflection.
For reliability, MAF allows optional isolation boundaries to
be created between the add-in and host – everything to the left of the
contract is loaded into the main (host) AppDomain and everything to the right
loaded into a newly created AppDomain which has its own security permission set. The isolation can also be done through
cross-process calls if true process-level isolation is necessary. Under the covers, the system uses
traditional remoting calls to do the work of marshaling calls back and forth.
Breaking down the pipeline, there are three main parts
starting in the center with the contract.
Contract
As you might expect, MAF is based on interface
contracts. Interfaces allow classes
in an application to be loosely coupled, reducing the risk when changes are
made between dependent sections of the code. The interface contract is shared between the host and
add-in and once it has been established, it should never be changed.
Consider the simple example of a translator program. The host will expect to load one or
more translator add-ins that will do some work on an inbound string and return
the results. To accomplish this, I
might create an ITranslator
interface that looks like:
[AddInContract]
public interface ITranslator : IContract
{
string Translate(string input);
}
Notice that the interface derives from IContract.
This is a requirement for
add-in contracts and is what will be used to provide marshalling support when
the pipeline is established. We
also need to decorate the interface with the [AddInContract] attribute – this is the marker used by MAF to
identify the contract when it is dynamically constructing the pipeline. Both of these types come from the System.AddIn.Pipeline namespace in System.AddIn.Contract.dll.
Views
Moving to the edges, we find the views. This is the code that the add-in and
host directly interact with. It
represents a host or add-in specific “view” of the contract and, like the
contract, is contained in a separate assembly.
Both of the view classes will echo the structure of the
contract, but not actually be dependent upon the contract. For example, our host side view might
look like this:
public abstract class TranslatorHostView
{
public abstract string Translate(string input);
}
The view is commonly exposed as an abstract class to make it look more natural
when used by the host, but an interface will work just as well. The host uses the view directly when
communicating with any add-in based on the contract.
On the add-in side, we have an almost identical class
– except we decorate this type with the [AddInBase] attribute so MAF knows which side of the pipeline this
view is for (the add-in). This
is located in the System.AddIn.Pipeline
namespace in the System.AddIn
assembly.
When we create each add-in, the implementation will use this
type as the base class.
[AddInBase]
public abstract class TranslatorAddInView
{
public abstract string Translate(stringinp);
}
Adapters
The last piece of the pipeline is the adapters. The adapters play a very special role
in the pipeline – they are the glue that binds the contract to the view:
implementing lifetime management and any necessary data conversion between the
two ends.
It may seem redundant to have this class, but by separating
the view from the contract we introduce version tolerance into our architecture
– the host can version independently of the add-in and vice-versa. Depending on the situation, the
adapters can be as simple as a pass-through class, or can provide higher-level
services to allow non-serializable types to be marshaled across the isolation
boundaries.
On the host side, the adapter will implement the view
(remember it is either an interface or abstract class). It will be passed a reference to the
contract in the constructor and it is responsible for connecting the two together.
[HostAdapter]
public class TranslatorHostViewToContract :
TranslatorHostView
{
ITranslator _contract;
ContractHandle _lifetime;
public TranslatorHostViewToContract(
ITranslator contract)
{
_contract =
contract;
_lifetime = new ContractHandle(contract);
}
public override string Translate(string inp)
{
return
_contract.Translate(inp);
}
}
In this simple example, the code caches off the contract interface. This is a remoting proxy to the actual
loaded add-in. The adapter then
implements the Translate method and
passes it forward to the contract for implementation. If we had non-serializable types, then the adapter would be
responsible for converting them into something that was serializable.
It also provides some lifetime management for the
contract. Because the contract is
a remoting proxy and is likely running in a separate AppDomain (or even
process) we have to be concerned with how long it lives. MAF provides all the support for this through
the ContractHandle class. Most of the time, all you will need to
do is store a ContractHandle in your
host adapter and then pass it the inbound contract to wrap in your constructor.
Finally, in order for MAF to identify this class, it must be
decorated with the [HostAdapter]
attribute from the System.AddIn.Pipeline
namespace out of the System.AddIn.dll.
The add-in side looks very similar, but does the opposite: it
makes the add-in view look like the contract.
[AddInAdapter]
public class TranslatorAddInViewToContract :
ContractBase, ITranslator
{
TranslatorAddInView _view;
public TranslatorAddInViewToContract(
TranslatorView view)
{
_view = view;
}
public string Translate(string inp) {
return _view.Translate(inp);
}
}
MAF passes the view to the constructor and the class caches the reference off
in a field. It implements the contract
(ITranslator) and ContractBase which provides the
implementation of the IContract interface
for us (remember this was a required interface on our contract). As the host makes calls to the contract
interface, this class will translate those calls to the add-in view, which as
you will see is the implementation provided by the add-in itself. Note how the [AddInAdapter] attribute is used to mark this class so MAF can
discover it.
If it seems like all the above is a lot of repetitious,
boilerplate code well.. you’re right!
To make it easier on the developer to create the pipeline components,
the MAF team has created a pipeline
builder available at http://www.codeplex.com/clraddins. It takes the contract assembly and then
generates the views and adapters from it:

Creating an add-in
Once the pipeline pieces are built you can create
add-ins. Each add-in provides an
implementation of the abstract add-in view. For example, we might provide a BabelFish add-in for
universalized translation:
[AddIn(“BabelFishTranslator”,
Description=“Universal
translator”,
Version=“1.0.0.0”,
Publisher=“Zaphod Beeblebrox”]
public class BabelFishAddIn :
TranslatorAddInView
{
public string Translate(string input)
{
...
}
}
The add-in implements the AddInView,
providing a concrete implementation of the Translate
method. It is decorated with the [AddIn] attribute that allows it to
provide a name, version, description and other data the host can use to decide
whether it is a useful translator.
Putting the pieces together – the directory structure
To properly identify each of the components necessary, MAF
enforces a particular directory structure you need to follow for
deployment. Each piece is stored
in a sub-directory off the root of the pipeline directory (this is typically
the APPBASE where the host executable is stored).

The
directory names are required, but not case sensitive – each directory
holding a single piece of the pipeline that MAF will load dynamically when it
is loading the add-in. The
host-side view is always located in the same directory as the host executable
itself so it does not need a dedicated directory. When creating your Visual Studio project, it is important to
set the output directories appropriately so that you create the above directory
structure. In addition, all
references between the components should be marked as CopyLocal = “false” in
Visual Studio to ensure a local copy of the assembly isn’t placed into the
sub-directory:

Discovering and loading add-ins from the host
The last piece of the puzzle is the actual loading of
add-ins from the host. This is
done in three basic steps:
1.
Identify and catalog the add-ins
2.
Retrieve the list of specific add-ins based on
view or name
3.
Activate and use the add-in
The first step is to identify the available add-ins for the
host. This is done through the System.AddIn.AddInStore class:
string[] errorList = AddInStore.Rebuild(
PipelineStoreLocation.ApplicationBase);
Calling Rebuild
forces MAF to walk the directory structure and create the pipeline
database. It stores this
information in the pipeline root directory and returns a list of errors, if any
occur. The most common errors are
missing pipeline components – where MAF is unable to locate some required
portion of the pipeline such as a View or Adapter.
Next, the host will retrieve a list of add-ins based on the
host view through the FindAddIns
method – these will be the add-ins conforming to a specific contract
(whatever the view/adapter is bound to):
Collection<AddInToken> addinTokens =
AddInStore.FindAddIns(
typeof(TranslatorHostView),
PipelineStoreLocation.ApplicationBase);
The first parameter is the host view type – so MAF
knows what add-ins we are looking for, the second is the pipeline root
directory, which is the same directory passed to the Rebuild method and indicates where the pipeline database is stored.
The returning collection represents a series of tokens that are used to identify and
control the add-ins. This is how
the host can examine, activate and unload the add-in. To activate a specific add-in, you can take the token and
call Activate:
Collection<AddInToken>
addinTokens;
...
foreach
(AddInToken token in addinTokens)
{
TranslatorHostView view =
token.Activate<TranslatorHostView>(
AddInSecurityLevel.Internet);
string hello =
view.Translate(“Bonjour”);
...
}
The Activate
call loads all the required components, instantiates the add-in type and returns
the host view used to interact with the add-in. Calls made to this object will be marshaled back and forth
to the add-in using the pipeline.
Notice that the parameter passed to Activate indicates the security level required. There are several overrides that allow
you to dictate exactly how the add-in is loaded. You can load the add-in into a specific AppDomain –
the current one for best performance:
token.Activate<TranslatorHostView>(AppDomain.CurrentDomain)
You can specify a specific permission set to restrict the
things the add-in can do on your behalf:
PermissionSet
pset = ...;
token.Activate<TranslatorHostView>(pset);
Or you can specify a known permission set based on the CAS zones:

Once the add-in is activated, the host can call it just like
any other object – but never forget that you are likely crossing an
isolation barrier! Make sure to design
your contracts so that you minimize the number of calls between the host and
add-ins to ensure your performance doesn’t suffer.
When you are finished with the add-in, you can tell MAF to
unload it through the AddInController
associated with the view:
AddInController ctrl =
AddInController.GetAddInController(view);
ctrl.Shutdown();
This will unload the add-in side pipeline and then destroy the containing
AppDomain so you release the resources associated with it.
There are many other capabilities MAF provides such as
versioning, passing collections and WPF visuals, passing non-serializable
types, etc. You can get a pretty
decent overview of the features from MSDN and the
MAF team has a blog at http://blogs.msdn.com/clraddins/
that has some great, practical information on using this new framework.
Thanks to all those who attended the GNET in Tampa last week. We had a blast with all of you and wish you the best of luck going back and using all the cool tricks and technologies we showed you!
As promised, here are the demos we build during the week - Demos.
Enjoy!
Sales of the TSP++ products have steadily fallen over the past 5 years, so much so that I decided two years ago to release the 2.x product into the open-source community, and now I am doing the same for the server edition.
The download is available here and requires a key to install it - use
J35M-3KXo-q60T
which will let you install the full product. Documentation and samples are all part of the image so have fun!
About a year ago, I blogged on using the .NET 2.0 facility SynchronizationContext to create thread-aware components which could be hosted and would "do the right thing" for callbacks. Here's the Link to that post.
Recently, I got an email from a reader who wanted to use that paradigm, but wanted to be able to suspend the operation for some period of time and then resume it later on. He asked if there was an easy way to modify the sample I presented, and it turns out that there is.
It essentially involves three steps:
1. In the AsyncStateData structure, add a ManualResetEvent object. This will be used to trigger the pause/resume behavior. It must be initially set as signaled.
2. In the actual asynchronous operation loop, add the event into the loop condition by calling event.WaitOne(). Make sure you have released any locks and the code can safely be halted at that point in the logic otherwise you may create deadlock scenarios and other difficult debugging issues.
3. Add a Pause and Continue method into the class which signals and resets the event.
Modifying my previous code example, here's what I come up with:
Step 1: Add the event to AsyncStateDate
1: class AsyncStateData 2: { 3: private ManualResetEvent pauseEvent = new ManualResetEvent(true); 4: ... 5: }
Step 2: Add event into the loop at a safe point
1: public partial class Calculator 2: { 3: private void InternalCalculatePi(int digits, AsyncStateData 4: asyncData) 5: { 6: int completedDigits = 0; 7: for (; !asyncData.canceled && pauseEvent.WaitOne(-1, true) && 8: completedDigits < digits; completedDigits++) 9: { ... } 10: } 11: 12: ... 13: }
Step 3: Add a Pause and Continue API
1: public partial class Calculator 2: { 3: public void PauseAsync(object asyncTask) 4: { 5: AsyncStateData asyncData = asyncTask as AsyncStateData; 6: if (asyncData != null && asyncData.running == true) 7: asyncData.pauseEvent.Reset(); 8: } 9: 10: public void ContinueAsync(object asyncTask) 11: { 12: AsyncStateData asyncData = asyncTask as AsyncStateData; 13: if (asyncData != null && asyncData.running == true) 14: asyncData.pauseEvent.Set(); 15: } 16: ... 17: } There are certainly other ways to do this as well, for example, create a dedicated Thread object instead of using an Async delegate and then call Suspend on the thread (and Resume to continue). That can be dangerous if you take locks while running your async operation so the ManualResetEvent is probably a better solution from that perspective (since you are blocking in a known location vs. just suspending the thread arbitrarily and potentially holding onto shared resources.
One of the nifty new features of the WPF platform is the pluggable data providers. It ships with two out of the box:
ObjectDataProvider: allows you to execute binding expressions against an object and it's methods XmlDataProvider: loads an XML data source and makes it available as a binding source
Both of these derive from the abstract class System.Data.DataSourceProvider which implements the binding glue (INotifyPropertyChanged) needed for data binding. A side note here is that you could write your own custom data provider if you needed to, although if the data is exposed through a .NET object, then the ObjectDataProvider is probably sufficient.
Using the providers is fairly easy -- let's say we have some XML data that looks like this:
<?xml version="1.0" encoding="utf-8"?> <taxrecords> <entry name="Opal Harrison" state="AL" income="$51,466.81" age="27" /> <entry name="Eugene Black" state="FL" income="$13,314.89" age="71" /> <entry name="Opal Chang" state="NC" income="$225,115.15" age="41" /> <entry name="Gary Waters" state="WI" income="$151,788.49" age="39" /> <entry name="Xavier Davis" state="AK" income="$136,344.97" age="66" /> <entry name="Stacy Harrison" state="TX" income="$122,432.82" age="32" /> </taxrecords>
The goal is to put this data into a ListBox - displaying the fields in the following format:
Name State, Age, Income
We could clearly do all of this from procedural code -- create an XmlReader object, load the data and render each XmlNode into the listbox. However, this is the 21st century and so we want to avoid coding as much as we can and utilize the underlying framework support instead!
We can get the data loaded into a collection source through the XmlDataProvider. This is easily done in XAML:
< XmlDataProvider Source="largeXmlFile.xml" x:Key="xmlData" XPath="/taxrecords" />
This will look for the file "largeXmlFile.xml", create an XmlDocument and load the file into memory. Notice we supply an XPath expression as part of this to indicate what we'd like the data provider to hand us as the collection itself. In this case, we want to see everything under the node "taxrecords" which is the root of the document. An interesting facet of this provider is that it performs it's work asynchronously -- you can see this behavior when you load very large XML files. The UI will come up first, completely empty and then suddenly be populated with data. The behavior can be adjusted through the IsAsynchronous property of the data provider. Setting this to false will delay the display of the UI until the data is fully available.
Another interesting thing about this class is that we can define the XML data inline within the XAML document. You do this with the x:XData tag:
<XmlDataProvider x:Key="xmlData" XPath="/taxrecords"> <x:Data> <taxrecords> <entry name="Opal Harrison" state="AL" income="$51,466.81" age="27" /> <entry name="Eugene Black" state="FL" income="$13,314.89" age="71" /> ... </taxrecords> </x:Data> </XmlDataProvider>
The next step is to bind this data to a ListBox control - this is a normal Data Binding expression:
< ListBox Name="lb1" Margin="10" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource xmlData, XPath=entry}}">
Notice how we use a new property of the BindingExpression called XPath. This property allows you to identify which element(s) you want to load from the XML data source. It is specific to this data provider and allows for any XPath expression to be supplied. This will succesfully load each of the "/taxrecord/entry" nodes into the ListBox, but the data itself will show up as a blank line. This, of course, is because the data is really an XmlNode object which the ListBox has no idea how to display. To fix this, we supply a DataTemplate to render our data:
< ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock FontWeight="Bold" Text="{Binding XPath=@name}" /> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding XPath=@state}" /> <TextBlock Text=", " /> <TextBlock Text="{Binding XPath=@age}" /> <TextBlock Text=", " /> <TextBlock Text="{Binding XPath=@income}" /> </StackPanel> </StackPanel> </DataTemplate> </ListBox.ItemTemplate>
Again, we use the XPath property to define what piece of information we are binding to -- attributes of our entry in this case. This template will give us the format we are looking for:
We can add sorting, filtering and grouping using the normal CollectionViewSource support. Here is a XAML file which will present the above UI complete with sorting by the age element. Notice how the XPath expression now moves to the CollectionView. This is because the CollectionViewSource creates a ListCollectionView to manage the XML nodes because the data provider supports the IList interface. The binding expression on the listbox is now simply a binding to the collection view.
< Window Title="AsyncDataBind" Height="300" Width="300" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:cm="clr-namespace:System.ComponentModel;assembly=WindowsBase" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
< Window.Resources>
<XmlDataProvider Source="largeXmlFile.xml" IsAsynchronous="True" x:Key="xmlData" XPath="/taxrecords" />
<CollectionViewSource x:Key="collView" Source="{Binding Source={StaticResource xmlData},XPath=entry}"> <CollectionViewSource.SortDescriptions> <cm:SortDescription PropertyName="@age" Direction="Ascending" /> </CollectionViewSource.SortDescriptions> </CollectionViewSource>
</Window.Resources>
<Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions>
< ListBox Name="lb1" Margin="10" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource collView}}">
<ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock FontWeight="Bold" Text="{Binding XPath=@name}" /> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding XPath=@state}" /> <TextBlock Text=", " /> <TextBlock Text="{Binding XPath=@age}" /> <TextBlock Text=", " /> <TextBlock Text="{Binding XPath=@income}" /> </StackPanel> </StackPanel> </DataTemplate> </ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding ElementName=lb1, Path=Items.Count}" /> <TextBlock Text=" Items" /> </StackPanel> </Button>
</Grid> </Window>
Data binding in WPF is extremely powerful -- I am constantly amazed at how much procedural code you can dump in favor of markup with creative bindings. In the next post I'll talk a bit more about asynchronouus bindings outside of the two data providers.
Until then..
I was preparing a sample memory leak application for an Advanced C# class at Microsoft this past week and debugging through it with SOS.DLL ("Son of Strike"). My prepared application was an ASP.NET application that would leak memory by holding references to the page objects after they had completed their work. I did this by having the page hook up an event handler to a global event and then never remove the handler.
This, of course, is bad form because the System.Web.UI.Page object is intended to be a transient object - it goes away at the end of the request - in production code, I would really bind the event to a handler in global.asax instead. But as I said, this was a sample.
So, I was debugging through it prior to the class just to make sure it exhibited the behavior I was looking for. I run the web page a few times, noted my working set going up and never coming back down. Next, I used ADPLUS.VBS to take a hang dump of the process. I then loaded this dump up into WinDBG and started poking around. First, I looked at the heap and sure enough I saw a bunch of page objects:
0:000> .load sos
0:000> !DumpHeap -stat
total 36955 objects
Statistics:
MT Count TotalSize Class Name
7b4ecd7c 1 12 System.Windows.Forms.ButtonInternal.ButtonPopupAdapter
7b481f00 1 12 System.Windows.Forms.LinkLabel+LinkComparer
7b475ca8 1 12 System.Windows.Forms.FormCollection
7b474f8c 1 12 System.Windows.Forms.Layout.DefaultLayout
7b4749e0 1 12 System.Windows.Forms.ClientUtils+WeakRefCollection
7b473ca8 1 12 System.Windows.Forms.Layout.ArrangedElementCollection
7a755834 1 12 System.Diagnostics.PerformanceCounterCategoryType
7a753394 1 12 System.Diagnostics.TraceOptions
7a71a710 1 12 System.Net.TimeoutValidator
.......
00166030 891 169744 Free
054d24d4 3128 187680 System.Web.UI.LiteralControl
0548cbd4 519 197220 ASP.default_aspx
791242ec 1545 297960 System.Collections.Hashtable+bucket[]
79124670 1185 1090500 System.Char[]
79124228 11961 1279380 System.Object[]
790fa3e0 19149 1561392 System.String
Total 110069 objects
So, next I used DumpHeap to just look at this specific type by giving it a metadata token:
0:000> !DumpHeap -mt 0548cbd4
Address MT Size
.....
01854ff0 0548cbd4 380
01860130 0548cbd4 380
0186b2b4 0548cbd4 380
018773f8 0548cbd4 380
01882538 0548cbd4 380
0188d6bc 0548cbd4 380
01898840 0548cbd4 380
018a39c4 0548cbd4 380
018aeb48 0548cbd4 380
total 519 objects
Statistics:
MT Count TotalSize Class Name
0548cbd4 519 197220 ASP.default_aspx
Total 519 objects
I then used the very cool GCRoot command to determine why a page instance was still rooted and therefore not collectable.
Note: it appears that GCRoot doesn't work well inside VS.NET 2005 - apparently the SOS debugging extension is using some debugger API which isn't fully supported in VS.NET, so you need to familiarize yourself with WinDBG to do this.
0:000> !gcroot 018aeb48
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 3a8
Scan Thread 2 OSTHread e8
Scan Thread 3 OSTHread 1a8
Scan Thread 6 OSTHread 7d4
Scan Thread 7 OSTHread 2b4
Scan Thread 8 OSTHread fdc
Scan Thread 9 OSTHread eac
DOMAIN(001E5E08):HANDLE(Pinned):12312f0:Root:0226c498(System.Object[])->
018af940(System.EventHandler)->
0186c0ac(System.Object[])->
018af920(System.EventHandler)->
018aeb48(ASP.default_aspx)
With this output, I can tell that my default_aspx object is being kept alive through an EventHandler as I expected. The interesting thing about this output is I cannot tell which event handler is keeping it alive - i.e. there is nothing in this root list that points to a specific object holding it other than a object[]. That essentially means this is a static event and there isn't an actual object around on the heap for it. This just makes the debugging exercise more interesting - after all if it were an instance event then I would see the class name at the top of the root list and we could stop right here.
So, my next step is to dump the event handler to try to identify what method it is wrapping - I could then search the code for this method and find out where it is being bound. I dump out the EventHandler object:
0:000> !do 018af920
Name: System.EventHandler
MethodTable: 7910d61c
EEClass: 790c3a7c
Size: 32(0x20) bytes
(C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
MT Field Offset Type VT Attr Value Name
790f9c18 40000f9 4 System.Object 0 instance 018aeb48 _target
79109208 40000fa 8 ...ection.MethodBase 0 instance 00000000 _methodBase
790fe160 40000fb c System.IntPtr 0 instance 88962888 _methodPtr
790fe160 40000fc 10 System.IntPtr 0 instance 0 _methodPtrAux
790f9c18 4000106 14 System.Object 0 instance 00000000 _invocationList
790fe160 4000107 18 System.IntPtr 0 instance 0 _invocationCount
From this I get the internals of the EventHandler object. It gives me some specific information:
- _target is the object the delegate is holding onto - my default_aspx in this case. The value better match up with my original object!
- _methodBase is used for dynamic code and is null here.
- _methodPtr is the method associated with the instance, or possibly a dynamically generated thunk for static methods. This is what I'm interested in.
- _methodPtrAux is used for static methods; this holds the method descriptor and the _methodPtr is a dynamically generated block of code to remove the this reference. Noting that this is null, I can infer that this is an instance method bound to the delegate.
- _invocationList and _invocationCount are used for Multicast Delegates -- these are both zero indicating that this is a single delegate and there is no chain to follow.
My next step is to try to get a valid method name from the _methodPtr so I convert that to hex and pass it into IP2MD.
0:000> !ip2md 0n88962888
Failed to request MethodData, not in JIT code range
This is a pretty common error and simply means that the method may not have been JITted yet, or may be a dynamic block of code which never went through the JIT compiler. In .NET 1.1 we could start dumping method descriptors and trying to match up the address (DumpClass -md) for this class and it's base class.
However, under .NET 2.0 this rarely works anymore - the address doesn't appear to ever match up to a valid descriptor. However, it clearly is part of the managed heap due to it's address. So, failing to locate this address, I tried disassembling the code:
0:000> !u 0n88962888
Unmanaged code
054d7748 e862289b74 call mscorwks!LogHelp_TerminateOnAssert+0x3f5f (79e89faf)
054d774d 5e pop esi
054d774e cc int 3
054d774f cc int 3
054d7750 38c8 cmp al,cl
054d7752 48 dec eax
054d7753 05a0774d05 add eax,54D77A0h
054d7758 0100 add dword ptr [eax],eax
054d775a 0011 add byte ptr [ecx],dl
054d775c 0000 add byte ptr [eax],al
Humph. This doesn't even look like valid code to me.. this looks like random data, so I dumped it out:
0:000> dd 0n88962888
054d7748 9b2862e8 cccc5e74 0548c838 054d77a0
054d7758 11000001 90000000 054d77a0 11000002
054d7768 90000004 00000000 054d77a0 00000000
The third and fourth DWORD look interesting because they appear to fall in the managed heap as well -- so I began to dump them out trying to figure out what they were. I found that the first value is a method descriptor:
0:000> !dumpmd 0548c838
Method Name: _Default.OnDatabaseHasChanged(System.Object, System.EventArgs)
Class: 054ab574
MethodTable: 0548c86c
mdToken: 06000004
Module: 0548c35c
IsJitted: no
m_CodeOrIL: ffffffff
It is the real method bound to the delegate instance. The other DWORD appears to be an metadata reference to the event owner itself:
0:000> !dumpmt 054d77a0
EEClass: 0551940c
Module: 048ac9ec
Name: DatabaseMonitor
mdToken: 02000002 (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\leakypage\2a399ab5\b1e04c63\App_Code.onwg1zqj.dll)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 8
From here I can see the module that this code is defined in (the dynamically generated App_Code directory) and the name ( DatabaseMonitor). This gives me enough information to stop here and begin looking at the code itself - specifically where default.aspx binds it's OnDatabaseHasChanged method to the DatabaseMonitor static class defined somewhere in the App_Code directory. If I didn't have the source code available, I could locate the module and then save it out to a file through the savemodule command:
0:000> lm m App_Code_onwg1zqj
start end module name
04de0000 04de8000 App_Code_onwg1zqj C (no symbols)
0:000> !savemodule 04de0000 c:\appcode.dll
3 sections in file
section 0 - VA=2000, VASize=504, FileAddr=200, FileSize=600
section 1 - VA=4000, VASize=2c8, FileAddr=800, FileSize=400
section 2 - VA=6000, VASize=c, FileAddr=c00, FileSize=200
I could then ILDasm or Reflector the generated assembly and look for my bug from that.
I love SOS and WinDBG.. if only they could match up managed source code to the image I'd abandon VS.NET as a debugger altogether..
Recording telephone conversations with TAPI
A colleague of mine, Kev Jones, has posted some information on using a detached SQL Server database for driving VSTS unit tests which works great if you need a full blown SQL implementation. However, if all you want is a simple data feed, you can also use an Excel file, and as it turns out, it's pretty easy to do.
Let's start by stealing Kev's sample, hopefully he won't mind -- say I have a starship class with a FirePhotonTorpedo method:
public class Enterprise { private int torpedosLeft = 10;
public int FirePhotonTorpedo(int count) { if (torpedosLeft < count) throw new ArgumentException("count");
torpedosLeft -= count; // Instruct bridge officer to "Fire!" return torpedosLeft; } }
The equivalent unit test might look something like:
[TestMethod] public void TestFirePhotonTorpedo() { Enterprise target = new Enterprise(); int torpedoCount = 5; int expected = 5;
int actual = target.FirePhotonTorpedo(torpedoCount); Assert.AreEqual(expected, actual, "FirePhotonTorpedo did not return the expected value."); }
This code just tests a specific case -- I would also need to write other unit tests for edge cases and exceptional cases. I can do this several different ways I could do this:
1) Write a unit test for each specific case passing each value and testing the expected result. 2) Put all the tests into this one unit test function -- asserting each validation as we go. 3) Pull the input and expected out of a database and run them through the function.
This last step, as Kev details can be done from a SQL database - either one reachable by all the people who might run the unit tests, or as his blog shows from a .MDF file you check in with the project and then locally attach prior to running the unit tests. Hit the link above for the details on that.
So, to make an Excel data-driven test, the first step is to create an Excel document with columns for each of our pieces of data. My sample Excel document has the following columns:
| ID |
A unique number identifying the row so we can use the Random data driven test |
| torpedoCount |
The input for our unit test |
| expected |
The expected result coming out of the function |
| shouldFail |
Whether the function will throw an exception. |
I can then enter a single test on each row of the given worksheet. Here's a screen shot --

You can create multiple "tables" by adding additional sheets to the worksheet. Each sheet can be named as appropriate; I'm naming this one "TorpedoData".
Now, I need to add the appropriate attribute to my test case to indicate that it should pull the data from a data source and run the method once per row found. The key is that any ADO.NET data source can be used. Here I will specify my Excel file which is named "UnitTests.xls" and indicate that the table itself is a particular sheet within the Excel document "TorpedoData":
[TestMethod] [DeploymentItem("Tests.xls")] // Copies the file to the deployment directory [DataSource( "System.Data.OleDb", // The provider "Provider=Microsoft.Jet.OLEDB.4.0;Data Source='Tests.xls';Persist Security Info=False;Extended Properties='Excel 8.0'", "TorpedoData$", // The table name, in this case, the sheet name with a '$' appended. DataAccessMethod.Sequential)] public void TestFirePhotonTorpedo() { }
I also need to update the test case itself to use the data -- we do that through the TestContext.DataRow property that gives us access to the current row of data from our data source:
public void TestFirePhotonTorpedo() { Enterprise target = new Enterprise(); int torpedoCount = Convert.ToInt32(TestContext.DataRow["torpedoCount"]); int expected = Convert.ToInt32(TestContext.DataRow["expected"]); bool shouldFail = (bool)TestContext.DataRow["shouldFail"];
TestContext.WriteLine("Running test with TorpedoCount={0}, ExpectedCount={1}, ShouldFail={2}", torpedoCount, expected, shouldFail);
try { int actual = target.FireTorpedos(torpedoCount); Assert.AreEqual(expected, actual, "FireTorpedos did not return the expected value."); } catch (Exception ex) { if (!shouldFail) Assert.Fail(string.Format("FireTorpedo threw exception {0}", ex.Message)); } }
So, here we will grab the data from the current row, converting it as necessary to the appropriate types, output a test line just to prove that we executed the method more than once and then run the test. The test will compare the result with the expected database result and output a failure if they aren't the same. If an exception is thrown, then the shouldFail must be true or that will be considered a failure.
This approach allows me to run through different scenarios very easily and I can just store the Excel worksheet right with my unit tests - make sure it's deployed to the target deployment directory (either through a DeploymentItem attribute, or through the test run configuration). The size of my table here is about 14K, compared to the equivalent .MDF file which is over 2M for the same data. If I didn't want to hardcode the filenames and such, I can also use an app.config file for the unit test and put the information there - just as Kev details.
Cool stuff indeed.
One issue that's always a struggle with building reusable components is managing asynchronous operations. The problem is that depending on the type of application that is going to use the component, the thread used for callbacks and events may or may not be important. For example, with Console based applications, callbacks on different threads might be ok - at least as long as the application itself ensures thread safety. But, with a Windows Forms application, threading is critical - you are not allowed to touch UI controls from any thread other than the main one and so we end up with the Control.BeginInvoke logic in each of our callbacks which sucks.
Enter the Synchronization Context - a new feature of .NET 2.0. Here's how it works:
You derive your component from System.ComponentModel.Component.Component and provide your typical asynchronous function -- in this example, we will build a PiCalculator:
public class PiCalculator : Component { public PiCalculator(); public object CalculatePi(int digits, object stateData); public void CancelAsync(object asyncTask); }
Here, what we've done is create a new component with a default constructor and a method called CalculatePi which takes the number of digits, an optional piece of state data and returns an object. With this component, I'd like to have multiple outstanding asynchronous operations and so I need some way to track each one to identify it when it completes, and also to cancel it if it runs too long.
Microsoft's general pattern for this is to use the stateData parameter and have the application pass in some unique value to identify the request.
This is an ok way to do it, but it puts several restrictions on our implementation:
1) The state data must be supplied 2) The state data must be unique for each operation
In addition, there's the possibility of a race condition if I re-use the state data after canceling an operation. So, to avoid all of these issues, I have the component return the task identifier as part of the request - this is the object return value coming back from CalculatePi. I can then use that object to identify the results, and I can pass it into the component's CancelAsync method to cancel a pending request.
So, how do I get my results? Through a delegate callback of course! I need to create a delegate signature that uses it. The general EventHandler callback signature is: void Method(object sender, EventArgs e) so, we'll use a derivative of this as our delegate:
public delegate void PiCalculationCompletedEventHandler(object sender, PiCalculationEventArgs e);
Then we'll create the class which will be used to report the results. The class is pretty simple - just a data holder really:
public class PiCalculationEventArgs : EventArgs { private int _digits; private string _value; private bool _canceled; private object _stateData; private object _taskId;
public object TaskId { get { return _taskId; } }
public object State { get { return _stateData; } }
public bool Canceled { get { return _canceled; } }
public int Digits { get { return _digits; } }
public string Result { get { return _value; } }
internal PiCalculationEventArgs(object taskId, int digits, string value, object stateData, bool canceled) { _digits = digits; _value = value; _canceled = canceled; _stateData = stateData; _taskId = taskId; } }
Now we can implement out component. First, we'll add a Completed event:
public event PiCalculationCompletedEventHandler CalculationComplete;
This will be used by the application to hook into the results of our component's calculation. The next part is the key to all of this - AsyncOperation. The AsyncOperation class is new to 2.0 and provides the facility to perform callbacks on the appropriate thread. Essentially, it acts as a callback mediator - detecting the type of thread it was created on and then providing the appropriate marshaling code for it's internal delegate. You utilize the callback through a new delegate type -- SendOrPostCallback. We tie our callback to this delegate type and it will marshal us to the correct threading model. We can then execute our own internal CalculationComplete event. Here's the basic skeleton: First, we will create an internal contained class which will be used to track the request. This will actually be the object type we return from the CalculatePi method:
class AsyncStateData { public AsyncOperation asyncOperation; public volatile bool canceled = false; public volatile bool running = true;
public AsyncStateData(object stateData) { asyncOperation = AsyncOperationManager.CreateOperation(stateData); } }
Notice the call to AsyncOperationManager.CreateOperation? This is where we create our AsyncOperation class and this factory creator is the context detector. We will have an AsyncOperation for each event we intend to fire back into the client - in our case one for each Pi calculation. But, I could also create a single instance for the client as well - this is actually what is done in the ITapi3 component to allow it to be integrated onto a Windows Form even though events are being received on a background thread. The Tapi events are always fired on the appropriate thread - the background one for non-WinForms apps and the UI thread for WinForms apps.
Next, we will create our internal callback - this is the callback that will actually be fired internally and then call the real application event, so we'll hook that up with an anonymous delegate in the constructor of our component:
private SendOrPostCallback completionMethodDelegate;
public PiCalculator() { completionMethodDelegate = delegate(object evt) { // Called on the synchronization thread. if (CalculationComplete != null) CalculationComplete(this, (PiCalculationEventArgs)evt); }; }
Now, let's implement our CalculatePi method - it's pretty simple, we'll use an asynch delegate to our internal calculator, invoke it and return the AsyncStateData structure we create to identify each task submitted to the component. Then the calculation will be performed on a thread pool thread and we'll callback to the client when it is finished.
public object CalculatePi(int digits, object stateData) { PiDelegate piDel = InternalCalculatePi; AsyncStateData asyncData = new AsyncStateData(stateData); piDel.BeginInvoke(digits, asyncData, delegate(IAsyncResult ar) { piDel.EndInvoke(ar); }, null); return asyncData; }
CancelAsync is pretty simple too -- it will simply set the Canceled flag of the request:
public void CancelAsync(object asyncTask) { AsyncStateData asyncData = asyncTask as AsyncStateData; if (asyncData != null && asyncData.running == true) asyncData.canceled = true; }
Now, we need to implement our PiCalculator. We're going to cheese out here and just "pretend" to calculate Pi since that wasn't really the point of this component We'll define a delegate to use for the asynch execution and then implement our function:
private delegate void PiDelegate(int digits, AsyncStateData asyncData); private void InternalCalculatePi(int digits, AsyncStateData asyncData) { string PI_DIGITS = "3.141592637309238932482438234724782347234"; if (digits > PI_DIGITS.Length - 2) digits = PI_DIGITS.Length - 2;
// This would be a real calculator here.. int completedDigits = 0; for (; !asyncData.canceled && completedDigits < digits; completedDigits++) { Thread.Sleep(1000); }
asyncData.running = false; string data = PI_DIGITS.Substring(0, completedDigits + 2); asyncData.asyncOperation.PostOperationCompleted( completionMethodDelegate, new PiCalculationEventArgs(asyncData, digits, data, asyncData.asyncOperation.UserSuppliedState, asyncData.canceled)); }
The key to the callback is the invocation of the PostOperationCompleted method from the AsyncOperation instance we created. It is what calls our handler which will in turn call the client. Once PostOperationCompleted is called, no further work may be done with the AsyncOperation. So, it's not appropriate for progress reporting - instead you can use PostOperateration for that which allows for multiple calls.
Now, when using this component, we can simply call it as expected:
static void Main(string[] args) { ArrayList taskIds = new ArrayList(); PiCalculator piCalc = new PiCalculator(); piCalc.CalculationComplete += delegate(object sender, PiCalculationEventArgs e) { Console.WriteLine("[{0}] {1} = {2}, Canceled = {3}", Thread.CurrentThread.ManagedThreadId, e.Digits, e.Result, e.Canceled); }
foreach (string s in args) { taskIds.Add(piCalc.CalculatePi(Convert.ToInt32(s), s)); }
Console.WriteLine("Waiting for results .. press ENTER to cancel."); Console.ReadLine();
foreach (object task in taskIds) { piCalc.CancelAsync(task); }
Console.WriteLine("Press ENTER to terminate"); Console.ReadLine(); }
Here's the output:
Waiting for results .. press ENTER to cancel. [3] 10 = 3.1415926373, Canceled = False [4] 11 = 3.14159263730, Canceled = False [5] 14 = 3.14159263730923, Canceled = False [5] 15 = 3.141592637309238, Canceled = False [5] 17 = 3.14159263730923893, Canceled = False
Press ENTER to terminate [5] 20 = 3.1415926373092389324, Canceled = True
Not real exciting right? Notice the thread id in the [brackets] is different for some of the callbacks. This indicates we are getting called back on different threads - certainly not the main thread which is waiting for console input. The cool part of this component is that I can use it exactly the same way in a Windows Forms application! So, I don't have to know that the callback is on a different thread! I don't have to worry about doing a BeginInvoke or Invoke to get back to the UI thread. So, here's an example WinForm application:
public partial class CalcPiTestForm : Form { ArrayList _pendingTasks = new ArrayList();
public CalcPiTestForm() { InitializeComponent(); }
private void CalcPiTestForm_Load(object sender, EventArgs e) { piCalculator1.CalculationComplete += new UserMath.PiCalculationCompletedEventHandler(piCalculator1_CalculationComplete); }
void piCalculator1_CalculationComplete(object sender, UserMath.PiCalculationEventArgs e) { lock(_pendingTasks) { _pendingTasks.Remove(e.TaskId); }
// No need to do BeginInvoke here!! listBox1.Items.Add(string.Format("[{0}] {1} = {2}, Canceled = {3}", System.Threading.Thread.CurrentThread.ManagedThreadId, e.Digits, e.Result, e.Canceled)); }
private void btnCalculate_Click(object sender, EventArgs e) { if (maskedTextBox1.Text.Length > 0) { lock(_pendingTasks) _pendingTasks.Add(piCalculator1.CalculatePi(Convert.ToInt32(maskedTextBox1.Text))); } }
private void btnCancel_Click(object sender, EventArgs e) { lock(_pendingTasks) { foreach (object task in _pendingTasks) piCalculator1.CancelAsync(task); } } }
Notice the output here -- we haven't done anything special with the component's callbacks, but now the callback is always on thread [1]. This is the magic of AsyncOperation and synchronization contexts. Now go out there and write some thread-aware components! If you'd like this entire sample, here is the project: http://www.julmar.com/samples/asyncop.zip
I love anonymous delegates - I think they are extremely useful and allow me to solve some problems in very elegant ways. However, once you really get into them, you start to see the dark side of anonymous delegates and that is unregistration.
Here's the basic problem: when binding an instance delegate to an event to handle some activity, the delegate will cache off the instance reference - thereby keeping the reference alive. So for example, if I had a form which wanted to process some activity from an object:
class Publisher { public event EventHandler OnEvent; ... }
class MainForm : Form { Publisher _pub = new Publisher(); void ActivateChildForm() { ChildForm f = new ChildForm(_pub); f.Show(); } }
class ChildForm : Form { public ChildForm(Publisher pub) { pub.OnEvent += ProcessEvent; } void ProcessEvent(object sender, EventArgs e) { listBox1.Items.Add("Event was fired!"); } }
When the ChildForm instance is closed, the form won't be collected because the Publisher (_pub) is holding a reference to it. This is easily fixed by adding some code into the FormClosing event:
class ChildForm : Form { Publisher _pub;
public ChildForm(Publisher pub) { _pub = pub; _pub.OnEvent += ProcessEvent; }
void FormClosing(object sender, FormClosingEventArgs e) { _pub.OnEvent -= ProcessEvent; } }
Now, the form will be cleaned up when it's closed. This is pretty standard stuff, and most people that have been using .NET for a while know all this. Here's the rub: with .NET 2.0, we can simplify the code using anonymous delegates which are really helpful for these single-line processing event handling functions. So, I could recode my handler as:
class ChildForm : Form { public ChildForm(Publisher pub) { pub.OnEvent += delegate { listBox1.Items.Add("Event was fired!"); } this.OnClosing += delegate { pub.OnEvent -= ????? } } }
The issue is that I don't have a reference to the delegate as it's typed. Under the covers, the C# compiler has generated a temporary function (or possibly even pulled it out to a separate inner class) and there's no way for me to get to the underlying function. So, what can I do? Well, the easiest thing to do is to save off the function:
class ChildForm : Form { public ChildForm(Publisher pub) { EventHandler eh = delegate { listBox1.Items.Add("Event was fired!"); } pub.OnEvent += eh; this.OnClosing += delegate { pub.OnEvent -= eh; } } }
Now my code will function properly -- and is significantly reduced in size. Of course, I've lost the benefit of being able to hook up events through VS.NET because it always generates seperate functions and I would need to cache off the Publisher instance as well as my delegate in that case.
So, rule #1, always unregister the event when you are finished. Rule #2, remember that anonymous delegates may be keep your instance alive so unregister them as well, unless the event is to be hooked up throughout the lifetime of the application.
I've posted a new version of ATAPI.NET which supports consultation transfers and a simple phone sample that shows off how to use the features. In addtiion, this version of ATAPI.NET has a couple of bug fixes that were rolled in from a production project over the past couple of weeks to fix some weird startup/shutdown issues when a LINE_REINIT is reported by TAPI. You can get the new code from our samples link -- http://www.julmar.com/samples.htm
Forwarding lines with ATAPI.NET is simple and easy (assuming, of course, that the underlying TSP supports it).
The first step is knowing whether a given line device even supports forwarding. This is trivial:
TapiManager mgr = new TapiManager("ForwardingTest");
foreach (TapiLine line in mgr.Lines) { if (line.Capabilities.SupportsForwarding) { Console.WriteLine("Line {0} supports forwarding!", line.Name); } }
Once we've identified a specific line, we can look at each address and get more information such as the types of forwarding supported. For example, we might be able to forward to different numbers based on specific conditions such as whether the call goes unanswered vs. whether the address is in use and returning a busy signal. We might also be able to forward specific inbound callers (very useful to get rid of your bosses calls). We can get this information from the Capabilities of the TapiAddress object:
foreach (TapiAddress addr in line.Addresses) { Console.WriteLine("Forwarding modes supported on {0} are {1}", addr.Address, addr.Capabilities.SupportedForwardingModes); }
We can also retrieve any existing forwarding information through the Status of the TapiAddress:
foreach (ForwardInfo fwd in addr.Status.ForwardingInformatioin) Console.WriteLine("\t{0} to {1}:{2}", fwd.ForwardMode, fwd.DestinationAddressType, fwd.DestinationAddress);
This outputs: "Unconditional to PhoneNumber:1234" on a forwarded line I setup.
Finally, the big question is how to change the forwarding information, this is pretty easy as well. You can set forwarding information on two levels, the entire line (which impacts all addresses), or a specific address. This is done through two methods present on both TapiAddress and TapiLine which are Forward and CancelForward. So, to cancel all forwarding in effect on every line we could do the following:
Console.WriteLine("Canceling all forwards:"); foreach (TapiLine line in mgr.Lines) { if (line.Capabilities.SupportsForwarding) { try { line.CancelForward(); } catch (TapiException ex) { Console.WriteLine("{0} - {1}", line, ex.Message); } } }
Or, to setup the forwarding as above, I can issue a call to the Forward method:
ForwardInfo[] fwdInfo = new ForwardInfo[] { new ForwardInfo(ForwardingMode.Unconditional, 0, "1234") };
foreach (TapiLine line in mgr.Lines) { if (line.Capabilities.SupportsForwarding) { try { line.Forward(fwdInfo, 5, null); } catch (TapiException ex) { Console.WriteLine("{0} - {1}", line.Name, ex.Message); } } }
The ForwardInfo class describes a single forwarding instruction and you pass an array of these info the Forward method to indicate how things are to be managed. Exceptions need to be handled because the TAPI service provider might not allow the particular forwarding at this point in time, or the destination might not be allowed, etc.
Under the covers this will issue a lineForward request with a LINEFORWARDLIST setup for each of the ForwardInfo structures.
That about covers it! Ping me with any questions if you want.
I got hit with this problem from two separate clients last week - a .NET 1.1 application ported to .NET 2.0 is now terminating abruptly for no apparent reason. Well, of course there's a reason - and it's that both applications had "hidden" exceptions being thrown in some background thread that weren't being caught. Under .NET 1.1, the CLR would print any exceptions that occurred on threadpool threads to the console and then return the thread to the pool. In addition, the CLR would silently eat any exception thrown on the finalizer thread (again printing the stack trace to the console). Under .NET 2.0, the behavior has changed and it will cause the AppDomain to be unloaded (yikes!). So, for example:
class BadClass { ~BadClass() { throw new Exception("I'm Bad"); } }
class BadClass {
static void Main()
{
for (; ;) new BadClass();
}
}
The above code would run forever under CLR 1.1, but will terminate immediately when run under CLR 2.0 with an unhandled exception.
Unhandled Exception: System.Exception: I'm bad at BadClass.Finalize() in W:\Projects\TestApp\TestApp\Program.cs:line 11
There are a couple of ways to deal with this - the best is to do all your testing and close the holes. You really shouldn't have unhandled exceptions loose in your program. But for a really large program, or one where you don't know where the exception is happening, Microsoft has given you a couple of options. The first one is the <legacyUnhandledExceptionPolicy> tag which you can put into your app.config file:
<configuration> <runtime> <legacyUnhandledExceptionPolicy enabled="1" /> << B>runtime> < B><configuration>
This will essentially revert the CLR to the 1.1 behavior. Consider it a short-term fix because it will ultimately mask issues with your application that are really bugs.
Along with the above, you can also be notified about unhandled exceptions prior to the AppDomain being unloaded. This is done through several different methods based on the type of application. The first basic method is through the AppDomain.UnhandledException event which is raised for Console applications:
static void Main() { AppDomain.CurrentDomain.UnhandledException += delegate(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine("{0} - IsTerminating = {1}", e.ExceptionObject.ToString(), e.IsTerminating); };
for (; ;) new BadClass(); }
In this case, the application will still be terminated, but we will now get notified right before termination. This allows our application to log the error (using the nifty System.Diagnostics.TraceSource support or through the EventLog) so we at least know what happened.
For Windows Forms applications, things are a little different -- instead of the AppDomain.UnhandledException event being raised, the Windows Forms infrastructure will raise the Application.ThreadException event for exceptions that occur on the primary thread. The default behavior for this handler is to display the friendly System.Windows.Forms.ThreadException dialog:

If the exception occurs in the primary (message pumping) thread, the user will see the above dialog box and be given the choice to terminate the application or not. The code behind it looks something like:
void Application_ThreadException(object sender, ThreadExceptionEventArgs e) { try { // Call user override if (this.threadExceptionHandler != null) { this.threadExceptionHandler(Thread.CurrentThread, new ThreadExceptionEventArgs(e.Exception)); } else { using (ThreadExceptionDialog excptDlg = new ThreadExceptionDialog(e.Exception)) { DialogResult result = excptDlg.ShowDialog(); if (result == DialogResult.Abort) Application.Exit(); } } } catch { } }
That's great for the main thread, but if the exception occurs in a secondary thread, then the application will still be terminated after raising the AppDomain.UnhandledException event. So, for non-UI threads, you must still register the unhandled exception handler on the AppDomain, log the failure and then watch your application die. Having two handlers can be a pain and if you want to have the application terminate on any unhandled exception, you can direct Windows Forms to not catch primary thread exceptions automatically by using the Application.SetUnhandledExceptionBehavior method:
[ STAThread] static void Main() { Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); Application.Run(new Form1()); }
The above line will cause exceptions thrown in the main thread to be unhandled - thereby triggering the AppDomain.UnhandledException event.
For ASP.NET applications, things are also a bit different. First, be aware that any exception thrown during a Page request (the normal Page rendering process) will be handled automatically by ASP.NET and rendered back to the client based on the error settings in web.config. If I throw an exception while rendering the page, then I'll get an HTML response like:
Server Error in '/TestWebSite' Application. Bye, Bye!
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
The important thing here is that the AppDomain continues to run. I can tell ASP.NET to redirect to a specific page through the <customErrors> section of the web.config. Or, I can catch these exceptions and handle them myself through the global.asax Application_Error method (which is hooking the HttpApplication.Error event):
void Application_Error(object sender, EventArgs e) { Exception ex = Server.GetLastError(); Context.ClearError(); // Stop default error reporting Response.Write("Application Error:"); Response.Write("" + Server.HtmlEncode(ex.ToString()) + ""); }
A more popular way to display errors is to cache off the last error in a session variable and then execute Server.Transfer to some error.aspx page:
void Application_Error(object sender, EventArgs e) { Exception ex = Server.GetLastError(); Session["LastException"] = ex; Server.Transfer("myerrorpage.aspx"); }
Unfortunately, just like Windows Forms, if an unhandled exception occurs on a separate thread (such as a Threadpool thread), or outside the request processing framework, then the AppDomain is terminated. This has significant consequences for ASP.NET applications - because the website itself is cycled. This means you have lost session state and your users are going to notice the website outage. Most of the time, there isn't much information to work off of -- you end up with a cryptic Event Log entry like:
EventType clr20r3, P1 w3wp.exe, P2 6.0.3790.1830, P3 4333d6f1, P4 app_web_5hu0gabf, P5 0.0.0.0, P6 4415a8be, P7 7, P8 a, P9 system.exception, P10 NIL.
In order to get more information, we need to hook the AppDomain.UnhandledException event and then log the information, just like we have for Windows Forms and Console applications. We could put a handler into our global.asax, but that would only handle that one web application. It would be better to create a new HTTP Module with the event handler and hook that up through the web.config. Luckily for me, Microsoft has already published a KB article which shows off this exact solution - http://support.microsoft.com/?id=911816. In this article, Microsoft details creating a new HttpModule class which will log the exception information to the event log so you will know exactly where the exception occurred and can provide your own exception handling. Unfortunately, without the above <legacyUnhandledExceptionPolicy> entry, the website will still be cycled.
Bottom line is: be aware of this new, breaking change -- look for places where you might not be handling exceptions properly and implement good audit trail mechanisms to log failures when/if they occur. This will allow you to find and fix issues before they become production problems as you port your legacy code over to .NET 2.0!
So, a question was asked "How do I determine what's happening in the TAPI3 wrapper"? The answer is you turn on the internal trace source -- ITapi3 was built with a build in tracing facility to tell you when it had any underlying interface or COM failures and it's easy to activate. First, add an Application Configuration File to your project. Open that file and add the following lines:
<? xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <sources> <source name="ITapiTrace" switchName="tapiSwitch" switchType="System.Diagnostics.SourceSwitch"> <listeners> <add name="MyTraceLog" type="System.Diagnostics.TextWriterTraceListener" initializeData="MyTrace.txt" /> </listeners> </source> </sources> <switches> <add name="tapiSwitch" value="All" /> </switches> </system.diagnostics> </configuration>
This will create a file called "MyTrace.txt" in your working directory. The important line is the source tag which identifies the internal TraceSource object used by the ITapi3 library. Inside this file will be the internal TAPI3 calls being made for your application. As an example, the following trace shows me that several underlying COM errors occurred in the running of a simple TAPI3 application -- it was unable to retrieve the ITTerminal interface from the ITAddressEvent interface (which actually isn't really an error), failed to open the line (because Unimodem won't allow the media type VOICE to be passed for my modem), and failed to set the play list for this MSP -- [0x80040216] is actually a DirectShow error [VFW_E_NOT_FOUND].
ITapiTrace Verbose: 0 : Creating ITTAPI instance ITapiTrace Verbose: 0 : Hooking up connection sink to ITTAPI interface ITapiTrace Information: 0 : ITTapi::put_EventFilter(0x8001F) hr=0x0 ITapiTrace Error: 0 : COM Hresult 0x80040004 "The MEDIATYPE passed in to this method was invalid." generated at JulMar.Tapi3.TapiException.ThrowExceptionForHR(Int32 hr) at JulMar.Tapi3.TTapi.RegisterCallNotifications(ITAddress* pitf, Int16 vbMonitor, Int16 vbOwner, Int32 supportedMediaTypes) at JulMar.Tapi3.TAddress.Open(TAPIMEDIATYPES supportedMediaTypes) ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 5, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 4, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 3, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 2, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 1, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 0, Terminal= ITapiTrace Verbose: 0 : Processing TapiCallNotificationEventArgs: Event=CNE_OWNER, Call=TCall: 171360625 CS_OFFERING ITapiTrace Verbose: 0 : Processing TapiCallStateEventArgs: Call=TCall: 171360625 CS_OFFERING, State=CS_OFFERING, Cause=CEC_NONE ITapiTrace Error: 0 : COM Hresult 0x80040216 "" generated at JulMar.Tapi3.TapiException.ThrowExceptionForHR(Int32 hr) at JulMar.Tapi3.TTerminal.set_MediaPlayList(String[] fileList) at AnsMachine.AutoAttendantForm.AnswerCall() at AnsMachine.AutoAttendantForm.OnCallState(Object sender, TapiCallStateEventArgs e) ITapiTrace Verbose: 0 : Processing TapiCallStateEventArgs: Call=TCall: 171360625 CS_DISCONNECTED, State=CS_DISCONNECTED, Cause=CEC_DISCONNECT_NORMAL ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 5, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 4, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 3, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 2, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 1, Terminal= ITapiTrace Error: 0 : ITAddressEvent::get_Terminal failed hr=0x80040055 ITapiTrace Verbose: 0 : Processing TapiAddressChangedEventArgs: Evt=AE_RINGING, Address=DSSP Line #1 - Address 0, Terminal= ITapiTrace Error: 0 : ITTapi::Shutdown hr=0x0
I got an email question today asking about how to write a .WAV file out to an active call using our TAPI3 wrapper. It's actually pretty easy to do and it follows along with the normal SDK method used in C++. Here's the relevant code (for the full example, download the http://www.julmar.com/samples/tapi3wrapper.zip and look at the AutoAttendant sample).
First, when a new offering call shows up, get hold of the file playback terminal for it and set it up to play your .WAV files. We cannot play the files until the call is answered, but this will essentially "queue" it up.
// Method called when TE_CALLSTATE == OFFERING raised private void AnswerCall() { // Get the playback terminal from the call try { playbackTerminal = activeCall.RequestTerminal(TTerminal.FilePlaybackTerminal, TAPIMEDIATYPES.AUDIO, TERMINAL_DIRECTION.TD_CAPTURE); if (playbackTerminal != null) { playbackTerminal.MediaPlayList = new string[] { "Hello.wav" }; activeCall.SelectTerminalOnCall(playbackTerminal); activeCall.Answer(); } else { MessageBox.Show("Failed to retrieve playback terminal."); activeCall.Disconnect(DISCONNECT_CODE.DC_REJECTED); } } catch (TapiException ex) { } }
Next, watch for the call media to change indicating we have an active stream for our terminal. When that happens, start the playback stream:
// Method called when TE_CALLMEDIA is raised private void OnCallMedia(object sender, TapiCallMediaEventArgs e) { try { if (activeCall != null && e.Event == CALL_MEDIA_EVENT.CME_STREAM_ACTIVE && e.Terminal.Direction == TERMINAL_DIRECTION.TD_CAPTURE && playbackTerminal != null) { playbackTerminal.Start(); SetStatusMessage("File Playback Terminal started "); } } catch (TapiException ex) { } }
Finally, when the file terminal is finished, close and dispose the stream. This is done just to cleanup the resources properly:
// Method called when TE_FILETERMINAL is raised private void OnFileTerminal(object sender, TapiFileTerminalEventArgs e) { // We are interested in TMS_IDLE because we will un-select playback and // select recording if (e.State == TERMINAL_MEDIA_STATE.TMS_IDLE) { if (e.Terminal.Direction == TERMINAL_DIRECTION.TD_CAPTURE && playbackTerminal != null) { try { // Remove the playback terminal activeCall.UnselectTerminalOnCall(playbackTerminal); playbackTerminal.Dispose(); playbackTerminal = null; } catch (TapiException ex) { } } } }
That's pretty much it -- there's a full sample of this that should work with voice modems or any streaming-capable TSP.
Updated: 3/16/05 -- It doesn't appear to completely work with the H.323 provider; the file terminal gets connected but apparently never reports an IDLE state and so never gets disconnected in the above sample. This appears to be true of the Platform SDK samples as well.
New announcement - TAPI3 wrapper for .NET available on our samples page -- http://www.julmar.com/samples/tapi3wrapper.zip
Recently, I released the ATAPI.NET project into the wild for people to be able to easily code up .NET applications that utilize TAPI. That assembly is based on the C API exported from TAPI 2.1 and so uses the older form of TAPI programming. With Windows 2000, Microsoft released a COM version of TAPI - dubbed TAPI 3.0 that was designed to allow VB developers to access the telephony API. However, as I noted in my previous post, Microsoft claims that the object model is too complex and that there are significant issues with accessing it from managed code.
.NET has a built-in facility to access COM objects, it creates .NET wrappers (called RCW's) around the COM interfaces and then allows you to call the COM object as if it were a .NET object. That's great until you need to mesh the COM style of cleanup (AddRef/Release) with the .NET style (GC). Essentially, the COM object doesn't get released until the managed wrapper gets collected. In addition, if you have multiple wrappers for a single interface (TAPI returns the same interface through different methods), the interface won't be released until all the wrappers are collected.
Sometimes, this isn't really a problem - so we keep a COM interface alive a little longer than normal. In TAPI's case however it can be a huge problem because some TAPI service providers will not let you create new calls unless the existing call interface has been released - even if it isn't connected to anything.
So, two options exist today:
1) Don't use TAPI 3.x - use TAPI 2.x where you have more control over the underlying TAPI call handle and can call lineDeallocate yourself. In .NET this means using some wrapper like ATAPI.NET. If you don't need terminal and stream support, this is fine. This is what the newsgroups typically will recommend to people.
2) Call Marshal.ReleaseCOMObject on every outstanding interface. Just plain ugly and extremely error-prone.
Now, a new option exists. I've been in conversations with Matthias Moetje - one of the TAPI MVPs and went back to my original TAPI3 wrapper which was written in MC++ (yuck) and ported it to C++/CLI (which, BTW, is very cool). It has almost everything supported except agents and call center support. It solves the above problem in a couple of steps:
1) It guarantees that the same interface will match 1:1 with a managed object as long as the object hasn't been collected yet. This means the object will only have one holding managed object. 2) It implements IDisposable on most of the wrappers allowing the client to get rid of interfaces immediately. 3) It "auto-disposes" calls when they hit the disconnected state. This can be turned on or off depending on need through the TTapi.AutoDestroyCalls flag
Instead of a Register function, each address has an Open and Monitor method and a Close method. Since you have to pass the address in anyway, I moved the function to that level and made it distinct rather than having you pass in two booleans to indicate intent.
There are some shorthand functions: for example, the TCall object (which represents ITBasicCallControl, ITCallInfo, et.al.) has a SelectDefaultTerminals method which enumerates the streams and hooks up the default static/video terminals for each stream - basically the code that was in every TAPI sample provided by MS. There's also a FindTerminal method on the TStream class which will locate a terminal of the proper media type/direction if it exists.
The library itself follows the TAPI3 interfaces pretty closely - except it combines the various control interfaces together. So for example:
|
Class |
Interfaces |
|
TTapi |
ITTapi, ITTapi2 |
|
TAddress |
ITAddress, ITAddress2, ITAddressCapabilities, ITAddressTranslation, ITLegacyAddressMediaControl, ITLegacyAddressMediaControl2, ITMediaSupport, ITTerminalSupport |
|
TCall |
ITCallInfo, ITBasicCallControl, ITBasicCallControl2, ITStreamControl, ITLegacyCallMediaControl, ITLegacyCallMediaControl2 |
In each case, the same properties and methods are exposed by the reference class and it will do the underlying cast necessary to get to the functionality. The downside of this, is that if the interface isn't supported, the library must throw an exception - in this case a TapiException which holds a string message indicating the failure and an error code (the HRESULT). The TTapi class exposes the TE_xxx events you generally registered a sink for.
I didn't bother to encapsulate the DirectShow library - instead each of the TAPI wrappers provides a QueryInterface method which you can use to get to an interface if necessary. So, for example, if you want to get to the venerable IVideoWindow interface from DirectShow, you can include an interop reference to Quartz.DLL and then do the following on the TTerminal object:
IVideoWindow videoWindow = thisTerminal.QueryInterface(typeof(IVideoWindow)) as IVideoWindow; if (videoWindow != null) { ... }
So, here's an example of using it - I think it's much cleaner that the comparable RCW code, and it's thread-aware and works around the general problem of TAPI3 under .NET through a Dispose mechanism and the TTapi.AutoDestroyCalls flag.
using System; using System.Collections.Generic; using System.Text; using JulMar.Tapi3;
namespace TestTapi { class Program { static void Main(string[] args) { TTapi tapi = new TTapi(); TCall call = null; TAddress modemAddr = null;
tapi.Initialize(); tapi.TE_CALLNOTIFICATION += delegate(object sender, TapiCallNotificationEventArgs e) { Console.WriteLine("New call {0} detected from {1}", e.Call.ToString(), e.Event); }; tapi.TE_CALLSTATE += delegate(object sender, TapiCallStateEventArgs e) { Console.WriteLine("{0}:{4} has changed state to {1} due to {2} - current={3}:{5}", e.Call, e.State, e.Cause, e.Call == call, e.Call.GetHashCode(), (call != null) ? call.GetHashCode() : 0); if (e.State == CALL_STATE.CS_INPROGRESS && e.Call == call) { Console.WriteLine("Dropping call"); e.Call.Disconnect(DISCONNECT_CODE.DC_NORMAL); } };
foreach (TAddress addr in tapi.Addresses) { if (String.Compare(addr.ServiceProviderName, "unimdm.tsp", true) == 0 && addr.QueryMediaType(TAPIMEDIATYPES.AUDIO)) modemAddr = addr; }
if (modemAddr != null) { Console.WriteLine("{0} = {1} ({3}) [{2}]", modemAddr.AddressName, modemAddr.State, modemAddr.ServiceProviderName, modemAddr.DialableAddress); modemAddr.Monitor(TAPIMEDIATYPES.AUDIO); ConsoleKey ki = ConsoleKey.A;
while (ki != ConsoleKey.Q) { // Flip the auto-destroy flag if (ki == ConsoleKey.D) { tapi.AutoDestroyCalls = !tapi.AutoDestroyCalls; Console.WriteLine("Set AutoDestroy to {0}", tapi.AutoDestroyCalls); } // List existing calls else if (ki == ConsoleKey.L) { foreach (TCall _call in modemAddr.EnumerateCalls()) { Console.WriteLine("Existing call found: {0}:{1}", _call, _call.GetHashCode()); _call.Dispose(); // Go ahead and dump it } } // Create a new call else { call = modemAddr.CreateCall("5551213", LINEADDRESSTYPES.PhoneNumber, TAPIMEDIATYPES.DATAMODEM); Console.WriteLine("Created new call {0}:{1}", call, call.GetHashCode()); try { // This will fail if existing call interface is still around (i.e. not disposed) call.Connect(false); } catch (TapiException ex) { Console.WriteLine(ex.Message); } } Console.WriteLine("Press a key to try another call.. Q to quit"); ki = Console.ReadKey().Key; } }
// This will destroy any outstanding interfaces tapi.Shutdown();
// Call should be disposed here.. state will be CS_UNKNOWN if (call != null) Console.WriteLine("{0} {1}", call, call.CallState); } } }
Ok, ok so it's been a while. I've been very busy with two tasks -- first, I spent the last few weeks doing a Guerrilla .NET for DevelopMentor with Rich Blewitt. We had a blast together and it was great to hang out with him. I also spent a day sitting in on DM's new C++/CLI class being taught by the very capable Marcus Heege - incredible stuff which every C++ guy on the Microsoft platform should get into..
The other thing I've been working with is resurrecting an old project of mine - ATAPI which was originally setup to wrap the TAPI 2.x API in an "easy to use" set of C++ classes. I'd ported it to .NET a few years ago but was not really happy with the results. I had the chance to revisit it because of a client's requirement to integrate TAPI into their .NET platform code. So, I spent a couple of weeks working on the codebase again under .NET 2.0 and this time around I'm pretty pleased with the architecture. I wanted something very easy to use, and I think I've achieved that even though it isn't a complete wrapper.
For example, to walk through all the lines and dump out the device classes available - you can simply do this:
using System; using System.Collections.Generic; using System.Text; using JulMar.Atapi;
namespace EnumDevices { class Program { static void Main(string[] args) { TapiManager mgr = new TapiManager("EnumDevices"); mgr.Initialize(); // Start up Tapi
foreach (TapiLine line in mgr.Lines) { foreach (string s in line.Capabilities.AvailableDeviceClasses) Console.WriteLine("{0} - {1}", line.Name, s); } mgr.Shutdown(); } } }
Cool huh?
So.. why not use the TAPI3 COM API you ask? Well, as it turns out, it doesn't work that well with the RCW infrastructure in .NET -- check out http://support.microsoft.com/kb/841712/en-us where Microsoft basically says "TAPI3 is too complicated".. like I needed someone to tell me that..
The ATAPI.NET stuff is available from JulMar's download area - you can get it along with a sample program from http://www.julmar.com/samples/atapinet.zip.
enjoy.
|