You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Pavel Torgashov的Autocomplete Menu实现WinForms XML标签自动补全与闭合?

实现XML/HTML标签自动补全方案(基于Fast Colored TextBox + Autocomplete Menu)

以下是针对你需求的具体实现步骤,全部基于现有控件的扩展开发:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 16:05:37