如何用Pavel Torgashov的Autocomplete Menu实现WinForms XML标签自动补全与闭合?
以下是针对你需求的具体实现步骤,全部基于现有控件的扩展开发:
1. 输入“<”时触发标签建议
通过监听Fast Colored TextBox的TextChanged事件,检测用户输入<后,生成预设的标签列表并传递给Autocomplete Menu:
// 窗体初始化时绑定事件 private void Form_Load(object sender, EventArgs e) { fctb.TextChanged += Fctb_TagSuggestionTextChanged; autocompleteMenu1.TargetControl = fctb; } private void Fctb_TagSuggestionTextChanged(object sender, TextChangedEventArgs e) { int currentPos = fctb.SelectionStart; if (currentPos <= 0 || fctb.Text[currentPos - 1] != '<') return; // 自定义你的XML/HTML标签集合 var tagList = new List<string> { "div", "span", "p", "img", "a", "ul", "li", "table" }; var autoCompleteItems = tagList.Select(tag => new AutocompleteItem(tag) { Replacement = tag, ToolTipTitle = "HTML标签", ToolTipText = $"插入<{tag}>标签" }).ToList(); autocompleteMenu1.Show(autoCompleteItems); }
2. 光标在标签内时触发属性建议
先通过辅助方法判断当前光标所处的标签,再根据标签类型匹配对应的属性列表:
// 辅助方法:获取当前光标所在的标签名 private string GetCurrentActiveTag() { int lineIndex = fctb.SelectionStartLine; string currentLine = fctb.Lines[lineIndex]; int posInLine = fctb.SelectionStart - fctb.GetFirstCharIndexFromLine(lineIndex); // 向前查找最近的<符号 int openBracketPos = currentLine.LastIndexOf('<', posInLine - 1); if (openBracketPos == -1) return null; // 排除闭合标签(如</div>) if (currentLine[openBracketPos + 1] == '/') return null; // 提取<到第一个空格/ >之间的标签名 int tagEndPos = currentLine.IndexOfAny(new[] { ' ', '>' }, openBracketPos + 1); tagEndPos = tagEndPos == -1 ? currentLine.Length : tagEndPos; return currentLine.Substring(openBracketPos + 1, tagEndPos - openBracketPos - 1).Trim(); } // 属性建议的TextChanged事件处理 private void Fctb_AttributeSuggestionTextChanged(object sender, TextChangedEventArgs e) { string tagName = GetCurrentActiveTag(); if (string.IsNullOrEmpty(tagName)) return; // 自定义标签-属性映射表 var attributeMap = GetTagAttributeMap(tagName); if (attributeMap.Count == 0) return; var autoCompleteItems = attributeMap.Select(kv => new AutocompleteItem(kv.Key) { Replacement = $"{kv.Key}=\"\"", // 自动选中属性值的输入位置 SelectionStartAfterReplace = fctb.SelectionStart + kv.Key.Length + 2, SelectionLengthAfterReplace = 0, ToolTipTitle = $"属性:{kv.Key}", ToolTipText = kv.Value }).ToList(); autocompleteMenu1.Show(autoCompleteItems); } // 定义标签对应的属性集合 private Dictionary<string, string> GetTagAttributeMap(string tagName) { return tagName.ToLower() switch { "img" => new() { { "src", "图片路径" }, { "alt", "替代文本" }, { "width", "宽度" } }, "a" => new() { { "href", "链接地址" }, { "target", "打开方式" }, { "title", "提示文本" } }, "div" => new() { { "class", "CSS类名" }, { "id", "唯一标识" }, { "style", "内联样式" } }, _ => new() { { "class", "CSS类名" }, { "id", "唯一标识" } } }; }
3. 自动闭合未关闭的标签
监听用户输入>的动作,判断当前标签是否为自闭合标签,若非则自动插入对应的闭合标签:
// 辅助方法:判断是否为自闭合标签 private bool IsSelfClosingTag(string tagName) { var selfClosingTags = new List<string> { "img", "br", "hr", "input", "meta", "link" }; return selfClosingTags.Contains(tagName.ToLower()); } // 自动闭合标签的TextChanged事件处理 private void Fctb_AutoCloseTagTextChanged(object sender, TextChangedEventArgs e) { int currentPos = fctb.SelectionStart; if (currentPos <= 1 || fctb.Text[currentPos - 1] != '>') return; // 查找对应标签名 int openBracketPos = fctb.Text.LastIndexOf('<', currentPos - 2); if (openBracketPos == -1) return; if (fctb.Text[openBracketPos + 1] == '/') return; int tagEndPos = fctb.Text.IndexOfAny(new[] { ' ', '>' }, openBracketPos + 1); if (tagEndPos == -1) return; string tagName = fctb.Text.Substring(openBracketPos + 1, tagEndPos - openBracketPos - 1).Trim(); if (IsSelfClosingTag(tagName)) return; // 插入闭合标签并定位光标 string closingTag = $"</{tagName}>"; fctb.Text = fctb.Text.Insert(currentPos, closingTag); fctb.SelectionStart = currentPos; }
额外优化建议
- 将标签、属性列表移至配置文件(如XML/JSON),避免硬编码,方便后续维护。
- 增加嵌套标签层级检测,确保闭合标签与父标签正确匹配。
- 结合Fast Colored TextBox的语法高亮API,让补全内容与现有高亮风格统一。
- 处理标签删除场景,自动移除对应的闭合标签(需实现简单的标签栈跟踪)。
内容的提问来源于stack exchange,提问作者RichardB
相关产品推荐
相关产品推荐

