CBD(Component Based Development)的开发方法,就是要以控件作为软件组装的基本单位,而不是以函数、过程、类作
为软件组装的基本单位。所以在Winform的GIS开发中,如果仅仅把MapXtreme控件放进窗体,然后在这个窗体上添加所有
GIS的操作的代码,那么很快就会发现这个应用程序变得腐朽(corrupt)而不可维护。软件的坏味道(bad smell)来自封装程度
不足。
胖控件,就是指把大部分的计算工作都交给Map控件来完成,例如专题图的划分范围颜色配置,图元的距离,最短路径等;
MapXtreme实现这些功能也许很直接,却未必是最高效的。把计算放在控件外部又如何呢?这就是本文论及的瘦控件。
瘦控件仅仅为用户提供一个GIS呈现和基本操作(放大缩小漫游选择)的地方,其他的操作,都放在控件外边的程序实现。
放在控件上的图元,只保留一个必备的ID列。
瘦控件有几个好处:
1 提高封装程度,便于GIS功能的重用和维护。
2 容易扩展控件原来的功能。例如下例《多行InfoTips的实现》,就是把tips的计算任务放在了控件外部;
3 可以采用更高效的纯数学的计算包来实现某些由Map控件实现的功能。例如笔者使用纯数学的方法来实现图元覆盖区域
的计算,速度提高何止十倍。
瘦控件要多瘦才算好?哪些基本功能必须实现? 外部如何实现控件原有的功能?如何扩展控件功能?如何保证控件性能?
这些问题都会影响到瘦控件的表现。下面笔者给出几个源代码片断,结合本人的另一个帖子http://excel.cnblogs.com/archive/2005/11/15/276694.html,
相信很容易就能制作出满足各位需求的足够瘦而且彪悍的MapXtreme控件了。
1 显示多行InfoTips
把鼠标放在图元上点击一下,就能出现tips,但是这个tips的内容是系统生成的,并且只能显示一行,也不知道能不能编程修改它的显示方式,所以我就写了几行代码,来实现自定义的多行tips显示。
步骤:
1)在Winform中加入MapControl和一个TextBox
2)将TextBox的属性MultiLine设为True, Visable设为False,BorderStyle设为FixSingle
3)捕获MapControl1的MouseDown和MouseUp事件,函数内容如下:
private
void
mapControl1_MouseDown(
object
sender, System.Windows.Forms.MouseEventArgs e)
{
//如果没有转载任何tab文件,则返回
if(mapControl1.Map.Layers.Count == 0 || this.currentLayerName=="")//currentLayerName是顶层Layer的名字,同时也是Table的名字
return;
MapInfo.Engine.Selection selection=MapInfo.Engine.Session.Current.Selections.DefaultSelection;
MapInfo.Data.Table table=MapInfo.Engine.Session.Current.Catalog.GetTable(currentLayerName);
MapInfo.Data.IResultSetFeatureCollectionfeatureCollection=selection[table];
//如果没有选中任何图元,或者选中多个图元,则返回
if(featureCollection==null||featureCollection.Count!=1)
return;
//取第一个图元
MapInfo.Geometry.Geometry geometry=featureCollection[0].Geometry;
//创建连接和命令来查询table中的数据
MapInfo.Data.MIConnection connection=new MapInfo.Data.MIConnection();
MapInfo.Data.MICommand command=connection.CreateCommand();
command.CommandText="SELECT * FROM "+currentLayerName+" WHERE Objwithin @featureObject";
command.Parameters.Add("@featureObject",geometry);
connection.Open();
MapInfo.Data.MIDataReader dataReader=command.ExecuteReader();
int n=dataReader.FieldCount;
this.textBoxInfoTips.Clear();
//把查询到的内容放进tips中
for(int i=0;i<n;i++)
{
this.textBoxInfoTips.AppendText(dataReader.GetName(i)+"\t");
}
this.textBoxInfoTips.AppendText("\n");
while(dataReader.Read())
{
for(int i=0;i<n;i++)
{
object o=dataReader.GetValue(i);
if(o==DBNull.Value)
this.textBoxInfoTips.AppendText("null\t");
else
this.textBoxInfoTips.AppendText(o.ToString()+"\t");
}
this.textBoxInfoTips.AppendText("\n");
}
//改变tips的显示位置
this.textBoxInfoTips.Location=new System.Drawing.Point(e.X,e.Y+50);
this.textBoxInfoTips.Visible=true;
}
private
void
mapControl1_MouseUp(
object
sender, System.Windows.Forms.MouseEventArgs e)
{
this.textBoxInfoTips.Visible=false;
}
2 取得选择图元的ID列表
要做瘦控件,首要的是找到一个属性,使得控件外部的数据结构能够与控件内部的图元关联起来。
例如图元的ID。
下面的源码是取出当前选择的所有图元的ID列表,传出给控件外使用。
//
1 获得最上层的选择
MapInfo.Data.IResultSetFeatureCollection featureCollection
=
MapInfo.Engine.Session.Current.Selections.DefaultSelection[
0
];
if
(featureCollection.Count
==
0
)
{
return;
}
//
2 创建列表,用以存放ID
ArrayList featureList
=
new
ArrayList();
//
3 创建连接和命令来查询table中的数据
MapInfo.Data.MIConnection connection
=
new
MapInfo.Data.MIConnection();
connection.Open();
MapInfo.Data.MICommand command
=
connection.CreateCommand();
command.CommandText
=
"
SELECT ID FROM
"
+
featureCollection.Table.Alias
+
"
WHERE obj=obj
"
;
MapInfo.Data.MIDataReader dataReader
=
command.ExecuteReader();
//
4 遍历所有选择,取出它们的ID
while
(dataReader.Read())
{
int featureId=(int)dataReader.GetValue(0);
featureList.Add(featureId);
}
//
5 关闭连接(必须执行此步,否则会出现运行时错误)
dataReader.Close();
dataReader.Dispose();
command.Cancel();
command.Dispose();
connection.Close();
connection.Dispose();
3 用程序选择指定层中符合条件的图元
瘦控件中重要一环是图元选择的联动,即用户对控件内的图元进行鼠标选择可以反映到控件外部的数据结构中,外部的数据结构的图元选择也可以反映到控件内。
下面的源代码正是实现用程序选择指定层中符合条件的图元的。
public
void
FeaturesSelect(
int
[] featureList,
string
layerName)
{
MapInfo.Data.Table table=MapInfo.Engine.Session.Current.Catalog.GetTable(layerName);
if(table==null)
return;
MapInfo.Data.IResultSetFeatureCollectionfeatures=MapInfo.Data.FeatureCollectionFactory.CreateResultSetFeatureCollection(table,null);
foreach(int featureId in featureList)
{
MapInfo.Engine.Session.Current.Catalog.Search(
table,
MapInfo.Data.SearchInfoFactory.SearchWhere("ID="+featureId.ToString()),
features,
MapInfo.Data.ResultSetCombineMode.AddTo);
}
MapInfo.Engine.Session.Current.Selections.DefaultSelection.Clear();
MapInfo.Engine.Session.Current.Selections.DefaultSelection.Add(features);
}
4 瘦控件的专题图制作
既然控件内不作任何的专题图运算,只好由我们自己做了。
我用的比较多的专题图是范围专题图,所以此处给出的例子也是这样的。
本文参考了wwj925和无忌版主的讨论稿想请教版主一个问题,关于修改图元集的样式!特此鸣谢!
下面的源码给出详细的注释,应该不难看懂,我将不多作解释了。
/**/
/// <summary>
/// 手工制作专题图
/// </summary>
/// <param name="dt">表中必须有三列:ID,颜色R分量,颜色G分量,颜色B分量</param>
/// <param name="layerAlias">要操作的层的名字</param>
public
void
FeatureTheme(System.Data.DataTable dt,
string
layerAlias)
{
if(dt==null|| dt.Rows.Count == 0)
return;
MapInfo.Data.Table table=MapInfo.Engine.Session.Current.Catalog.GetTable(layerAlias);
if(table==null)
return;
//把数据按照color分等价类,方便批量改变图元颜色
Hashtable color2FeatureIdList=new Hashtable(); //key=color, value=arraylist,arraylist的value=featureId
//创建空结果集featureCollection,用以存放所有涉及的图元
MapInfo.Data.IResultSetFeatureCollection featureCollection=
MapInfo.Data.FeatureCollectionFactory.CreateResultSetFeatureCollection(table,null);
try
{
foreach(System.Data.DataRow dr in dt.Rows)
{
int featureId=(int)dr["ID"];
int R=Convert.ToInt32(dr["R"]);
int G=Convert.ToInt32(dr["G"]);
int B=Convert.ToInt32(dr["B"]);
Debug.Assert(R<256&&G<256&&B<256&&R> =0&&G>=0&&B>=0,"颜色分量的范围是0到255。");
Color featureColor = Color.FromArgb(150,R,G,B);
//把同色的图元ID放在一起
if(!color2FeatureIdList.ContainsKey(featureColor))
{
ArrayList al=new ArrayList();
al.Add(featureId);
color2FeatureIdList.Add(featureColor,al);
}
else
{
ArrayListal=(ArrayList)color2FeatureIdList[featureColor];
al.Add(featureId);
}
//把arraylist中的所有feature都选进featureCollection中
SelectFeatureIntoCollection(table,featureId,featureCollection);
}
}
catch(ArgumentException)
{
Trace.WriteLine("数据表中无法找到指定的列","输入数据错误");
return ;
}
catch(InvalidCastException)
{
Trace.WriteLine("数据列值不能转成数字类型","输入数据错误");
return ;
}
catch(System.OverflowException)
{
Trace.WriteLine("数据列值超出数值最大范围","输入数据错误");
return ;
}
catch(System.FormatException)
{
Trace.WriteLine("数据列值格式错误,出现了非数字的字母","输入数据错误");
return ;
}
//创建连接
MapInfo.Data.MIConnection connection=new MapInfo.Data.MIConnection();
connection.Open();
//创建ResultSet图层
// MapInfo.Mapping.FeatureLayer themeLayer = newMapInfo.Mapping.FeatureLayer(featureCollection.Table,"themeLayer","themeLayer");
// mapControl1.Map.Layers.Insert(0,themeLayer);
// MapInfo.Mapping.FeatureLayer layer=(MapInfo.Mapping.FeatureLayer)mapControl1.Map.Layers[0];
// layer.Enabled = true;
//事实证明,用ResultSet图层会影响原来的tab文件的数据,所以此处改用TableInfoMemTable
MapInfo.Data.TableInfoMemTable themeTableInfo= new MapInfo.Data.TableInfoMemTable("themeLayer");
foreach(MapInfo.Data.Column col in table.TableInfo.Columns) //复制表结构
{
MapInfo.Data.Column col2=col.Clone();
col2.ReadOnly=false;
themeTableInfo.Columns.Add(col2);
}
MapInfo.Data.Table themeTable = MapInfo.Engine.Session.Current.Catalog.CreateTable(themeTableInfo);
MapInfo.Data.MICommand command1=connection.CreateCommand();
command1.CommandText = "Insert into " +themeTable.Alias + " Select * From "+featureCollection.Table.Alias;//复制图元数据
command1.Prepare();
command1.ExecuteNonQuery();
command1.Cancel();
command1.Dispose();
//逐个检索颜色
foreach(System.Drawing.Color color in color2FeatureIdList.Keys)
{
Color BorderColor = Color.FromArgb(0,0,0);
ArrayList al=(ArrayList)color2FeatureIdList[color];
string whereClause=CreateWhereClause(al);
//创建style
MapInfo.Styles.SimpleLineStyleline = new MapInfo.Styles.SimpleLineStyle(newMapInfo.Styles.LineWidth(1,MapInfo.Styles.LineWidthUnit.Pixel), 2,BorderColor);//1无边界色,2有边界色
MapInfo.Styles.SimpleInteriorinterior = new MapInfo.Styles.SimpleInterior(2,color); //2实习,3斑马线
MapInfo.Styles.AreaStyle area= new MapInfo.Styles.AreaStyle(line,interior);
MapInfo.Styles.CompositeStyle cs = new MapInfo.Styles.CompositeStyle(area,null,null,null);
//批量更新图元颜色
MapInfo.Data.MICommand command=connection.CreateCommand();
command.CommandText = "update " +themeTable.Alias + " set obj=obj,MI_Style=@style"+whereClause;
command.Parameters.Add("@style",cs);
command.Prepare();
command.ExecuteNonQuery();
command.Cancel();
command.Dispose();
}
//关闭连接
connection.Close();
connection.Dispose();
//把新表添加为顶图层
MapInfo.Mapping.FeatureLayer themeFeatureLayer = new MapInfo.Mapping.FeatureLayer(themeTable);
this.mapControl1.Map.Layers.Insert(0,themeFeatureLayer);
}
/**/
/// <summary>
/// 把table中ID=featureId的图元选进featureCollection中
/// </summary>
/// <param name="table">MapInfo的数据表</param>
/// <param name="featureId">图元id</param>
/// <param name="featureCollection">引用的图元选择集</param>
private
void
SelectFeatureIntoCollection(MapInfo.Data.Table table,intfeatureId,MapInfo.Data.IResultSetFeatureCollection featureCollection)
{
MapInfo.Engine.Session.Current.Catalog.Search(
table,
MapInfo.Data.SearchInfoFactory.SearchWhere("ID="+featureId.ToString()),
featureCollection,
MapInfo.Data.ResultSetCombineMode.AddTo);
}