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

DevExpress WinForms:设置AllowAllUp=true后SpreadsheetControl对齐按钮视觉分组丢失

问题

我正在使用DevExpress WinForms SpreadsheetControl(v23.2)并自定义Ribbon,已关联SpreadsheetBarController自动更新按钮状态。希望水平对齐按钮(左对齐、居中、右对齐)实现如下Excel式行为:

  • 作为互斥单选组(仅一个可被选中)
  • 当选中单元格为“常规”对齐时,所有按钮可取消选中
  • 视觉上呈现为连贯的分段控件(BarButtonGroup内部边框合并)

遇到的问题:
为实现互斥性和合并边框,我给组内所有按钮设置了GroupIndex,但GroupIndex会强制至少保留一个选中状态,不符合“常规”对齐的需求。添加AllowAllUp = true后解决了逻辑问题,但WXI皮肤不再渲染为分段控件,每个按钮显示独立的分离边框。

我的设置代码:

private void SetupAlignmentButtons()
{
    // horzCmds包含: "FormatAlignmentLeft", "FormatAlignmentCenter", "FormatAlignmentRight"
    var horzCmds = new List<string> { "FormatAlignmentLeft", "FormatAlignmentCenter", "FormatAlignmentRight" };

    foreach (RibbonPageGroup group in ribbonPage1.Groups)
    {
        foreach (BarItemLink link in group.ItemLinks)
        {
            if (link.Item is BarButtonGroup btnGroup)
            {
                foreach (BarItemLink subLink in btnGroup.ItemLinks)
                {
                    if (subLink.Item is DevExpress.XtraSpreadsheet.UI.SpreadsheetCommandBarButtonItem cmdBtn)
                    {
                        if (horzCmds.Contains(cmdBtn.CommandName))
                        {
                            cmdBtn.GroupIndex = 202; 
                            cmdBtn.AllowAllUp = true; 
                        }
                    }
                }
            }
        }
    }
}

期望视觉效果:
期望的分段按钮样式

当前异常效果:
当前的分离按钮样式

解决方案

方法1:直接使用默认对齐按钮组(最简方案)

DevExpress默认的Spreadsheet Ribbon已经实现了你需要的所有逻辑和视觉样式,无需手动修改属性。可以直接将默认的对齐组复制到自定义Ribbon中:

// 创建默认Spreadsheet Ribbon并提取对齐组
var defaultRibbon = spreadsheetControl1.CreateRibbon();
var alignmentGroup = defaultRibbon.Pages
    .First(p => p.Caption == "开始") // 找到"开始"选项卡
    .Groups.First(g => g.Caption == "对齐方式"); // 找到对齐组

// 将组添加到自定义Ribbon的目标页面
ribbonPage1.Groups.Add(alignmentGroup);

方法2:自定义BarCheckItem组并手动同步状态

放弃修改原生SpreadsheetCommandBarButtonItem的AllowAllUp,改用BarCheckItem组成BarButtonGroup,通过事件同步选中状态:

  1. 给所有BarCheckItem设置相同GroupIndex,并将**BarButtonGroup的AllowAllUp设为true**(注意是组的属性,不是单个按钮)
  2. 绑定SelectionChanged事件,根据单元格对齐状态手动更新复选框选中状态

示例代码:

private BarCheckItem leftAlignItem, centerAlignItem, rightAlignItem;

private void InitAlignmentGroup()
{
    // 创建对齐复选按钮
    leftAlignItem = new BarCheckItem 
    { 
        Caption = "左对齐", 
        GroupIndex = 202,
        Glyph = DevExpress.Images.ImageResourceCache.Default.GetImage("office2013/format/alignment_left_16x16.png")
    };
    centerAlignItem = new BarCheckItem 
    { 
        Caption = "居中", 
        GroupIndex = 202,
        Glyph = DevExpress.Images.ImageResourceCache.Default.GetImage("office2013/format/alignment_center_16x16.png")
    };
    rightAlignItem = new BarCheckItem 
    { 
        Caption = "右对齐", 
        GroupIndex = 202,
        Glyph = DevExpress.Images.ImageResourceCache.Default.GetImage("office2013/format/alignment_right_16x16.png")
    };

    // 创建分段按钮组并启用全取消
    var btnGroup = new BarButtonGroup();
    btnGroup.ItemLinks.Add(leftAlignItem);
    btnGroup.ItemLinks.Add(centerAlignItem);
    btnGroup.ItemLinks.Add(rightAlignItem);
    btnGroup.AllowAllUp = true;

    // 添加到自定义Ribbon组
    ribbonPageGroup1.ItemLinks.Add(btnGroup);

    // 绑定事件同步选中状态
    spreadsheetControl1.SelectionChanged += SyncAlignmentButtonState;
}

private void SyncAlignmentButtonState(object sender, EventArgs e)
{
    var worksheet = spreadsheetControl1.ActiveWorksheet;
    var format = worksheet.SelectedCells.BeginUpdateFormatting();
    var horizontalAlign = format.Alignment.Horizontal;
    worksheet.SelectedCells.EndUpdateFormatting();

    leftAlignItem.Checked = horizontalAlign == SpreadsheetHorizontalAlignment.Left;
    centerAlignItem.Checked = horizontalAlign == SpreadsheetHorizontalAlignment.Center;
    rightAlignItem.Checked = horizontalAlign == SpreadsheetHorizontalAlignment.Right;
}

方法3:修改WXI皮肤的渲染规则

使用DevExpress Skin Editor工具修改WXI皮肤,强制BarButtonGroup在AllowAllUp=true时仍渲染为分段控件:

  1. 打开Skin Editor,加载WXI皮肤
  2. 导航到Bar -> BarButtonGroup节点
  3. 修改AllowAllUp为true时的边框绘制逻辑,取消按钮间的独立边框设置
  4. 导出修改后的皮肤并应用到项目中

方法4:自定义绘制BarButtonGroup

通过CustomDraw事件手动绘制分段边框,忽略AllowAllUp的样式影响:

private void barButtonGroup1_CustomDraw(object sender, BarItemCustomDrawEventArgs e)
{
    if (e.Link is not BarButtonGroupLink groupLink || e.SkinOwner is not RibbonControl) return;

    var skin = SkinManager.GetSkin(SkinProductId.Bar, e.Link.LinkObject.LookAndFeel);
    var groupElement = skin[BarSkins.SkinBarButtonGroup];
    var buttonElement = skin[BarSkins.SkinBarButton];

    // 绘制组的整体背景和边框
    e.Graphics.FillRectangle(groupElement.GetBackgroundBrush(e.Graphics, e.Bounds), e.Bounds);
    e.Graphics.DrawRectangle(groupElement.GetBorderPen(e.Graphics, e.Bounds), e.Bounds);

    // 绘制每个按钮,合并中间边框
    int itemCount = groupLink.ItemLinks.Count;
    int itemWidth = e.Bounds.Width / itemCount;
    for (int i = 0; i < itemCount; i++)
    {
        var itemBounds = new Rectangle(
            e.Bounds.Left + i * itemWidth, 
            e.Bounds.Top, 
            itemWidth, 
            e.Bounds.Height
        );
        // 移除中间按钮的左侧边框,实现合并效果
        if (i > 0) itemBounds.X += 1;

        // 绘制按钮背景
        var link = groupLink.ItemLinks[i];
        var isChecked = (link.Item as BarCheckItem)?.Checked ?? false;
        var brush = isChecked ? buttonElement.GetPressedBackgroundBrush(e.Graphics, itemBounds) 
                              : buttonElement.GetBackgroundBrush(e.Graphics, itemBounds);
        e.Graphics.FillRectangle(brush, itemBounds);

        // 绘制按钮内容(图标/文本)
        link.DrawContent(e.Graphics, itemBounds);
    }

    e.Handled = true;
}

内容的提问来源于stack exchange,提问作者NgDinhThi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 20:02:28