This section describes how to create a user interface for your task. The task UI is developed in a separate project and compiled to a separate assembly from the task to simplify localization and maintenance. Other than the DTSTask attribute, the task has no connection to its TaskUI. This section starts by describing how to build a rudimentary custom task UI for the sample task and then moves on to describe how to use the foundation class Microsoft used for building rich task UI.
Building the Sample Task UI (Simple)To start the TaskUI project, step through the same steps used to start any of the custom components. Because they were covered in detail earlier, only the high-level steps are given here.
Adding the FormAt this point, you have a compiling component, but it contains no form and it has no relationship to the sample task. To add the TaskUI form, complete the following steps:
As you can see, the interface for task UIs is called IDTSTaskUI. This interface has four methods:
This code saves the Taskhost reference and then passes it into the form when the GetView method is called. The form sets properties on the Taskhost and on the task as well if you add properties to the task. There is one more thing to do now and that is to connect the task with the task UI. You do this with the DTSTask attribute UITypeName parameter. You will remember that the format for the UITypeName is as follows: UITypeName = "NamespaceTypeName, AssemblyName, Version=AssemblyVersion, Culture=Neutral, PublicKeyToken= PublicKeyToken".
Figure 24.15. Finding the public key token for the TaskUI assembly
After you get the UITypeName right, you're ready to build and install the task and task UI assemblies. Make sure the task assembly is installed in the GAC and the DTS subfolder. Also, make sure the task UI assembly is installed in the GAC. Create a new SSIS project, add the SampleTask to the Toolbox if you haven't already, and drop it onto the package surface. Double-click on the task to open the task UI. Tip After you have opened the task UI inside the designer, the designer caches a reference to the assembly. If you make subsequent changes to the task UI, they are not reflected in the designer until you close down all instances of the designer that have previously opened the task UI. If you make changes to the task UI, close down the designer containing the package you use to test the task UI, rebuild the entire task and task UI project, and reload the test package. That is the only way to consistently ensure that the designer picks up the updated task UI. If you get lost or just want to go straight to the code, you can look at the SampleTask solution in the samples under SAMPLES\SRC\SampleComponents\SampleTask. Building a Sophisticated TaskUI (Complex)Although simple taskUIs might be sufficient for some tasks, if you want your tasks to have the same look and feel as the stock tasks, you need to create a more sophisticated taskUI. Integration Services provides a standard framework for taskUIs. It is the same framework Microsoft used for building all the stock taskUIs and supports the Property Expression page and Expression Builder by default. The taskUI foundation classes and definitions are found in the following assemblies:
Caution When you right-click on the References node for your taskUI project and select Add References, the Microsoft.DataTransformationService.Controls assembly does not show up in the .NET assemblies tab of the Add Reference dialog box. You need to select the Browse tab and search for the assembly. This makes a copy of the assembly while you are designing the UI. You do not need to install this assembly, however, because it is in the GAC. You only need a copy of the assembly while developing the task UI. On machines where SSIS has been installed, you'll find this assembly in the C:\Windows\Assemblies folder or its equivalent. There are two sample tasks that use the framework. StarterTaskUI in the StarterTask sample solution is a rudimentary taskUI you can use for starting your own tasks and taskUIs. CryptoTask is a complete task and taskUI and uses some of the more interesting features of the framework such as custom UITypeEditors. The samples shown here are from the CryptoTask sample code. Only the more interesting aspects of the code are covered here because the taskUI code is somewhat lengthy. You can find both projects in the SAMPLES\SRC\SampleComponents folder. Figure 24.16 shows the CryptoTask taskUI settings node. This taskUI has four custom properties that are typical of the kinds of properties that you might want to support on your taskUIs. The four property types are as follows:
Figure 24.16. The CryptoTask Settings nodeBy now, the taskUI design should be familiar to you. On the left side of the taskUI is the tree view that contains nodes that when selected present you with different property grids or views on the right side of the taskUI. The upper-left of the taskUI shows an icon and description. Each of these sections of the taskUI is customizable. Starting the ProjectTo get started with the taskUI, you can use the same steps as described for the simple taskUI by creating a class library project, deriving the class from IDtsTaskUI, and implementing the Initialize and GetView methods or you can use the StarterTask project and modify it as needed. The following is the code for the CryptoTask IDtsTaskUI interface methods:
If you worked through the simple task sample, this code should look familiar to you. It's almost exactly the same and is how the designer communicates with the taskUI. Notice that the Initialize method stores a reference to the serviceProvider parameter and that in the GetView method a new form called CryptoTaskUIMainWnd is created. The serviceProvider parameter is later used for creating connection managers. When the designer calls GetView on CryptoTaskUI, it simply creates an instance of the CryptoTaskUIMainWnd. The CryptoTaskUIMainWnd is derived from the taskUI framework DTSBaseTaskUI base class. The taskUI code has five source files:
TaskUI Foundation Classes, Interfaces, and TermsThe following list of terms are used throughout the TaskUI Foundation Classes. Understanding these terms will help you better understand the taskUI code.
The IDTSTaskUIHost InterfaceThe IDTSTaskUIHost interface is used for communicating back and forth between the tree and the views. You do not need to implement this interface because it is already implemented in the base DTSBseTaskUI. The IDTSTaskUIHost interface has the following methods and properties: // Add an IDTSTaskUIView derived view to the right side panel void AddView( string viewName, IDTSTaskUIView view, TreeNode parentNode ); // Add an IDTSTaskUIView derived view to the right side panel with an image // Provide an index into the image list for displaying in the tree void AddView( string viewName, int imageIndex, IDTSTaskUIView view, TreeNode parentNode ); // Return the TreeNode associated with a view TreeNode GetViewNode(IDTSTaskUIView view); // Return the view associated with a TreeNode IDTSTaskUIView GetView(TreeNode treeNode); // Add an imagelist for displaying in the tree. void SetImageList(ImageList imageList); // Remove a view from tree void RemoveView(IDTSTaskUIView view); // Remove a view from tree void RemoveView(TreeNode viewNode); // display a specific view void SelectView(IDTSTaskUIView view); // display a specific view void SelectView(TreeNode viewNode); // Display the error message in the error area of the form void DisplayError(string errorMessage); // By default set to true. If set to false the views will be initialized bool FastLoad{ get; set; } // If true the dialog can be closed with the OK button. // true by default if all the views return true from OnValidate method bool CanCommit{ get; set; } The IDTSTaskUIView InterfaceThis interface must be implemented by hosted views and allows the tree view to communicate various changes to the hosted view. The following code shows the IDTSTaskUIView methods and how they are used. public interface IDTSTaskUIView { // If FastLoad from IDTSTaskUIHost is set to true, // this method is called when the view is added to the tree. // Otherwise it will be called when the view is about to // be displayed for the first time void OnInitialize( IDTSTaskUIHost treeHost, // IDTSTaskUIHost that hosts the view. System.Windows.Forms.TreeNode viewNode, // The task that is being edited. object taskHost, // The tree node associated with this view. object connections // The connection managers in the package. ); // Called whenever the view is about to be displayed void OnSelection(); // Called whenever the view is about to be exited. // You can use this method to refuse to leave the view. void OnLoseSelection( ref bool bCanLeaveView, // True if the view can be left. ref string reason // Error message if bCanLeaveView returned false. ); // Called when the OK button is clicked to validate the input. void OnValidate( ref bool bViewIsValid, // Result of the validation, true if validated successfully. ref string reason // Error message if bViewIsValid returned false. ); // Called when OK button is clicked, but only if all OnValidate calls succeeded. void OnCommit( object taskHost // The TaskHost for the task to modify. ); } TaskUI Provided AttributesThe TaskUI foundation classes also provide a set of attributes you can use to modify the hosted views. These attributes control the behavior of the property grid cells and provide a way to easily support properties of different types. EnableExpandable Use the EnableExpandableAttribute to specify whether a user-defined type is to be expandable or nonexpandable in the property grid based on a Boolean property defined by the user-defined type. Caution The type must have a copy constructor defined and expandable types must have the RefreshProperties(RefreshProperties.All) attribute. The following code sample is an example of an expandable type and how to set one up in a view: EnableExpandable("DTSExpandable")] public class ExpandableType { private bool bExpandable = false; private bool bExpanded = true; // Constructor public ExpandableType() { } // Copy constructor public ExpandableType(ExpandableType xt) { this.DTSExpandable = xt.DTSExpandable; this.bExpanded = xt.Expanded; } // Don't show in the designer [Browsable(false)] public bool DTSExpandable { get{ return bExpandable;} set{ bExpandable = value; } } public bool Expanded { get { return bExpanded; } set { bExpanded = false; } } } In the hosted view class, you would have the following code to add the type to the property grid: Public class Class1 { ... Public ExpandableType xpand = new ExpandableType(); [RefreshProperties(RefreshProperties.All)] public ExpandableType ExpandableProperty { get{ return m_xtVal; } set{ m_xtVal = value;} } } The ExpandableProperty is a property on the hosted view, but stores the value in a member variable. Later, when the user closes the taskUI with the OK button and commits the changes, the hosted view can set the task's property value to the value in the member variable. EnableDisable Use the EnableDisable attribute to disable a property in the property grid based on an array of values of another property. One of the two properties needs to have the RefreshProperties attribute, as shown for the EnableExpandable attribute. SortProperties Use this attribute to dictate the order of properties in the property grid. This attribute receives an array of strings containing one of the following:
LocalizablePropertyDescription The LocalizablePropertyDescription attribute provides a way to retrieve a localizable description for the property that will appear in the description section at the bottom of the property grid. LocalizablePropertyName The LocalizablePropertyName provides a way to retrieve a localizable name for the property. LocalizablePropertyCategoryThe LocalizablePropertyCategory provides a way to retrieve a localizable category name that shows in between the properties and categorizes them into groups. Using the TaskUI Foundation ClassesTake a look at the sample code to see some of these pieces working together. In CryptoTaskUIMainWnd.cs, you'll find the constructor for the CryptoTaskUIMainWnd class where the hosted views are created and added to the main form. public partial class CryptoTaskUIMainWnd : DTSBaseTaskUI { #region members private const string Title = "Crypto Task Editor"; private const string Description = "This task encrypts and decrypts text."; public static Icon taskIcon = new Icon(typeof(SSIS2K5.Samples.Tasks.CryptoTask.CryptoTask), "TaskIcon.ico"); #endregion // Construct and pass base class to the // title, taskicon, desc., taskhost, & connections. public CryptoTaskUIMainWnd(TaskHost taskHost, object connections) : base(Title, taskIcon, Description, taskHost, connections) { // Proactively assert that the taskHost is valid Debug.Assert(taskHost != null); // Create a new general hosted view GeneralView generalView = new GeneralView(); // Add the view to the main form this.DTSTaskUIHost.AddView("General", generalView, null); // Create a new settings view SettingsView settingsView = new SettingsView(); // Add the view to the main form this.DTSTaskUIHost.AddView("Settings", settingsView, null); InitializeComponent(); } } The description is what shows up on the top of the taskUI with the icon. The constructor also calls into the base class constructor to ensure that the icon, description, and other elements are correctly displayed. Then, the code adds the two hosted views. The hosted views are where the really interesting things happen. Getting the ConnectionsThe following code shows how to retrieve only the connections of a given type in the internal class FileConnections, which is a TypeConverter:
The FileConnections TypeConverter is used to provide the names of file connection managers in the taskUI by passing it as a parameter to the TypeConverter attribute on the SourceConnection and DestinationConnection properties, as follows: [ Category("Connections"), Description("The destination connection"), TypeConverter(typeof(FileConnections)) ] public string DestinationConnection { get { return _destConnection; } set { _destConnection = value; } } Notice also that the Category attribute creates a grouping of properties based on the string passed as a parameter, in this case "Connections." Handling Connection Manager EventsThe special handling needed for the Connection Manager combo box is done in the PropertyValueChanged event handler in the SettingsView.cs source code, as follows: private const string NEW_CONN = ">New Connection...<"; private void propertyGridSettings_PropertyValueChanged(object s, System.Windows.Forms.PropertyValueChangedEventArgs e) { #region New File Connection for Source path if (e.ChangedItem.PropertyDescriptor.Name.CompareTo("SourceConnection") == 0) { if (e.ChangedItem.Value.Equals(NEW_CONN)) { ArrayList list = null; this.Cursor = Cursors.WaitCursor; if (!((settingsNode.SourceCMName == null) || (settingsNode.SourceCMName == ""))) settingsNode.SourceCMName = null; list = ((IDtsConnectionService)settingsNode.Connections). CreateConnection("FILE"); this.Cursor = Cursors.Default; if ((list != null) && (list.Count > 0)) { ConnectionManager cMgr = (ConnectionManager)list[0]; settingsNode.SourceCMName = cMgr.Name; } else { if (e.OldValue == null) { settingsNode.SourceCMName = null; } else { settingsNode.SourceCMName = (string)e.OldValue; } } } } } In the preceding code, the event handler checks to see whether the <New Connection> option was chosen from the Connection Manager combo box. If it was, the code uses the IDtsConnectionService to create a new connection manager of type FILE. This is how the taskUI launches the correct Connection Manager creation dialog box. The taskUI is perhaps the most complex part of building a custom task; however, with the taskUI foundation class library, you can quickly build a professional looking and functional taskUI that looks identical to the stock taskUIs and automatically take advantage of the built-in property expression hosted view. This introduction didn't cover every aspect of writing taskUIs, but should get you started. You can study the sample code to get a better understanding of how to roll your own taskUIs. Also, you can copy the starter task and taskUI project into a new folder and quickly start building your custom tasks and taskUIs beginning with what is already in those projects. Debugging the TaskBecause of the way the designer (BIDS) executes packages, you must take two different approaches to debugging your custom task based on whether you are executing the package. The designer has a special debugging host called DTSDebugHost that it launches. The host prevents problems with orphaned packages or rogue tasks and helps the designer be more robust, but it also makes it a little tricky to debug components. Debugging Tasks During Design TimeWhen debugging tasks and taskUIs in the designer, it is easiest to create a testing package in another instance of the designer and then attach to the second instance of the designer from the task project designer. Following are the steps to set up for debugging a task.
Figure 24.18. Hitting the breakpoint in the development environmentThis also works for the task code. However, because only certain task code paths are executed during design time, you have to judiciously decide where to put the breakpoint. For example, you can place a breakpoint in a property GET method and then modify the method through the property grid in BIDS. Debugging Tasks During Execution TimeDuring execution, it's a little trickier, but not much. Instead of attaching to a running instance of BIDS, you attach to DTSDebugHost. Also, you need to set a breakpoint in the package so the package doesn't complete execution before you have a chance to attach to it. Finally, if you want to debug the InitializeTask or Validate methods, you need to use the design time debugging method. The designer calls those methods before loading the package into the debug host and executing it. The following steps show one way to debug a task's Execute method:
|