Extending the QueryAttributesTask to highlight selected features

Bryan Baker of the .NET SDK team wrote the following great post on extending a task to modify its behavior and then adding that task to the .NET Global Assembly Cache so it can be reused across applications. 

Note: Also see the follow-up to this post from May 3, 2007.

Highlighting all task results

 

With the Web ADF at 9.2, a website can have one or more tasks to allow users to find features or locations on the map. For example, you might add a QueryAttributesTask to the website to allow users to find cities by name, by typing the first few characters in the name.

 

The out-of-the-box tasks at 9.2 do not automatically highlight found features on the map. Instead, the user can highlight features by clicking individual check-boxes of features in the task results (see graphic, with two cities selected).

 

 

What if you want to have all features highlighted immediately when the task runs? There is currently no setting to enable this. But such immediate highlighting is possible through customization. Let’s look at one relatively easy approach for someone with modest programming skills. We’re looking specifically at the Web ADF for the Microsoft .NET Framework here, by the way.

 

We will extend a task control to modify its behavior. The object-oriented nature of .NET allows us to create a new class (a task, in this case) that inherits all of the behavior and properties of the original class. We only need to add or modify the original class where we need it to act differently from the original class (task). Some aspects of tasks may be difficult or impossible to change, but modifying the task results output is not difficult.

 

The approach we’ll look at can apply to any task that produces a task result in the form of a graphics layer, where users can click on feature check-boxes to highlight them on the map. This includes the SearchAttributesTask, FindAddressTask, FindPlaceTask and QueryAttributesTask. We’ll look specifically at the QueryAttributesTask in this example.

Extending an out-of-the-box task

 

First, I open Visual Studio 2005 and create a new project (not a new website), a Class Library project to be specific. One great thing about .NET is that even though the original class was written in C#, we can extend it in any language—we’ll use VB to show this here. We can put our new class library anywhere; it doesn’t have to go into a web folder. I’ll call my project QuerySelectTaskVB. By the way, we could also use either Visual Basic Express or Visual C# Express, but it’s more difficult to debug with a linked web application.

 

Visual Studio creates the project and adds a new class called Class1.vb. I right-clicked on it in the Solution Explorer and renamed it QuerySelectTaskVB. This also renames the class in the code view—nice.

 

We need to add some references to the project to the libraries we’ll be using. I right-click on the project in Solution Explorer, and chose Add Reference, and in the pop up dialog I select these libraries and then click OK:

  • ESRI.ArcGIS.ADF.Tasks
  • ESRI.ArcGIS.ADF.Web
  • ESRI.ArcGIS.ADF.Web.DataSources
  • ESRI.ArcGIS.ADF.Web.UI.WebControls
  • System.Web

 

To start creating our class, we add some Imports statements at the top of the class file so we can use classes without having to type the full path. We’ll also add a Namespace around our class to better identify it.

 

Imports System
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Data
Imports System.Web
imports System.Web.UI
Imports ESRI.ArcGIS.ADF.Tasks
Imports ESRI.ArcGIS.ADF.Web.Display.Graphics
Imports ESRI.ArcGIS.ADF.Web.UI.WebControls

 

Namespace QuerySelect
  Public Class QuerySelectTaskVB
  End Class
End Namespace

 

We tell the class to extend (inherit) the existing QueryAttributesTask by adding to the class declaration we’ve already created:

 

Public Class QuerySelectTaskVB
    Inherits QueryAttributesTask

End Class

 

The only thing we want to change in the task is how it outputs the results to the TaskResults control. The task creates this output in the standard task method called ExecuteTask(). We want the QueryAttributesTask to create its output as usual, but we will modify the results once they’re created. So we create our own version of ExecuteTask() that overrides the original. Our version will call the base (parent) class version of the method, then modify the results that have been created. The following code goes inside the Class definition we saw above:

 

    Public Overrides Sub ExecuteTask()
        ' QueryAttributesTask creates its results
  &nbs
p;    
MyBase.ExecuteTask()
        ' We'll modify the Results next
    End Sub

 

Once the parent class has run its ExecuteTask, we can modify the results. The results are stored in a task property object called Results (makes sense, eh?). If the query finds features, those get stored in Results as a standard .NET DataSet, which contains one or more DataTable objects. But if nothing was found, the Results contains a different type, a SimpleTaskResult. Also, if the map service isn’t working right, the results might have a DataSet, but the name (caption) just has an error message. Let’s make sure we have valid results:

 

        ' We'll modify the Results next
        ' Make sure features were found

        If TypeOf Results Is DataSet Then

            Dim resultsDS As DataSet = CType(Results, DataSet)
            ' Check for errors during query

            If resultsDS.DataSetName.IndexOf("Error") > 0 Then
               
Return
           
End If

             ' Next we can get the table of results
       
End If

 

Now we can get the table from the DataSet. With the QueryAttributesTask, only one DataTable will be in the DataSet (other tasks may have multiple tables). One more error check: after the task performs its query using the Web ADF common API, it converts the data table to a GraphicsLayer object, so that it contains rendering (symbology) information. If it had any problems making that conversion, then features can’t be selected on the map.

 

            ' Next we can get the table of results
           
' Get the one table in the QueryAttributesTask result
           
Dim resultsTable As DataTable = resultsDS.Tables(0)
 

            ' Make sure no problems creating a GraphicsLayer from the results
           
If resultsTable Is Nothing OrElse Not TypeOf resultsTable Is GraphicsLayer Then

                Return

            End If

            ' Now we can modify the table results

 

Now we can actually set the selection for the features found! The GraphicsLayer, which extends the DataTable type, will have a Boolean-type column indicating whether the row (map feature) is selected. We can get this column, and use it when looping through the features to set all features as selected:

 

            ' Now we can modify the table results
           
Dim graphicsLayer As GraphicsLayer = _
              CType
(resultsTable, GraphicsLayer)
 

            ' Get the column that holds the selection attribute
           
Dim selectedCol As DataColumn = graphicsLayer.IsSelectedColumn
 

            ' Set each feature to selected
           
For Each row As DataRow In graphicsLayer.Rows
                row(selectedCol) = True

            Next

 

That’s it! Now each feature will be selected in the TaskResults tree and also on the map….that is, once our task is inside a website, which we’ll show shortly.

 

We compile the code by choosing Build—Build Solution f
rom the menu (no errors, of course!). This puts a compiled .dll file into the Bin directory of the project (you can see that by looking at the project folder with Windows Explorer).

 

Adding the task to Visual Studio

The easiest way to add the task to a website is when it’s in the Visual Studio toolbox. It can be added manually to an individual website, but requires more editing of the source of the page. Let’s get the task into Visual Studio. There’s more on this in the Developer Help for ArcGIS Server and ArcIMS, so we’ll go over this quickly.

 

First, in the code for the task itself, we add some information so Visual Studio sets properties for task when we drag it onto the page from the toolbox. This goes just above the class declaration:

 

<ToolboxData("<{0}:QuerySelectTaskVB runat=""server"" Width=""200px"" Transparency=""35""" _

        + "BackColor=""White"" TitleBarColor=""WhiteSmoke"" TitleBarSeparatorLine=""False""" _

        + "TitleBarHeight=""20px"" BorderColor=""LightSteelBlue"" BorderStyle=”Outset”’ _

        + "BorderWidth=""1px"" Font-Names=""Verdana"" Font-Size=""8pt"" ForeColor=""Black"">" _

        + "</{0}: QuerySelectTaskVB >")> _

Public Class QuerySelectTaskVB

    Inherits QueryAttributesTask

 

Next we will add the task to .NET’s Global Assembly Cache. This makes it easier to add the task to various websites. Before we can do this, we must create a strong name to sign the task. We open the .NET command prompt and type:

 

sn –k QuerySelectVB.snk

 

This creates a key file (QuerySelectVB.snk) in the folder indicated by the command prompt (probably C:Program FilesMicrosoft Visual Studio 8SDKv2.0). I moved that file using Windows Explorer to the project directory where my task .vb file is located. I then added that to my project by right-clicking on the project in Visual Studio’s Solution Explorer, choosing Add-Existing Item, changing the type to All Files, and clicking on the .snk file. I used the keypair to sign the assembly by right-clicking on the project and choosing Properties. In the Properties page, I clicked the Signing tab. I checked the box Sign the assembly and clicked my .snk file in the drop-down list. Finally, I recompiled the code (Build-Build Solution).

 

Now we can add the task to the Global Assembly Cache. Here’s the easy way: I opened two Windows Explorer windows, one to my project folder’s bin directory where the .dll is located, the other to C:WindowsAssembly. I dragged the QuerySelectTaskVB.dll into the C:WindowsAssembly window. This doesn’t actually move the file. It just registers the assembly so it’s available to all applications on the system.

 

Finally let’s add the task to Visual Studio. Open Visual Studio and then any .aspx page (you can create a website and page if necessary). Open the toolbox if necessary. Right-click where you want to add the task and click Choose Items (you can also create a new tab with New Tab).  In the Choose Toolbox Items dialog, click Browse, and navigate to your DLL’s location. Highlight it and click Open. This adds the task to the list of available assemblies. Make sure its check box is checked, and click OK. This adds the task to the Visual Studio toolbox.

 

Now the task is available whenever you design an ASP.NET web page. Notice it has an icon—the same one as the QueryAttributesTask. It inherits this along with other properties of the parent task.

Using the custom task in a website

 

We can use the task in any website. I created a new website using the Web Mapping Application template. I could have added this website to the same solution as my task code if I’m using Visual Studio (useful for debugging the task), or in a new instance of Visual Studio. I could also open a website created with ArcGIS Server Manager or ArcIMS Web Manager, and add the task to it.

 

Once we have our web page open in Visual Studio, we can drag our new task from the toolbox into the Task Manager. Once it’s there, we can click its “smart tag” in its upper right to set the task’s properties.

 

 

 

Yet another great thing about extending an existing task: we get its designer tools with no coding required. We can set the Task Results container and use Edit the Query to create the query, exactly the same as with the standard QueryAttributesTask (see the Developer Help for tips on that).

 

The demo site for this page uses this custom task to query a water-well point layer for estimated yield. Choose a value from the drop-down list, and up to 50 features will be found. Notice that all features are immediately highlighted. Our custom task acts like the standard QueryAttributesTask, except that it highlights all features returned.

 

Try it out: http://serverx.esri.com/QuerySelectTaskDemo/.

Discussion

 

You can do other interesting things with the Results of the task. For instance, you could add a hyperlink to the task results output by adding the <a href…> tag to the DataSet’s DataSetName property, or to the DataTable.TableName. The hyperlink is displayed in the tree of the task results. You can also modify the DataTable itself, such as by hiding columns you don’t want displayed. Another example would be to display the results as a table, by creating a GridView from the DataTable and getting the HTML output from the GridView. You’d then create a new TaskResultsNode, add the output to that, and set it as the Results instead of the DataSet.

 

You might be tempted to customize other aspects of tasks, such as modifying
the task’s user interface. Although some customizations are possible, you’ll probably find that in many cases it would be easier to author your own task from scratch. The Developer Help has a good discussion of writing tasks, and the Web ADF has a couple of samples that can help get you started.

 

You might have noticed that the task user interface does not have the look-and-feel of other tasks—the colors, fonts, etc. The standard tasks have properties defined in the Theme of the website, specifically in the Default.skin file. If you want the same look-and-feel of the standard tasks, you can create your own control skin by copying properties from one of the standard tasks in Default.skin.

 

We haven’t discussed how to make our custom task available within Manager, so that non-programmers could use your task. This is a much more complex chore than adding the task to Visual Studio. Tasks are configured in Manager using a separate class called a web configurator. The standard task’s web configurators are hard-coded to output information specifically for that task. It is not possible to easily modify them to instead output tags and properties for tasks that extend them. It would be necessary to rewrite most or all of the web configurator in order for it to properly output your task from Manager. The Developer Help has information on writing web configurators, if you are ambitious.

 

Download the code for this sample:

 

 

 

This entry was posted in Services and tagged , , . Bookmark the permalink.

Leave a Reply

7 Comments

  1. Jeremy says:

    Thanks David,

    Those image references weren’t supposed to be there. Darn copy/paste…

    Thanks,
    Jeremy

  2. Bryan Baker says:

    We mentioned in the post that you could add a skin for the control. That would make the control have the same look-and-feel of other tasks and panels. Here’s an example.

    This sets the properties to the “Blue Bridge” skin in the Web Mapping Application template. You would add this to the Default.skin file inside the App_Themes/Blue_Bridge folder inside your website.

    You may have to change the assembly to match your project’s assembly name. If you generated your own strong-name key, you’d also need to update the PublicKeyToken (to get that value, open C:WindowsAssembly with Windows Explorer, find your custom task assembly and copy the value there).

    You may also need to update the TagPrefix with the value assigned by Visual Studio when you drag the task into the ASPX web page.

    If you set the website to a different theme in web.config (or when you generated the site in Manager), you’d need to add this to the appropriate theme in App_Themes, and update the properties for color, images, etc.

  3. Bryan Baker says:

    It would be possible to zoom to the features found, though it would take some work. You would need to add code at the end of the code in the sample, just before the close of the ExecuteTask method. That code would cast the graphics layer to a FeatureGraphicsLayer to get its FullExtent property, which would show the extent of all features found (or you could get geometry of individual features via the GeometryColumnName property).

    The chore would then be zooming the map to this extent. To access the Map control, you can use Page.FindControl with the Map’s ID (typically Map1), unless the Map is inside another control, such as a floating panel, in which case you’d have to find the containing control first.

    Once you have the Map control reference, you can set its extent and call Map1.Refresh(). However, since the Map didn’t initiate the callback, this won’t by itself make the map refresh on the client–so far the Map is only “refreshed” on the server. To tell the client to refresh the map, copy the CallbackResults from the Map to the task, something like:
    CallbackResults.CopyFrom(Map1.CallbackResults). That should cause the Map to do its own callback and refresh the map.

  4. Jorge Vinagre says:

    ArcGis Server 9.2 – Extend the TaskEditor Help

    Hi there.
    I’m having a problem and it seems that there isnt much documentation about this…
    (I´m rather new to this so excuse me if i dont use the correct terms :) )
    I need to extend the Editor task to do the fallowing:

    Add a new button to the panel and, when editing, let the user draw connected lines
    Then i have to capture the list of points when the user double clicks the map so i can use those points to perform some calculations and generate some other lines.

    Can anyone help to find some examples on this?

    Thanks

  5. Bryan Baker says:

    Currently it’s not really possible to extend the EditorTask. The good news is that Service Pack 2 will add some capabilities for customizing the EditorTask. Look for that in the next few weeks.

  6. rwbrown says:

    Bryan,
    This sample is great. I have used it in more than one custom task. The only problem that I am having is that my map control redraws even when there are no results. Have you had the same problem?

  7. TJBNC says:

    I am missing a property set somewhere, I expect, but when you add this class, the UI for the task is visible when the page is loaded. What needs to be changed to hide the UI until the user clicks the task in the tasks panel?