fix: 更新图标和按钮样式以提升用户界面一致性
- 在 MainFormUi.cs 中,将“加载迭代列表”按钮的图标更改为 Fetch,更新“补挂父项”按钮的图标为 Tree,以增强视觉一致性。 - 在 Theme.cs 中,新增 Fetch 和 Tree 图标类型,并优化现有图标的绘制逻辑,确保在不同尺寸下的清晰度。 - debug.log 文件中添加了新的错误记录,便于后续调试和问题追踪。
This commit is contained in:
@@ -276,7 +276,7 @@ namespace TfsExcelImporter
|
|||||||
_fExcel = NewField(_txtExcel, 500, true);
|
_fExcel = NewField(_txtExcel, 500, true);
|
||||||
_fParent = NewField(_txtParentPbi, 150, true);
|
_fParent = NewField(_txtParentPbi, 150, true);
|
||||||
|
|
||||||
_btnLoadIterations = NewButton("加载迭代列表", BtnKind.Secondary, IconKind.Sync, 130);
|
_btnLoadIterations = NewButton("加载迭代列表", BtnKind.Secondary, IconKind.Fetch, 130);
|
||||||
_btnBrowse = NewButton("浏览…", BtnKind.Secondary, IconKind.Folder, 92);
|
_btnBrowse = NewButton("浏览…", BtnKind.Secondary, IconKind.Folder, 92);
|
||||||
_btnTemplate = NewButton("导出 Excel 模板", BtnKind.Secondary, IconKind.Doc, 142);
|
_btnTemplate = NewButton("导出 Excel 模板", BtnKind.Secondary, IconKind.Doc, 142);
|
||||||
_chkSkip = NewCheck("跳过已存在标题", "");
|
_chkSkip = NewCheck("跳过已存在标题", "");
|
||||||
@@ -454,7 +454,7 @@ namespace TfsExcelImporter
|
|||||||
_cardAdv.ShowBadge = false;
|
_cardAdv.ShowBadge = false;
|
||||||
_fArea = NewField(_txtAreaPath, 420, true);
|
_fArea = NewField(_txtAreaPath, 420, true);
|
||||||
_btnFixArea = NewButton("按迭代修 Area", BtnKind.Secondary, IconKind.Wrench, 158);
|
_btnFixArea = NewButton("按迭代修 Area", BtnKind.Secondary, IconKind.Wrench, 158);
|
||||||
_btnLinkParent = NewButton("补挂父项", BtnKind.Secondary, IconKind.ArrowUp, 120);
|
_btnLinkParent = NewButton("补挂父项", BtnKind.Secondary, IconKind.Tree, 120);
|
||||||
|
|
||||||
int y = _cardAdv.HeaderH + Theme.S12;
|
int y = _cardAdv.HeaderH + Theme.S12;
|
||||||
y = Row(_cardAdv, y, 52, "Area 路径", _fArea, "改 Area 与补挂父项都按上面的「目标迭代」筛选已有工作项");
|
y = Row(_cardAdv, y, 52, "Area 路径", _fArea, "改 Area 与补挂父项都按上面的「目标迭代」筛选已有工作项");
|
||||||
|
|||||||
+154
-45
@@ -199,10 +199,14 @@ namespace TfsExcelImporter
|
|||||||
|
|
||||||
internal enum IconKind
|
internal enum IconKind
|
||||||
{
|
{
|
||||||
None, ChevronDown, ChevronRight, Check, Bolt, Folder, Doc, ArrowUp, Wrench, Trash, Table, Gear, Sync
|
None, ChevronDown, ChevronRight, Check, Bolt, Folder, Doc, ArrowUp, Wrench, Trash, Table, Gear, Fetch, Tree
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>GDI+ 矢量图标:统一 24 网格、约 1.6 描边、圆头。界面内不用 emoji、不用位图。</summary>
|
/// <summary>
|
||||||
|
/// GDI+ 矢量图标:统一 24 网格、约 2 描边、圆头圆角。界面内不用 emoji、不用位图。
|
||||||
|
/// 这条流水线的图标实际只有 14~16px,所以几何一律「少笔画、留大间隙」:
|
||||||
|
/// 细节(桶身竖纹、齿轮齿)在小尺寸会糊成一坨,宁可省略也不用 2 个网格以内的短笔画。
|
||||||
|
/// </summary>
|
||||||
internal static class Icons
|
internal static class Icons
|
||||||
{
|
{
|
||||||
public static void Draw(Graphics g, IconKind kind, Rectangle box, Color color)
|
public static void Draw(Graphics g, IconKind kind, Rectangle box, Color color)
|
||||||
@@ -215,7 +219,10 @@ namespace TfsExcelImporter
|
|||||||
float ox = box.X + (box.Width - side) / 2f;
|
float ox = box.X + (box.Width - side) / 2f;
|
||||||
float oy = box.Y + (box.Height - side) / 2f;
|
float oy = box.Y + (box.Height - side) / 2f;
|
||||||
|
|
||||||
using (var pen = new Pen(color, Math.Max(1.2f, 1.7f * k)))
|
// 描边随尺寸线性走(24 网格 2px),最小 1.35 保证 14px 下不断线
|
||||||
|
float stroke = Math.Max(1.35f, 2f * k);
|
||||||
|
using (var pen = new Pen(color, stroke))
|
||||||
|
using (var brush = new SolidBrush(color))
|
||||||
{
|
{
|
||||||
pen.StartCap = LineCap.Round;
|
pen.StartCap = LineCap.Round;
|
||||||
pen.EndCap = LineCap.Round;
|
pen.EndCap = LineCap.Round;
|
||||||
@@ -223,69 +230,127 @@ namespace TfsExcelImporter
|
|||||||
switch (kind)
|
switch (kind)
|
||||||
{
|
{
|
||||||
case IconKind.ChevronDown:
|
case IconKind.ChevronDown:
|
||||||
Poly(g, pen, ox, oy, k, 6, 9, 12, 15, 18, 9);
|
Poly(g, pen, ox, oy, k, 6.2f, 9.2f, 12f, 15f, 17.8f, 9.2f);
|
||||||
break;
|
break;
|
||||||
case IconKind.ChevronRight:
|
case IconKind.ChevronRight:
|
||||||
Poly(g, pen, ox, oy, k, 9, 6, 15, 12, 9, 18);
|
Poly(g, pen, ox, oy, k, 9.2f, 6.2f, 15f, 12f, 9.2f, 17.8f);
|
||||||
break;
|
break;
|
||||||
case IconKind.Check:
|
case IconKind.Check:
|
||||||
Poly(g, pen, ox, oy, k, 5, 13, 10, 18, 19, 6);
|
Poly(g, pen, ox, oy, k, 4.8f, 12.6f, 9.6f, 17.4f, 19.2f, 6.6f);
|
||||||
break;
|
break;
|
||||||
case IconKind.Bolt:
|
case IconKind.Bolt: // 实心:14px 下描边闪电会糊成一根棍
|
||||||
Poly(g, pen, ox, oy, k, 13, 3, 6, 13, 12, 13, 11, 21, 18, 11, 12, 11, 13, 3);
|
Fill(g, brush, ox, oy, k, 13.2f, 2.4f, 4.8f, 13.4f, 10.4f, 13.4f,
|
||||||
|
10.8f, 21.6f, 19.2f, 10.6f, 13.6f, 10.6f);
|
||||||
break;
|
break;
|
||||||
case IconKind.Folder:
|
case IconKind.Folder:
|
||||||
Poly(g, pen, ox, oy, k, 3, 7, 9, 7, 11, 9, 21, 9, 21, 19, 3, 19, 3, 7);
|
Poly(g, pen, ox, oy, k, 2.8f, 6.4f, 9.4f, 6.4f, 11.4f, 8.6f,
|
||||||
|
21.2f, 8.6f, 21.2f, 18.8f, 2.8f, 18.8f, 2.8f, 6.4f);
|
||||||
break;
|
break;
|
||||||
case IconKind.Doc:
|
case IconKind.Doc:
|
||||||
Poly(g, pen, ox, oy, k, 6, 3, 15, 3, 19, 7, 19, 21, 6, 21, 6, 3);
|
Poly(g, pen, ox, oy, k, 5.8f, 3f, 14.6f, 3f, 19f, 7.4f, 19f, 21f, 5.8f, 21f, 5.8f, 3f);
|
||||||
Poly(g, pen, ox, oy, k, 15, 3, 15, 7, 19, 7);
|
Poly(g, pen, ox, oy, k, 14.6f, 3f, 14.6f, 7.4f, 19f, 7.4f);
|
||||||
Line(g, pen, ox, oy, k, 9, 12, 16, 12);
|
Line(g, pen, ox, oy, k, 8.8f, 12.2f, 16f, 12.2f);
|
||||||
Line(g, pen, ox, oy, k, 9, 16, 16, 16);
|
Line(g, pen, ox, oy, k, 8.8f, 16.2f, 16f, 16.2f);
|
||||||
break;
|
break;
|
||||||
case IconKind.ArrowUp:
|
case IconKind.ArrowUp:
|
||||||
Line(g, pen, ox, oy, k, 12, 20, 12, 5);
|
Line(g, pen, ox, oy, k, 12f, 20.4f, 12f, 5.2f);
|
||||||
Poly(g, pen, ox, oy, k, 5, 12, 12, 5, 19, 12);
|
Poly(g, pen, ox, oy, k, 5.8f, 11.4f, 12f, 5.2f, 18.2f, 11.4f);
|
||||||
break;
|
break;
|
||||||
case IconKind.Wrench:
|
case IconKind.Wrench: // 实心开口扳手:环+手柄在 16px 下会读成放大镜,实心剪影才立得住
|
||||||
g.DrawArc(pen, ox + 3 * k, oy + 3 * k, 8 * k, 8 * k, 300f, 250f);
|
var wst = g.Save();
|
||||||
Line(g, pen, ox, oy, k, 10, 14, 19, 21);
|
g.TranslateTransform(ox + 12f * k, oy + 12f * k);
|
||||||
|
g.RotateTransform(45f);
|
||||||
|
using (var path = new GraphicsPath())
|
||||||
|
{
|
||||||
|
path.AddPolygon(new[]
|
||||||
|
{
|
||||||
|
new PointF(-4.8f * k, -9.4f * k), new PointF(-1.7f * k, -9.4f * k),
|
||||||
|
new PointF(-1.7f * k, -6.2f * k), new PointF(1.7f * k, -6.2f * k),
|
||||||
|
new PointF(1.7f * k, -9.4f * k), new PointF(4.8f * k, -9.4f * k),
|
||||||
|
new PointF(4.8f * k, -4.4f * k), new PointF(2.2f * k, -1.6f * k),
|
||||||
|
new PointF(2.2f * k, 9.6f * k), new PointF(-2.2f * k, 9.6f * k),
|
||||||
|
new PointF(-2.2f * k, -1.6f * k), new PointF(-4.8f * k, -4.4f * k)
|
||||||
|
});
|
||||||
|
g.FillPath(brush, path);
|
||||||
|
}
|
||||||
|
g.Restore(wst);
|
||||||
break;
|
break;
|
||||||
case IconKind.Trash:
|
case IconKind.Trash:
|
||||||
Poly(g, pen, ox, oy, k, 4, 7, 20, 7);
|
Line(g, pen, ox, oy, k, 4.2f, 8.2f, 19.8f, 8.2f); // 盖
|
||||||
Poly(g, pen, ox, oy, k, 8, 7, 8, 20, 16, 20, 16, 7);
|
Line(g, pen, ox, oy, k, 9.4f, 4.2f, 14.6f, 4.2f); // 提手(与盖留 4 单位空隙)
|
||||||
Line(g, pen, ox, oy, k, 11, 4, 13, 4);
|
Poly(g, pen, ox, oy, k, 6.8f, 8.2f, 6.8f, 20f, 17.2f, 20f, 17.2f, 8.2f); // 桶身
|
||||||
break;
|
break;
|
||||||
case IconKind.Table:
|
case IconKind.Table:
|
||||||
g.DrawRectangle(pen, ox + 3 * k, oy + 5 * k, 18 * k, 14 * k);
|
var tr = new RectangleF(ox + 2.8f * k, oy + 4.6f * k, 18.4f * k, 14.8f * k);
|
||||||
Line(g, pen, ox, oy, k, 3, 10, 21, 10);
|
using (var path = Theme.RoundRect(tr, 2.2f * k)) g.DrawPath(pen, path);
|
||||||
Line(g, pen, ox, oy, k, 10, 10, 10, 19);
|
Line(g, pen, ox, oy, k, 2.8f, 9.6f, 21.2f, 9.6f);
|
||||||
|
Line(g, pen, ox, oy, k, 10.4f, 9.6f, 10.4f, 19.4f);
|
||||||
break;
|
break;
|
||||||
case IconKind.Gear:
|
case IconKind.Gear: // 环 + 8 根短齿 + 实心轴:齿短一点才不会糊成太阳
|
||||||
g.DrawEllipse(pen, ox + 8 * k, oy + 8 * k, 8 * k, 8 * k);
|
for (int i = 0; i < 8; i++)
|
||||||
g.DrawEllipse(pen, ox + 10.5f * k, oy + 10.5f * k, 3 * k, 3 * k);
|
{
|
||||||
|
double a = i * Math.PI / 4;
|
||||||
|
Line(g, pen, ox, oy, k, 12f + (float)Math.Cos(a) * 5.2f, 12f + (float)Math.Sin(a) * 5.2f,
|
||||||
|
12f + (float)Math.Cos(a) * 7.4f, 12f + (float)Math.Sin(a) * 7.4f);
|
||||||
|
}
|
||||||
|
g.DrawEllipse(pen, ox + 6.8f * k, oy + 6.8f * k, 10.4f * k, 10.4f * k);
|
||||||
|
g.FillEllipse(brush, ox + 10.2f * k, oy + 10.2f * k, 3.6f * k, 3.6f * k);
|
||||||
break;
|
break;
|
||||||
case IconKind.Sync: // 拉取服务器数据:带缺口的圆 + 箭头
|
case IconKind.Fetch: // 从服务器拉取(加载迭代列表):下箭头落进托盘,与「开始导入」的上箭头成对
|
||||||
g.DrawArc(pen, ox + 4 * k, oy + 4 * k, 16 * k, 16 * k, 300f, 250f);
|
// 圆形箭头在 16px 下始终读成「缺口的圈 + 一坨」,这里换直线几何;
|
||||||
Poly(g, pen, ox, oy, k, 19, 4, 20, 9, 15, 8);
|
// 托盘底边取 19.8 而不是 20.2,100%/125% 下都落在像素中心,不会渲成一条虚灰线
|
||||||
|
Line(g, pen, ox, oy, k, 12f, 3.4f, 12f, 12.2f);
|
||||||
|
Poly(g, pen, ox, oy, k, 6.4f, 7.4f, 12f, 13.2f, 17.6f, 7.4f);
|
||||||
|
Poly(g, pen, ox, oy, k, 5f, 14.6f, 5f, 19.8f, 19f, 19.8f, 19f, 14.6f);
|
||||||
|
break;
|
||||||
|
case IconKind.Tree: // 父项 + 两个子项:比链环更像「挂到父项下」,16px 也分得清
|
||||||
|
using (var node = Theme.RoundRect(new RectangleF(ox + 8.8f * k, oy + 2.6f * k, 6.4f * k, 5.2f * k), 1.4f * k))
|
||||||
|
using (var c1 = Theme.RoundRect(new RectangleF(ox + 2.6f * k, oy + 15.4f * k, 6.4f * k, 5.2f * k), 1.4f * k))
|
||||||
|
using (var c2 = Theme.RoundRect(new RectangleF(ox + 15f * k, oy + 15.4f * k, 6.4f * k, 5.2f * k), 1.4f * k))
|
||||||
|
{
|
||||||
|
g.FillPath(brush, node);
|
||||||
|
g.FillPath(brush, c1);
|
||||||
|
g.FillPath(brush, c2);
|
||||||
|
}
|
||||||
|
Line(g, pen, ox, oy, k, 12f, 7.8f, 12f, 12.4f);
|
||||||
|
Line(g, pen, ox, oy, k, 5.8f, 12.4f, 18.2f, 12.4f);
|
||||||
|
Line(g, pen, ox, oy, k, 5.8f, 12.4f, 5.8f, 15.4f);
|
||||||
|
Line(g, pen, ox, oy, k, 18.2f, 12.4f, 18.2f, 15.4f);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.SmoothingMode = old;
|
g.SmoothingMode = old;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>圆徽标里画勾用的内缩盒子:铺满整个圆会贴边,视觉上像被裁掉一块。</summary>
|
||||||
|
public static Rectangle BadgeGlyph(Rectangle circle)
|
||||||
|
{
|
||||||
|
int inset = Math.Max(2, circle.Width * 2 / 11); // 22px 圆 → 4px 内缩(14px 盒子)
|
||||||
|
return Rectangle.Inflate(circle, -inset, -inset);
|
||||||
|
}
|
||||||
|
|
||||||
private static void Poly(Graphics g, Pen pen, float ox, float oy, float k, params float[] xy)
|
private static void Poly(Graphics g, Pen pen, float ox, float oy, float k, params float[] xy)
|
||||||
{
|
{
|
||||||
var pts = new PointF[xy.Length / 2];
|
g.DrawLines(pen, Points(ox, oy, k, xy));
|
||||||
for (int i = 0; i < pts.Length; i++)
|
}
|
||||||
pts[i] = new PointF(ox + xy[i * 2] * k, oy + xy[i * 2 + 1] * k);
|
|
||||||
g.DrawLines(pen, pts);
|
private static void Fill(Graphics g, Brush brush, float ox, float oy, float k, params float[] xy)
|
||||||
|
{
|
||||||
|
g.FillPolygon(brush, Points(ox, oy, k, xy));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Line(Graphics g, Pen pen, float ox, float oy, float k, float x1, float y1, float x2, float y2)
|
private static void Line(Graphics g, Pen pen, float ox, float oy, float k, float x1, float y1, float x2, float y2)
|
||||||
{
|
{
|
||||||
g.DrawLine(pen, ox + x1 * k, oy + y1 * k, ox + x2 * k, oy + y2 * k);
|
g.DrawLine(pen, ox + x1 * k, oy + y1 * k, ox + x2 * k, oy + y2 * k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PointF[] Points(float ox, float oy, float k, float[] xy)
|
||||||
|
{
|
||||||
|
var pts = new PointF[xy.Length / 2];
|
||||||
|
for (int i = 0; i < pts.Length; i++)
|
||||||
|
pts[i] = new PointF(ox + xy[i * 2] * k, oy + xy[i * 2 + 1] * k);
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum BadgeKind { Pending, Active, Done, Warn }
|
internal enum BadgeKind { Pending, Active, Done, Warn }
|
||||||
@@ -329,7 +394,8 @@ namespace TfsExcelImporter
|
|||||||
var r = new RectangleF(0.5f, 0.5f, Width - 1f, Height - 1f);
|
var r = new RectangleF(0.5f, 0.5f, Width - 1f, Height - 1f);
|
||||||
using (var b = new SolidBrush(fill)) g.FillEllipse(b, r);
|
using (var b = new SolidBrush(fill)) g.FillEllipse(b, r);
|
||||||
g.SmoothingMode = old;
|
g.SmoothingMode = old;
|
||||||
if (Kind == BadgeKind.Done) Icons.Draw(g, IconKind.Check, ClientRectangle, ink);
|
// 勾画在圆内缩 4px(14px 盒子 / 22px 圆),铺满整圆会贴边看着像被裁掉
|
||||||
|
if (Kind == BadgeKind.Done) Icons.Draw(g, IconKind.Check, Icons.BadgeGlyph(ClientRectangle), ink);
|
||||||
else
|
else
|
||||||
TextRenderer.DrawText(g, Number.ToString(), Theme.CaptionBold, ClientRectangle, ink,
|
TextRenderer.DrawText(g, Number.ToString(), Theme.CaptionBold, ClientRectangle, ink,
|
||||||
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding);
|
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding);
|
||||||
@@ -505,7 +571,7 @@ namespace TfsExcelImporter
|
|||||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
using (var b = new SolidBrush(badgeFill)) g.FillEllipse(b, badge);
|
using (var b = new SolidBrush(badgeFill)) g.FillEllipse(b, badge);
|
||||||
g.SmoothingMode = old;
|
g.SmoothingMode = old;
|
||||||
if (Badge == BadgeKind.Done) Icons.Draw(g, IconKind.Check, badge, badgeInk);
|
if (Badge == BadgeKind.Done) Icons.Draw(g, IconKind.Check, Icons.BadgeGlyph(badge), badgeInk);
|
||||||
else
|
else
|
||||||
TextRenderer.DrawText(g, StepNumber.ToString(), Theme.CaptionBold, badge, badgeInk,
|
TextRenderer.DrawText(g, StepNumber.ToString(), Theme.CaptionBold, badge, badgeInk,
|
||||||
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding);
|
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding);
|
||||||
@@ -575,7 +641,7 @@ namespace TfsExcelImporter
|
|||||||
Width = Math.Max(Theme.Px(designMin), pad * 2 + gw + gap + Theme.TextWidth(Text, Font));
|
Width = Math.Max(Theme.Px(designMin), pad * 2 + gw + gap + Theme.TextWidth(Text, Font));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GlyphWidth { get { return Glyph == IconKind.None ? 0 : Theme.Px(14); } }
|
private int GlyphWidth { get { return Glyph == IconKind.None ? 0 : Theme.Px(16); } }
|
||||||
|
|
||||||
private Color FaceColor
|
private Color FaceColor
|
||||||
{
|
{
|
||||||
@@ -773,6 +839,10 @@ namespace TfsExcelImporter
|
|||||||
/// <summary>宿主右侧留给自绘箭头的区域,同时也是点击展开下拉的热区。</summary>
|
/// <summary>宿主右侧留给自绘箭头的区域,同时也是点击展开下拉的热区。</summary>
|
||||||
private int ArrowZone { get { return Theme.Px(26); } }
|
private int ArrowZone { get { return Theme.Px(26); } }
|
||||||
|
|
||||||
|
/// <summary>内容左缘相对 _box 的缩进。TextBox 就摆在 Px(7),组合框闭合态文字必须共用同一基准,
|
||||||
|
/// 否则同一排的输入框文字会错开一条竖线。</summary>
|
||||||
|
private static int TextInsetX { get { return Theme.Px(7); } }
|
||||||
|
|
||||||
public InputField()
|
public InputField()
|
||||||
{
|
{
|
||||||
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint
|
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint
|
||||||
@@ -851,19 +921,30 @@ namespace TfsExcelImporter
|
|||||||
// 闭合态就是普通输入框:白底黑字。焦点/校验态由宿主描边表达,
|
// 闭合态就是普通输入框:白底黑字。焦点/校验态由宿主描边表达,
|
||||||
// 不响应 Selected 标记——否则一聚焦整块变浅蓝,还会把旁边的箭头衬成白色方块。
|
// 不响应 Selected 标记——否则一聚焦整块变浅蓝,还会把旁边的箭头衬成白色方块。
|
||||||
using (var b = new SolidBrush(Theme.Card)) e.Graphics.FillRectangle(b, full);
|
using (var b = new SolidBrush(Theme.Card)) e.Graphics.FillRectangle(b, full);
|
||||||
|
// 文字矩形不能直接用 e.Bounds:原生闭合区自带内缩(左侧约 2px、上沿同理),
|
||||||
|
// 且该内缩随 DPI 放大(150% 下实测 4px),照它画出来的文字会比同排 TextBox
|
||||||
|
// 右移、下沉 3~4px,看上去就是「没居中」。改成在 _box 坐标里定目标矩形
|
||||||
|
// (与 TextBox 共用 TextInsetX 基准),再换算到组合框客户区,DPI 无关。
|
||||||
|
// 额外再让 1px:原生那条「闭合区」裁剪边界正好压在文字起点上,贴着画会把首字
|
||||||
|
// 最左边的反锯齿列切掉("ntlm" 的 n 少半竖就是这个);顺带补上 TextBox 自身
|
||||||
|
// 1px 的 EM 内边距,两者文字才是同一条竖线。
|
||||||
|
var target = new Rectangle(TextInsetX + Theme.Px(1), 0,
|
||||||
|
Math.Max(0, _box.Width - TextInsetX - Theme.Px(1) - ArrowZone), _box.Height);
|
||||||
|
var textRect = cb.IsHandleCreated && _box.IsHandleCreated
|
||||||
|
? cb.RectangleToClient(_box.RectangleToScreen(target))
|
||||||
|
: target;
|
||||||
TextRenderer.DrawText(e.Graphics, cb.GetItemText(cb.Items[e.Index]), cb.Font,
|
TextRenderer.DrawText(e.Graphics, cb.GetItemText(cb.Items[e.Index]), cb.Font,
|
||||||
new Rectangle(full.X, full.Y, Math.Max(0, full.Width - ArrowZone), full.Height),
|
textRect, Theme.Ink,
|
||||||
Theme.Ink,
|
|
||||||
TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding
|
TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPadding
|
||||||
| TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
|
| TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
using (var b = new SolidBrush(selected ? Theme.PrimarySoft : Theme.Card))
|
using (var b = new SolidBrush(selected ? Theme.PrimarySoft : Theme.Card))
|
||||||
e.Graphics.FillRectangle(b, full);
|
e.Graphics.FillRectangle(b, full);
|
||||||
// 列表项贴着自己窗口的左沿,要补一段缩进才和输入框里的文字对齐;
|
// 列表项贴着自己窗口的左沿(列表窗口对齐宿主外沿),要补上 _box 的 2px 内缩
|
||||||
// 闭合态的组合框本身就摆在宿主内缩处,原生 bounds 已经带了这个缩进。
|
// 和文字基准缩进,才和闭合态里的文字严格同一条竖线。
|
||||||
int pad = inBox ? 0 : Theme.Px(10);
|
int pad = TextInsetX + 2;
|
||||||
int right = inBox ? ArrowZone : Theme.Px(6);
|
int right = Theme.Px(6);
|
||||||
var text = new Rectangle(full.X + pad, full.Y,
|
var text = new Rectangle(full.X + pad, full.Y,
|
||||||
Math.Max(0, full.Width - pad - right), full.Height);
|
Math.Max(0, full.Width - pad - right), full.Height);
|
||||||
TextRenderer.DrawText(e.Graphics, cb.GetItemText(cb.Items[e.Index]), cb.Font, text,
|
TextRenderer.DrawText(e.Graphics, cb.GetItemText(cb.Items[e.Index]), cb.Font, text,
|
||||||
@@ -955,6 +1036,9 @@ namespace TfsExcelImporter
|
|||||||
private void ToggleComboDropDown()
|
private void ToggleComboDropDown()
|
||||||
{
|
{
|
||||||
if (!_combo.IsHandleCreated) return;
|
if (!_combo.IsHandleCreated) return;
|
||||||
|
// 先让组合框拿到焦点:没焦点就 CB_SHOWDROPDOWN,列表展开后随即被焦点变化收掉,
|
||||||
|
// 表现出来就是「点一下闪一下」。
|
||||||
|
if (!_combo.Focused) _combo.Focus();
|
||||||
bool dropped = SendMessage(_combo.Handle, CB_GETDROPPEDSTATE, IntPtr.Zero, IntPtr.Zero) != IntPtr.Zero;
|
bool dropped = SendMessage(_combo.Handle, CB_GETDROPPEDSTATE, IntPtr.Zero, IntPtr.Zero) != IntPtr.Zero;
|
||||||
SendMessage(_combo.Handle, CB_SHOWDROPDOWN, new IntPtr(dropped ? 0 : 1), IntPtr.Zero);
|
SendMessage(_combo.Handle, CB_SHOWDROPDOWN, new IntPtr(dropped ? 0 : 1), IntPtr.Zero);
|
||||||
}
|
}
|
||||||
@@ -1040,14 +1124,19 @@ namespace TfsExcelImporter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ComboListHook _listHook;
|
private ComboListHook _listHook;
|
||||||
|
private IntPtr _listHwnd = IntPtr.Zero;
|
||||||
|
private bool _listSkinned;
|
||||||
|
|
||||||
/// <summary>ComboLBox 随组合框句柄一起创建,尽早挂上钩子,第一次下拉就不跳。</summary>
|
/// <summary>ComboLBox 随组合框句柄一起创建,尽早挂上钩子,第一次下拉就不跳。</summary>
|
||||||
private void HookComboList()
|
private void HookComboList()
|
||||||
{
|
{
|
||||||
if (_listHook != null || _combo == null || !_combo.IsHandleCreated) return;
|
if (_combo == null || !_combo.IsHandleCreated) return;
|
||||||
var info = new ComboInfo();
|
var info = new ComboInfo();
|
||||||
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(ComboInfo));
|
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(ComboInfo));
|
||||||
if (!GetComboBoxInfo(_combo.Handle, ref info) || info.hwndList == IntPtr.Zero) return;
|
if (!GetComboBoxInfo(_combo.Handle, ref info) || info.hwndList == IntPtr.Zero) return;
|
||||||
|
if (_listHook != null && _listHook.Handle == info.hwndList) return; // 已挂在当前列表窗口上
|
||||||
|
// 列表窗口被重建过:旧钩子会留在已销毁的句柄上,必须换新的
|
||||||
|
if (_listHook != null) { _listHook.ReleaseHandle(); _listHook = null; }
|
||||||
_listHook = new ComboListHook(this);
|
_listHook = new ComboListHook(this);
|
||||||
_listHook.AssignHandle(info.hwndList);
|
_listHook.AssignHandle(info.hwndList);
|
||||||
}
|
}
|
||||||
@@ -1076,17 +1165,37 @@ namespace TfsExcelImporter
|
|||||||
var info = new ComboInfo();
|
var info = new ComboInfo();
|
||||||
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(ComboInfo));
|
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(ComboInfo));
|
||||||
if (!GetComboBoxInfo(_combo.Handle, ref info) || info.hwndList == IntPtr.Zero) return;
|
if (!GetComboBoxInfo(_combo.Handle, ref info) || info.hwndList == IntPtr.Zero) return;
|
||||||
|
// 列表窗口重建后边框会长回来,按句柄判断是否需要重新剥一次
|
||||||
|
if (_listHwnd != info.hwndList) { _listHwnd = info.hwndList; _listSkinned = false; }
|
||||||
|
|
||||||
|
bool skin = false;
|
||||||
|
if (!_listSkinned)
|
||||||
|
{
|
||||||
int style = GetWindowLong(info.hwndList, GWL_STYLE);
|
int style = GetWindowLong(info.hwndList, GWL_STYLE);
|
||||||
if ((style & WS_BORDER) != 0)
|
if ((style & WS_BORDER) != 0)
|
||||||
|
{
|
||||||
SetWindowLong(info.hwndList, GWL_STYLE, style & ~WS_BORDER);
|
SetWindowLong(info.hwndList, GWL_STYLE, style & ~WS_BORDER);
|
||||||
|
skin = true;
|
||||||
|
}
|
||||||
int ex = GetWindowLong(info.hwndList, GWL_EXSTYLE);
|
int ex = GetWindowLong(info.hwndList, GWL_EXSTYLE);
|
||||||
if ((ex & WS_EX_CLIENTEDGE) != 0)
|
if ((ex & WS_EX_CLIENTEDGE) != 0)
|
||||||
|
{
|
||||||
SetWindowLong(info.hwndList, GWL_EXSTYLE, ex & ~WS_EX_CLIENTEDGE);
|
SetWindowLong(info.hwndList, GWL_EXSTYLE, ex & ~WS_EX_CLIENTEDGE);
|
||||||
|
skin = true;
|
||||||
|
}
|
||||||
|
_listSkinned = true;
|
||||||
|
}
|
||||||
RECT r;
|
RECT r;
|
||||||
if (!GetWindowRect(info.hwndList, out r)) return;
|
if (!GetWindowRect(info.hwndList, out r)) return;
|
||||||
var host = RectangleToScreen(ClientRectangle);
|
var host = RectangleToScreen(ClientRectangle);
|
||||||
|
// 位置已经被 ComboListHook 改写到宿主外沿时不要再 SetWindowPos:
|
||||||
|
// 每重摆一次列表窗口都要整窗重算重画,肉眼就是「打开瞬间闪一下」。
|
||||||
|
bool moved = r.Left != host.Left || r.Top != host.Bottom || (r.Right - r.Left) != host.Width;
|
||||||
|
if (!moved && !skin) return;
|
||||||
|
// 只有真的改了窗口样式才需要 SWP_FRAMECHANGED(它强制整窗重算非客户区)
|
||||||
SetWindowPos(info.hwndList, IntPtr.Zero, host.Left, host.Bottom,
|
SetWindowPos(info.hwndList, IntPtr.Zero, host.Left, host.Bottom,
|
||||||
host.Width, r.Bottom - r.Top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
|
host.Width, Math.Max(0, r.Bottom - r.Top),
|
||||||
|
SWP_NOZORDER | SWP_NOACTIVATE | (skin ? SWP_FRAMECHANGED : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e)
|
protected override void OnPaint(PaintEventArgs e)
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
[1101/171941.143:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: 系统找不到指定的文件。 (0x2)
|
[1101/171941.143:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: 系统找不到指定的文件。 (0x2)
|
||||||
[1101/173900.093:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: 系统找不到指定的文件。 (0x2)
|
[1101/173900.093:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: 系统找不到指定的文件。 (0x2)
|
||||||
|
[1101/184906.941:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: 系统找不到指定的文件。 (0x2)
|
||||||
|
[1101/191310.883:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: 系统找不到指定的文件。 (0x2)
|
||||||
|
|||||||
Reference in New Issue
Block a user