我在之前做的工具中,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个颜色和字体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){// 获取valuevar 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文件直接安装。