我在之前做的工具中,UI这部分基本没怎么深入,都是直接用现成的控件。
其中有一个问题比较突出,就是没有工具执行的进度框提示。曾经也用过系统自带的信息提示框和进度条,但太简陋,确实不好用。于是就想抄一个进度框来用,自然而然想到了ArcGIS中地理工具执行时显示的进度框:
这里做一个简单版的,够用就行(其实是目前只会这些)。
一、要实现的功能
进度框无法单独使用,这里结合之前做的一个【面要素拓扑】工具一起使用。
如上图所示,选择一个面要素,右键点击,在弹出的列表中点击【面要素拓扑】按钮,即可对所选要素进行处理,同时弹出一个进度提示框,实时显示目前的进程和用时。
可以看出,已经和ArcGIS中地理工具的进度框有几分相似。
其实,显示用时这一点不仅对用户有帮助,对开发、测试同样是很有用的,有利于不断优化你的代码,提高代码执行效率。毕竟一个简单的功能老是显示耗时几分钟,你也很难受。
二、实现流程
1、控件选择
首先,需要确定进度框用什么来做,可以用Form,但我最终还是选择了ArcGIS ProWindow控件,毕竟已经给我们集成了更多基础性功能,用起来应该更方便。
新建一个ArcGIS ProWindow,主要的控件就2个,【ProgressBar】进度条和【RichTextBox】富文本框。
【ProgressBar】用来显示当前执行进度,【RichTextBox】用来显示提示信息,包括正常流程信息和错误信息。这里使用【RichTextBox】而不是更常用的【TextBox】是考虑到显示内容需要不同字体和颜色,才能显示更丰富的文本内容,【TextBox】是无法做到的。
至于打开窗口的方法,其实【ArcGIS ProWindow】已经给我们写好了,正常创建【ArcGIS ProWindow】控件的时候,就会一起创建一个【Show*****.cs】,里面就有打开窗口的代码:
private ProcessWindow _processwindow = null;
protected override void OnClick()
{
if (_processwindow != null)
return;
_processwindow = new ProcessWindow();
_processwindow.Owner = FrameworkApplication.Current.MainWindow;
_processwindow.Closed += (o, e) => { _processwindow = null; };
_processwindow.Show();
}
把这部分代码抄到工具执行代码段里就行了。
2、【ArcGIS ProWindow】方法
因为上面的信息都是在【ArcGIS ProWindow】控件里显示的,所以方法都得写在ProcessWindow.xaml.cs里:
这里主要需要定义3个方法:【更新进度条、添加信息文本和添加耗时文本】。
1)更新进度条
// 变更进度条的进度【100%】
public void AddProcess(int percent)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
pb.Value += percent;
});
}
这个比较简单,只要修改进度条控件的Value值即可。不过需要注意的是,操作都要在UI线程上执行,不能放在主线程里,所以需要在【Dispatcher.Invoke】下执行。
2)添加信息文本
添加信息文本需要考虑到文本的颜色和字体,所以除了文本,还添加了颜色和字体2个参数,并且设置了默认值,大部分情况下其实用默认值就行,有需要才修改参数:
// 添加信息框文字
public void AddMessage(string add_text, SolidColorBrush solidColorBrush = null)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
if (solidColorBrush == null)
{
solidColorBrush = Brushes.Black;
}
// 创建一个新的TextRange对象,范围为新添加的文字
TextRange newRange = new TextRange(tb_message.Document.ContentEnd, tb_message.Document.ContentEnd)
{
Text = add_text
};
// 设置新添加文字的颜色
newRange.ApplyPropertyValue(TextElement.ForegroundProperty, solidColorBrush);
// 设置新添加文字的样式
newRange.ApplyPropertyValue(TextElement.FontStyleProperty, FontStyles.Normal);
});
}
3)添加耗时文本
耗时信息需要计算,在工具开始执行的时候在保存一下开始时间【time_base】,然后将其作为输入参数参与计算。将新的当前时间减去【time_base】即为耗时,然后作为为文字信息写入文本框,文本颜色设为灰色,文本字体设为斜体,这部分就直接固定,不再给参数。
// 添加信息框文字_时间
public void AddTime(DateTime time_base)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
DateTime time_now = DateTime.Now;
TimeSpan time_span = time_now - time_base;
string time_total = time_span.ToString()[..time_span.ToString().LastIndexOf(".")];
string add_text = "………………用时" + time_total + "\r";
// 创建一个新的TextRange对象,范围为新添加的文字
TextRange newRange = new TextRange(tb_message.Document.ContentEnd, tb_message.Document.ContentEnd)
{
Text = add_text
};
// 设置新添加文字的颜色为灰色
newRange.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Gray);
// 设置新添加文字的样式为斜体
newRange.ApplyPropertyValue(TextElement.FontStyleProperty, FontStyles.Italic);
});
}
4)组合方法
上述三个主要方法写完,其实还可以更进一步,因为正常情况下,在某一个节点,通常都不是只调用一个方法,而是多个一起调用,比如在添加文本信息的时候,同时更新进度条和上一个步骤的耗时。所以这里可以做一个方法的组合,调用的时候就可以只调用这个方法:
// 综合显示进度【AddTime+AddMessage+AddProcess】
public void AddProcessMessage(int percent, DateTime time_base, string add_text, SolidColorBrush solidColorBrush = null)
{
AddProcess(percent);
AddTime(time_base);
AddMessage(add_text, solidColorBrush);
}
// 综合显示进度【AddMessage+AddProcess】
public void AddProcessMessage(int percent, string add_text, SolidColorBrush solidColorBrush = null)
{
AddProcess(percent);
AddMessage(add_text, solidColorBrush);
}
3、在主程序中调用【ArcGIS ProWindow】方法
直接先上完整代码:
// 定义一个进度框
private ProcessWindow processwindow = null;
string tool_name = "面要素拓扑检查";
protected override async void OnClick()
{
try
{
// 打开进度框
ProcessWindow pw = ToolManager.OpenProcessWindow(processwindow, tool_name);
DateTime time_base = DateTime.Now;
pw.AddMessage("开始执行" + tool_name + "工具…………" + time_base + "\r", Brushes.Green);
var map = MapView.Active.Map;
// 获取默认数据库
var gdb = Project.Current.DefaultGeodatabasePath;
// 获取工程默认文件夹位置
var def_path = Project.Current.HomeFolderPath;
// 获取图层
FeatureLayer ly = MapView.Active.GetSelectedLayers().FirstOrDefault() as FeatureLayer;
// 如果选择的不是面要素或是无选择,则返回
if (ly.ShapeType != esriGeometryType.esriGeometryPolygon || ly == null)
{
pw.AddMessage("错误!请选择一个面要素!", Brushes.Red);
return;
}
string db_name = "Top2Check"; // 要素数据集名
string fc_name = "top_fc"; // 要素名
string top_name = "Topology"; // TOP名
string db_path = gdb + "\\" + db_name; // 要素数据集路径
string fc_path = db_path + "\\" + fc_name; // 要素路径
string top_path = db_path + "\\" + top_name; // TOP路径
string err_fc = @"检查结果";
string err_field = @"错误说明";
await QueuedTask.Run(() =>
{
pw.AddProcessMessage(10, "创建检查用的数据库和拓扑");
//获取图层的坐标系
var sr = ly.GetSpatialReference();
//在数据库中创建要素数据集
Arcpy.CreateFeatureDataset(gdb, db_name, sr);
// 将所选要素复制到创建的要素数据集中
Arcpy.CopyFeatures(ly.Name, fc_path);
// 新建拓扑
Arcpy.CreateTopology(db_path, top_name);
// 向拓扑中添加要素
Arcpy.AddFeatureClassToTopology(top_path, fc_path);
// 添加拓扑规则【重叠】
Arcpy.AddRuleToTopology(top_path, "Must Not Overlap (Area)", fc_path);
// 添加拓扑规则【空隙】
Arcpy.AddRuleToTopology(top_path, "Must Not Have Gaps (Area)", fc_path);
pw.AddProcessMessage(20, time_base, "生成重叠错误");
// 验证拓扑
Arcpy.ValidateTopology(top_path);
// 输出TOP错误
Arcpy.ExportTopologyErrors(top_path, gdb, "TopErr");
pw.AddProcessMessage(20, time_base, "生成空隙错误");
// 生成空隙
ToolManager.GetCave(fc_path, gdb + @"\" + err_fc);
// 添加说明字段
Arcpy.AddField(gdb + @"\" + err_fc, err_field, "TEXT");
// 空隙错误说明赋值
Arcpy.CalculateField(gdb + @"\" + err_fc, err_field, "'存在空隙'");
// 合并错误
Arcpy.Append(gdb + @"\TopErr_poly", gdb + @"\" + err_fc);
// 加载错误面图层
ToolManager.AddFeatureLayerToMap(gdb + @"\" + err_fc);
pw.AddProcessMessage(20, time_base, "生成错误标记");
// 重叠错误说明赋值
FeatureLayer init_layer = map.FindLayers(err_fc)[0] as FeatureLayer;
using (ArcGIS.Core.Data.Table table = init_layer.GetTable())
{
using (RowCursor rowCursor = table.Search(null, false))
{
TableDefinition tableDefinition = table.GetDefinition();
while (rowCursor.MoveNext())
{
using (Row row = rowCursor.Current)
{
// 获取value
var va = row[err_field];
// 赋值
if (va is null)
{
row[err_field] = "存在重叠面";
}
row.Store();
}
}
}
}
// 删除多余字段
Arcpy.DeleteField(err_fc, "ORIG_FID");
pw.AddProcessMessage(20, time_base, "应用错误图层的显示符号");
// 复制图层符号
string copy_lyrx = def_path + @"\检查结果.lyrx";
ToolManager.CopyResourceFile(@"CCTool.Data.Layers." + @"检查结果.lyrx", copy_lyrx);
// 应用图层符号
Arcpy.ApplySymbologyFromLayer(err_fc, copy_lyrx);
// 删除中间要素
List<string> list_del = new List<string>() { "TopErr_point", "TopErr_line", "TopErr_poly" };
foreach (var fc in list_del)
{
Arcpy.Delect(gdb + @"\" + fc);
}
// 删除数据集和符号图层
Arcpy.Delect(db_path);
File.Delete(copy_lyrx);
pw.AddProcessMessage(20, time_base, "工具运行完成!!!", Brushes.Blue);
});
}
catch (Exception ee)
{
MessageBox.Show(ee.Message + ee.StackTrace);
throw;
}
}
首先按上面【控件选择】那一节中的代码,先打开提示框。
在工具刚开始执行的时候,先生成一个初始时间,并把开始执行的信息和初始时间都添加到文本框里。
// 打开进度框
ProcessWindow pw = ToolManager.OpenProcessWindow(processwindow, tool_name);
DateTime time_base = DateTime.Now;
pw.AddMessage("开始执行" + tool_name + "工具…………" + time_base + "\r", Brushes.Green);
然后在每一个你认为需要添加信息的节点,直接调用上面写好的方法即可,如:
pw.AddProcessMessage(20, time_base, "生成重叠错误");
需要注意进度条的满值是100,这个需要自己协调好。
三、工程文件分享
最后,放上工程文件的链接:
PrcessingFramehttps://pan.baidu.com/s/12b74CXMIs9aBJaFq_QB1fg?pwd=qz8t
PS:可以直接点击...bin\Debug\net6.0-windows\下的.esriAddinX文件直接安装。