treeView 控件应该是所有控件里最难用代码操作的,因为需要时刻分析各项数据的先后关系,导致逻辑较为复杂难写,以下是利用百度AI的NLP服务里,依存句法分析的api返回的数据,来展示treeView控件的使用。最终效果如图:
参考链接:
- 百度AI:https://ai.baidu.com/ai-doc/NLP/ik6z52biz#%E4%BE%9D%E5%AD%98%E5%8F%A5%E6%B3%95%E5%88%86%E6%9E%90
- treeView使用:https://blog.csdn.net/ping_Kingzero/article/details/119493112
为了弄清代码含义,先展示百度的api的返回值含义和调用的代码,再来看treeView的代码:
using Newtonsoft.Json;
namespace nlp_app.Baidu
{
public class DepParserApp
{
public List<DepParserItemsItem>? DepParserDemo(string text)
{
NLPApp appInfo = NLPApp.getAppInfo();
// 调用依存句法分析,可能会抛出网络等异常,请使用try/catch捕获
var jsonResult = appInfo.Client.DepParser(text);
DepParserRoot result = JsonConvert.DeserializeObject<DepParserRoot>(jsonResult.ToString());
if (result != null)
{
return result.items;
}
return null;
}
}
public class DepParserItemsItem
{
public string id { get; set; }
public string word { get; set; }
public string postag { get; set; }
public string head { get; set; }
public string deprel { get; set; }
}
public class DepParserRoot
{
public ulong log_id { get; set; }
public string text { get; set; }
public List<DepParserItemsItem> items { get; set; }
}
}
接下来是treeView怎么实现的代码。基本思路是:
- 先用双层循环,按照先父后子的结点之间引用顺序来排序进入SortedList;
- 然后用Map记录每个结点的id(作为Key),父结点的id(在此处是head)、在treeView里位于上一层节点的第几个子节点(索引index),后两项装进数组作为Value;
- 遍历SortedList,因为是父子关系有序的,故不用担心先遍历的点需要指向后遍历的点。此时对每个遍历的点按照父节点关系(head),通过查Map,依次装入一个栈里,然后再依次出栈,栈顶一定是根节点。用treeView的SelectedNode指向当前层次的结点,最后插入当前遍历的结点。记得还要记录当前结点在treeView里的索引,并放入Map里以供后面查询。
private void button2_Click(object sender, EventArgs e)
{
textBox1.Text = checkTextLength(textBox1.Text); //输入的文本
treeView1.Nodes.Clear(); // 首先清理之前使用过的treeView结果
try
{
DepParserApp depParserApp = new DepParserApp();
List<DepParserItemsItem>? depParserItemsItems = depParserApp.DepParserDemo(UTF8ToGBK(textBox1.Text));
if (depParserItemsItems == null || depParserItemsItems.Count == 0)
{
MessageBox.Show("发生错误:返回null值,可能由于status或retcode不为0,或网络查询返回了null");
return;
}
SortedList<int, DepParserItemsItem> treeView_sortedList = new SortedList<int, DepParserItemsItem>();
string index; // 用于记录当前查找的元素,它的head属性值应该是多少
int p = 0;
int q = 0;
// 为了使用treeView,首先对List<DepParserItemsItem> 里的元素,按照head属性的先后顺序,
// 放到一个SortedList里,按照:根节点→一级节点→二级节点... 排列为层次顺序。使用双层循环完成排序
while (depParserItemsItems.Count > 0)
{
if (treeView_sortedList.Count == 0) { index = "0"; } // 初始时,需要找head为0的那个元素,其余时刻head是i值对应索引的SortedList元素的id值
else
{
index = treeView_sortedList[p - 1].id;
}
for (int j = 0; j < depParserItemsItems.Count; j++)
{
if (depParserItemsItems[j].head == index)
{
treeView_sortedList.Add(q, depParserItemsItems[j]);
q++;
depParserItemsItems.RemoveAt(j);
j--;
}
}
p++;
}
NLPApp appInfo = NLPApp.getAppInfo();
// 定义一个Map,key是List<DepParserItemsItem> 里元素的id,value是一个3个元素的数组:[head, list_index, treeView_index]
// head即当前元素id对应的父节点的id,list_index即当前元素在treeView_sortedList里的索引,treeView_index即当前节点在treeView里的序号
Hashtable map = new Hashtable();
for (int i = 0; i < treeView_sortedList.Count; i++)
{
string temp = treeView_sortedList[i].word + "(" + appInfo.DEPRELTable[treeView_sortedList[i].deprel] + ")";
if (i == 0)
{
this.treeView1.Nodes.Add(new TreeNode(temp));
int[] vs = new int[3] { 0, 0, 0 };
map.Add(treeView_sortedList[i].id, vs);
}
else
{
this.treeView1.SelectedNode = this.treeView1.Nodes[0]; // 每次从根节点出发
// 使用栈来完成获取当前节点位置
Stack stack = new Stack();
string locate = treeView_sortedList[i].head;
stack.Push(((int[])map[locate])[2]);
while (((int[])map[locate])[0] != 0)
{
locate = ((int[])map[locate])[0].ToString();
stack.Push(((int[])map[locate])[2]);
}
while (stack.Count > 0)
{
int j = (int)stack.Pop();
if (j == 0) { continue; }
this.treeView1.SelectedNode = this.treeView1.SelectedNode.Nodes[j];
}
int num = this.treeView1.SelectedNode.Nodes.Count;
this.treeView1.SelectedNode.Nodes.Add(new TreeNode(temp));
int[] vs = new int[3] { int.Parse(treeView_sortedList[i].head), i, num };
map.Add(treeView_sortedList[i].id, vs);
}
}
this.treeView1.ExpandAll();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}