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,通过事件同步选中状态:
- 给所有
BarCheckItem设置相同GroupIndex,并将**BarButtonGroup的AllowAllUp设为true**(注意是组的属性,不是单个按钮) - 绑定
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时仍渲染为分段控件:
- 打开Skin Editor,加载WXI皮肤
- 导航到
Bar -> BarButtonGroup节点 - 修改
AllowAllUp为true时的边框绘制逻辑,取消按钮间的独立边框设置 - 导出修改后的皮肤并应用到项目中
方法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
相关产品推荐
相关产品推荐

