Simulating properties on a bindable list using ITypedList
In this blog post I am going to illustrate how to simulate properties on a bindable list using the ITypedList interface. The ITypedList interface is used by the .NET data-binding mechanism to discover the properties of the items in the list that is being bound. So in fact the ITypedList interface can be implemented by custom collections to customize the properties available for binding on the object to bind to. The .NET data-binding mechanisms use the GetItemProperties(…) method on the ITypedList interface to obtain a PropertyDescriptorCollection to obtain the list of properties available on the object in the list. So, the GetItemProperties(…) method can be overridden to return a custom collection of properties that will be used for data-binding.
Let us take for example, the ‘Distance’ class given below.
public class Distance
{
double _tolerance = 0;
esriUnits _units = esriUnits.esriFeet;
public Distance(){}
public Distance(double ptolerance, esriUnits punits)
{
_tolerance = ptolerance;
_units = punits;
}
public double Tolerance
{
get { return _tolerance; }
set { _tolerance = value; }
}
public esriUnits Units
{
get { return _units; }
set { _units = value; }
}
}
The ‘Distance’ class above contains a tolerance measure and its units as it is public properties. Let’s say that we also have a typed collection ‘DistanceCollection’ of the ‘Distance’ objects.
public class DistanceCollection
{
#region “CollectionBase Members”
public Distance this[int index]
{
get
{
return ((Distance)List[index]);
}
set
{
List[index] = value;
}
}
public int Add(Distance value)
{
return (List.Add(value));
}
public int IndexOf(Distance value)
{
return (List.IndexOf(value));
}
public void Insert(int index, Distance value)
{
List.Insert(index, value);
}
public void Remove(Distance value)
{
List.Remove(value);
}
public bool Contains(Distance value)
{
// If value is not of type Distance, this will return false.
return (List.Contains(value));
}
protected override void OnInsert(int index, Object value)
{
// Insert additional code to be run only when inserting values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnRemove(int index, Object value)
{
// Insert additional code to be run only when removing values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnSet(int index, Object oldValue, Object newValue)
{
// Insert additional code to be run only when setting values.
if (oldValue.GetType() != typeof(Distance))
throw new ArgumentException(“oldValue must be of type Distance.”, “value”);
if (newValue.GetType() != typeof(Distance))
throw new ArgumentException(“newValue must be of type Distance.”, “value”);
}
protected override void OnValidate(Object value)
{
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
#endregion
}
A new datasource could be added in Visual Studio for the ‘DistanceCollection’ type as shown below…
Our goal is to simulate a property called ‘ToleranceInMeters’ on the ‘DistanceCollection’ type so that we have an additional column on the DataGridView specifying the tolerance in meters. In order to achieve this we implement ITypedList on the ‘DistanceCollection’ type and override the GetItemProperties(…) method so that it returns a PropertyDescriptorCollection object with an added PropertyDescriptor object for the ‘ToleranceInMeters’ property.
To return an additional property in the PropertyDescriptorCollection that returns the tolerance in meters, we create a new class called ‘ToleranceInMetersPropertyDescriptor’. And in the GetValue(…) method of the new class we implement the logic to convert the tolerance value from the existing units into meters. In the below code, the ‘ToleranceInMetersPropertyDescriptor’ class has been implemented as an inner class inside ‘DistanceCollection’. We also build the new PropertyDescriptorCollection in the static constructor of ‘DistanceCollection’ and return it inside the GetItemProperties(…) method.
public class DistanceCollection : CollectionBase, ITypedList
{
static DistanceCollection()
{
CalculatePropertyDescriptors();
}
#region “CollectionBase Members”
public Distance this[int index]
{
get
{
return ((Distance)List[index]);
}
set
{
List[index] = value;
}
}
public int Add(Distance value)
{
return (List.Add(value));
}
public int IndexOf(Distance value)
{
return (List.IndexOf(value));
}
public void Insert(int index, Distance value)
{
List.Insert(index, value);
}
public void Remove(Distance value)
{
List.Remove(value);
}
public bool Contains(Distance value)
{
// If value is not of type Distance, this will return false.
return (List.Contains(value));
}
protected override void OnInsert(int index, Object value)
{
// Insert additional code to be run only when inserting values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnRemove(int index, Object value)
{
// Insert additional code to be run only when removing values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnSet(int index, Object oldValue, Object newValue)
{
// Insert additional code to be run only when setting values.
if (oldValue.GetType() != typeof(Distance))
throw new ArgumentException(“oldValue must be of type Distance.”, “value”);
if (newValue.GetType() != typeof(Distance))
throw new ArgumentException(“newValue must be of type Distance.”, “value”);
}
protected override void OnValidate(Object value)
{
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
#endregion
#region “Simulated Property”
private class ToleranceInMetersPropertyDescriptor : PropertyDescriptor
{
UnitConverterClass _converter = new UnitConverterClass();
public ToleranceInMetersPropertyDescriptor(string name)
: base(name, null)
{
}
public override bool CanResetValue(object component)
{
return false;
}
public override System.Type ComponentType
{
get { return typeof(Distance); }
}
public override bool IsReadOnly
{
get { return true; }
}
public override System.Type PropertyType
{
get { return typeof(double); }
}
public override void ResetValue(object component)
{
}
public override object GetValue(object component)
{
Distance d = (Distance)component;
return _converter.ConvertUnits(d.Tolerance, d.Units, esriUnits.esriMeters);
}
public override void SetValue(object component, object value)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
#endregion
#region ITypedList Members
private static PropertyDescriptorCollection propertyDescriptors;
PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
{
return propertyDescriptors;
}
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
{
return “DistanceCollection”;
}
private static void CalculatePropertyDescriptors()
{
PropertyDescriptorCollection origProperties = TypeDescriptor.GetProperties(typeof(Distance));
ArrayList properties = new ArrayList();
foreach (PropertyDescriptor desc in origProperties)
{
properties.Add(desc);
}
properties.Add(new ToleranceInMetersPropertyDescriptor(“ToleranceInMeters”));
propertyDescriptors = new PropertyDescriptorCollection((PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor)));
}
#endregion
}
What do you achieve by all this? Well, we get a property on the ‘DistanceCollection’ type in the Data Sources window in Visual Studio 2005 and when it is dragged on to the form the DataGridView now appears with a an additional column called ‘ToleranceInMeters’.
In the constructor of the form, initialize the binding source for the DistanceCollection and the result will be what you see below.
DistanceCollection _Coll = new DistanceCollection();
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriFeet));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriInches));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriKilometers));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriMiles));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriNauticalMiles));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriDecimalDegrees));
distanceCollectionBindingSource.DataSource = _Coll;
Weird painting issues in custom controls inside ArcMAP running on Windows Server 2003
I am currently developing on a Windows XP machine for the project that I am currently working on. The deployment to the client is on a Citrix farm running Windows 2003 servers. All the user controls I had developed and hosted inside an IDockableWindow were working just fine on my machine. As soon as I deployed on the Windows Server 2003 machine, I started noticing weird behavior with the DataGridView control inside the dockable window like shown below.
Repercussions for not testing on different platforms especially the one the application is going to be deployed on. This time I got lucky and was able to figure out the problem soon enough. So based on an educated guess, I switched the Windows theme from “Classic” to “Windows XP”.
This change solved the problem and the DataGridView display as it should. Even though there could be a whole lot of reasons why this is happeneing, the solution is simple enough and could save somebody a whole lot of frustration.
Things to note when using multi-threading in ArcObjects
Here are some of my observations when using multi-threading in ArcObjects applications
- Different threads can be used to perform operations that are totally disconnected from the objects in the running instance of the ArcGIS applications. In other words, it is safe to create to a thread that connects to a SDE, opens a featureclass and performs some geometry operations on the features in the featureclass and disconnects after completion. Another use case is to open or create a MXD file and add layers to its map contents and close the MXD when done.
- Don’t access objects created in the running instance of the ArcGIS applications in a different thread. Thats is, don’t pass a reference to the map object or the goemetry of the selected feature to a different thread and perform operations on it in the thread. If you do, things start behaving weird.
- When you are automating ArcGIS applications from a different application(different process space and not different thread), use the IObjectFactory interface to create objects in the ArcGIS process space.
Brian Flood has written a couple of blog entries (“Background Processing” and “Background Processing Redux“) on this subject if you are interested in reading more.
Using IEnumGeomteryBind
The IEnumGeometryBind interface gives us an easy way of packaging all the shapes in a FeatureClass or an SelectionSet into a GeometryBag object. I am not sure if using it is more effecient than looping through the data source yourself and adding them to the GeometryBag class. But it sure does save us some lines of code. Here is how you use it.
IEnumGeometry eGeom = new EnumFeatureGeometryClass();
IEnumGeometryBind bind = eGeom as IEnumGeometryBind;
bind.BindGeometrySource(null, FeatureClass);
IGeometryFactory gFactory = new GeometryEnvironmentClass();
IGeometry geom = gFactory.CreateGeometryFromEnumerator(eGeom);
The first argument to the ‘BindGeometrySource’ function is a IQueryFilter object which can be set to null if all the features in the FeatureClass are required. The second argument for the ‘BindGeometrySource’ function could be either a FeatureClass or a SelectionSet.
Medieval HelpDesk… Real funny video
You have to watch this video about a help desk guy teaching a user how to use a book. This is a real funny video to watch even though it is not in English.
Comments on Microsoft Enterprise Library Exception Block
We have been using the “Microsoft Enterprise Library Exception Block” for exception handling and logging in the current Enterprise Information management Systems that I am working on. Based on my experience with the “Microsoft Enterprise Library Exception Block“, I had a couple of comments that wanted to put out there. These observations of mine may just be complaints about the library that I wish could have been circumvented. But I do agree that the library does provide us with a powerful tool to standardize the way exceptions are handled throughout the application. For those who haven’t used the Exception block library before, here is a quick overview. I feel that this is a great one line summary of the Exception block. “The Exception block separates the definition how an exception should be processed from the application code itself“.
Exception Handling Application Block
It is a framework for handling exceptions (catching, presentation to the user, logging, etc.) in a recommended and standardized way. It allows developers and policy makers to create a consistent strategy for processing exceptions that occur throughout the architectural layers of enterprise applications. It does this in the following ways:
- It supports exception handling throughout an application’s architectural layers and is not limited to service interface boundaries.
- It enables exception-handling policies to be defined and maintained at the administrative level so that policy makers, who may be system administrators as well as developers, can define how to handle exceptions. They can maintain and modify the sets of rules that govern exception handling without changing the application block’s code.
- It provides commonly used exception-handling functions, such as the ability to log exception information, the ability to hide sensitive information by replacing the original exception with another exception, and the ability to add contextual information to an exception by wrapping the original exception inside of another exception. These functions are encapsulated in .NET classes called exception handlers.
- It can combine exception handlers to produce the desired response to an exception, such as logging exception information followed by replacing the original exception with another.
- It lets developers create their own exception handlers.
- It invokes exception handlers in a consistent manner. This means that the handlers can be used in multiple places within and across applications.“
(ms-help://MS.VSCC.2003/ms.EntLib.2005Jun/ms.EntLib.2005Jun.Ex/ExceptionHandling/Intro/intro01.htm)
Architecture
The Exception block separates the definition of how an exception should be processed from the application code itself and moves this configuration to a XML config file. The configuration of the Exception block uses following terms:
Policy
A Policy has a name and a set of exception types that should be processed. An application can have several policies, because it could be necessary to have different processing instructions for one exception type, depending in which layer of the application the exception arises.Exception Type
For each policy one or more exception types can be defined. Exception types can be one of the standard .net exception classes or user defined ones. When an exception is thrown the exception block will determine the type of the exception against the definitions in the used policy and call the exception handlers of this type.Exception handlers
Exception handlers process the exception. An exception type can have one or more handlers. They are called by the application block in the order like they were configurated.
There are following built-in handlers:
- Wrap handler: Creates a new exception and sets the original exception as inner exception for the new one.
- Replace handler: Replaces an exception with a new (custom) one.
- Logging handler: Logs the exception to the Enterprise application logging block, which can be used to log events to different targets like event log, files, databases etc.
- Custom handlers: Developers can write their own exception handlers which can be easily included in the project (see “Creating a custom exception handler”.
The following figure illustrates examples of cross-layer and single-application component exception handling:
(ms-help://ms.EntLib.2005Jun/ms.EntLib.2005Jun.Ex/exceptionHandling/Intro/whentouse03.htm)
The figure above shows how the “UI Layer (UI)”, “Business Components Layer (BCL)” and the “Data Access Layer (DAL)” normally handle exceptions. Any exceptions that occur in the DAL library should be logged and then wrapped with some meaningful contextual information on where and possibly why the exception happened and passed up to the calling BCL library. The BCL library handles the exception by logging it and if necessary replacing it with an another exception that makes more sense when finally handled by the UI layer and displayed to the user.
The policy that should handle the exception is specified by the developer in the ExceptionPolicy.HandleException(…) method as shown below.
try
{
//…
}
catch (Exception e)
{
bool rethrow = ExceptionPolicy.HandleException(e, “exception policy name”);
if (rethrow)
{
throw;
}
}
Ideally, I think that there should be some kind of restrictions on the type of policies that can be specified in the parameter for the ExceptionPolicy.HandleException(…) method. By that I mean, for example the “Notify User” policy should never be used in the DAL or the BCL.
My other comment about the above code is that, the developer is now responsible for handling the “rethrow” boolean value returned by the ExceptionPolicy.HandleException(…) method and explicitly throwing the exception again. The rule to throw exception again is not encapsulated in the policy itself. Also, the decision to throw the exception again is based on the exception that has occurred. Some exceptions may need to be thrown again but some others need not. Given that most programmers are lazy, there is a real good chance that the exceptions do not thrown again. This can lead to situations where exceptions get swallowed without proper user notification if required. The kind of errors can be hard to track down because there may not be a user notification that some thing has gone wrong and can cause problems in parts of the system that rely on it. Maybe some custom FxCop rules can be checked to catch these errors before they move into production.
Tolerance units for IQueryFunctionality.Identify method
This is one of those issues on which I had spent quite a bit of time on trying to figure out the problem only to find out that it is an ESRI bug. I was having a hard time trying to get the identify on the features in the “FeatureGraphicsLayer” to work right. The issue i was having was that the identify on the features in the “FeatureGraphicsLayer” was acting funny when the map is zoomed in real close. The IQueryFunctionality.Identify(…) was returning results even when the user was clicking away from the features. When the user is zoomed out quite far in the map this is either not noticeable or it was returning more search features than it should. After eliminating a lot of possibilities, I narrowed down the problem to the value I am passing into the tolerance parameter of the Identify(…) function. As with all the Web ADF library, the documentation for the function and its parameters were missing. I had no way of finding out the units of the value I was passing into the tolerance parameter of the function. I was expecting it to be in pixels as it is with the ArcGIS Server layers. But things were not working as expected.
I emailed someone at ESRI (who will remain anonymous) about this issue and got this reply back.
Here is some information on tolerance with the IQueryFunctionality.Identify method:
ArcGIS Server:
The tolerance is in pixels around input geometry. It’s used in a call to the Identify method on the SOAP proxy (MapServerProxy or MapServerDcomProxy). The current extent of the map and the map size in pixels are used as input parameters to the method.
ArcIMS:
The tolerance is in pixels around an input point. It’s used to construct an IMS Envelope for an IMS Filter used in a call to FeatureLayer.Query() in the IMS API. The current extent of the map and the map size in pixels are used to construct the search envelope.Web ADF Graphics:
The tolerance is the percentage expansion around an input point using the graphics data source’s full extent. This is a bug (NIM009302). The tolerance should be in pixels, and it should use the current extent of the map to construct a search envelope.
If only I had known about this beforehand or if ESRI had made an tiny effort to document their library, I could saved myself a whole bunch of time and mental agony. With this new information, I was able to work around the problem by simply converting the points in the results table into screen points and doing a simple distance calculation between them screen point of the user click and identifying the closest feature to the user click point.
Here is the code sample to do the above
/// <summary>
/// Gets the index of the closest row if there are any points in the table that fall within the pixel tolerance that is specified.
/// If there are no points within the specified pixel tolerance then a NULL value is returned
/// </summary>
/// <param name=”clickScreenPoint”>The click screen point.</param>
/// <param name=”resultsTable”>The results table.</param>
/// <param name=”pixelTolerance”>The tolerance value in pixels.</param>
/// <param name=”mapEnvelope”>The map envelope.</param>
/// <param name=”mapWidth”>Width of the map.</param>
/// <param name=”mapHeight”>Height of the map.</param>
/// <returns></returns>
private int? GetClosestRowIndex(System.Drawing.Point clickScreenPoint, System.Data.DataTable resultsTable, double pixelTolerance, ESRI.ArcGIS.ADF.Web.Geometry.Envelope mapEnvelope, int mapWidth, int mapHeight)
{
System.Data.DataRowCollection rows = resultsTable.Rows;
ESRI.ArcGIS.ADF.Web.Geometry.Point p;
double distance;
int? minIndex = null;
int count = 0;
System.Drawing.Point currentScreenPoint;
foreach (System.Data.DataRow row in rows)
{
p = row[“GeometryElement”] as ESRI.ArcGIS.ADF.Web.Geometry.Point;
currentScreenPoint = p.ToScreenPoint(mapEnvelope, mapWidth, mapHeight);
distance = DistanceBetweenPoints(clickScreenPoint, currentScreenPoint);
if (distance < pixelTolerance)
{
pixelTolerance = distance;
minIndex = count;
}
count++;
}
return minIndex;
}
/// <summary>
/// Calculates the simple distance between the two screen points specified.
/// </summary>
/// <param name=”point1″>The point1.</param>
/// <param name=”point2″>The point2.</param>
/// <returns></returns>
public double DistanceBetweenPoints(System.Drawing.Point point1, System.Drawing.Point point2)
{
double xd = point2.X – point1.X;
double yd = point2.Y – point1.Y;
return Math.Sqrt((xd * xd) + (yd * yd));
}
‘goto’ statement in C# ??? !!!
I am actually really surprised to find out that there is a ‘goto’ statement in C#. This really does come as a surprise to me after years of being preached the evilness of the ‘goto’ statements. Maybe it is not all that bad. But I have not needed to use it before and hopefully never will.
ScaleDependentRenderer bug in ArcGIS Server 9.2 SP2 & SP3
Even though the documentation for ArcGIS 9.2 SP2 suggested that the “ScaleDependentRenderer” class in the “ESRI.ArcGIS.ADF.Web.Display.Renderer” namespace should be usable, aparently it was not. When I checked the “ESRI.ArcGIS.ADF.Web” DLL delivered with ArcGIS Server 9.2 SP2, the class was marked internal. Most probably because ESRI did not get around to completing its implementation before the release.
On further inquiry with ESRI support, it was confirmed to be a bug and ESRI had issued me an incident number (NIM008391).
Nothing new or shocking about bugs in ESRI software right? The most interesting part about this whole thing is how ESRI has handled it. Instead of fixing the bug by getting the “ScaleDependentRenderer” to work right, ESRI fixed it by removing the “ScaleDependentRenderer” from the documentation for ArcGIS 9.2 SP3. The class still remains marked internal in the DLLs delivered with 9.2 SP3 but just not in the documentation. How convenient?
When I search the newly released Bugs Online feature in the ESRI support site, the incident number does not show up in the search.
Finally …. ESRI Bugs Online
ESRI is finally making some visible improvements towards better fulfilling their promises made earlier in the year.
leave a comment