Skip to content

Commit fbe9686

Browse files
authored
更改组合框样式
1 parent fb02e28 commit fbe9686

File tree

1 file changed

+366
-0
lines changed

1 file changed

+366
-0
lines changed
Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Drawing;
4+
using System.Drawing.Drawing2D;
5+
using System.Drawing.Imaging;
6+
using System.Windows.Forms;
7+
using Svg;
8+
9+
namespace BluePointLilac.Controls
10+
{
11+
public class RComboBox : ComboBox
12+
{
13+
private Color borderColor = Color.FromArgb(210, 210, 215);
14+
private Color hoverBorderColor = Color.FromArgb(255, 145, 60);
15+
private Color focusBorderColor = Color.FromArgb(255, 107, 0);
16+
private Color backgroundColor = Color.FromArgb(250, 250, 252);
17+
private Color arrowColor = Color.FromArgb(100, 100, 100);
18+
private Color hoverArrowColor = Color.FromArgb(255, 107, 0);
19+
private Color buttonColor = Color.FromArgb(250, 250, 252);
20+
21+
private bool isHovered = false;
22+
private bool isFocused = false;
23+
private int borderRadius = 8;
24+
25+
private Bitmap cachedArrowBitmap;
26+
private Color lastArrowColor = Color.Empty;
27+
private Rectangle arrowRect;
28+
29+
private Timer animationTimer;
30+
private float currentBorderWidth = 1.2f;
31+
private float targetBorderWidth = 1.2f;
32+
private Color currentBorderColor;
33+
private Color targetBorderColor;
34+
private float arrowScale = 1.0f;
35+
private float targetArrowScale = 1.0f;
36+
37+
private bool isPainting = false;
38+
39+
public RComboBox()
40+
{
41+
SetStyle(ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer |
42+
ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
43+
44+
DrawMode = DrawMode.OwnerDrawFixed;
45+
DropDownStyle = ComboBoxStyle.DropDownList;
46+
IntegralHeight = false; // 防止下拉菜单高度问题
47+
48+
BackColor = MyMainForm.ButtonMain;
49+
ForeColor = MyMainForm.FormFore;
50+
51+
InitializeColors();
52+
InitializeAnimation();
53+
UpdateArrowRect();
54+
55+
// 事件处理
56+
MouseEnter += (s, e) => { isHovered = true; UpdateAnimationTargets(); Invalidate(); };
57+
MouseLeave += (s, e) => { isHovered = false; UpdateAnimationTargets(); Invalidate(); };
58+
GotFocus += (s, e) => { isFocused = true; UpdateAnimationTargets(); Invalidate(); };
59+
LostFocus += (s, e) => { isFocused = false; UpdateAnimationTargets(); Invalidate(); };
60+
61+
// 修复下拉菜单显示问题
62+
DropDown += OnDropDown;
63+
}
64+
65+
[DefaultValue(typeof(Color), "210, 210, 215")]
66+
public Color BorderColor
67+
{
68+
get => borderColor;
69+
set { borderColor = value; Invalidate(); }
70+
}
71+
72+
[DefaultValue(typeof(Color), "255, 255, 255")]
73+
public Color ButtonColor
74+
{
75+
get => buttonColor;
76+
set { buttonColor = backgroundColor = value; Invalidate(); }
77+
}
78+
79+
[DefaultValue(typeof(Color), "100, 100, 100")]
80+
public Color ArrowColor
81+
{
82+
get => arrowColor;
83+
set { arrowColor = value; Invalidate(); }
84+
}
85+
86+
[DefaultValue(8)]
87+
public int BorderRadius
88+
{
89+
get => borderRadius;
90+
set { borderRadius = value; Invalidate(); }
91+
}
92+
93+
private void InitializeAnimation()
94+
{
95+
animationTimer = new Timer { Interval = 32 };
96+
animationTimer.Tick += (s, e) => UpdateAnimation();
97+
animationTimer.Start();
98+
currentBorderColor = targetBorderColor = borderColor;
99+
}
100+
101+
private void UpdateAnimation()
102+
{
103+
if (isPainting || !Visible) return;
104+
105+
bool needsUpdate = false;
106+
107+
if (Math.Abs(currentBorderWidth - targetBorderWidth) > 0.05f)
108+
{
109+
currentBorderWidth += (targetBorderWidth - currentBorderWidth) * 0.2f;
110+
needsUpdate = true;
111+
}
112+
113+
if (currentBorderColor != targetBorderColor)
114+
{
115+
currentBorderColor = ColorLerp(currentBorderColor, targetBorderColor, 0.15f);
116+
needsUpdate = true;
117+
}
118+
119+
if (Math.Abs(arrowScale - targetArrowScale) > 0.05f)
120+
{
121+
arrowScale += (targetArrowScale - arrowScale) * 0.3f;
122+
needsUpdate = true;
123+
}
124+
125+
if (needsUpdate) Invalidate();
126+
}
127+
128+
private Color ColorLerp(Color c1, Color c2, float t) => Color.FromArgb(
129+
(int)(c1.R + (c2.R - c1.R) * t),
130+
(int)(c1.G + (c2.G - c1.G) * t),
131+
(int)(c1.B + (c2.B - c1.B) * t)
132+
);
133+
134+
private void UpdateAnimationTargets()
135+
{
136+
targetBorderWidth = isFocused ? 2.2f : isHovered ? 1.8f : 1.2f;
137+
targetBorderColor = isFocused ? focusBorderColor : isHovered ? hoverBorderColor : borderColor;
138+
targetArrowScale = isHovered ? 1.1f : 1.0f;
139+
}
140+
141+
private void InitializeColors()
142+
{
143+
borderColor = buttonColor = backgroundColor = MyMainForm.ButtonMain;
144+
arrowColor = ForeColor = MyMainForm.FormFore;
145+
146+
if (MyMainForm.IsDarkTheme())
147+
{
148+
hoverBorderColor = Color.FromArgb(255, 145, 60);
149+
focusBorderColor = Color.FromArgb(255, 107, 0);
150+
hoverArrowColor = Color.FromArgb(255, 107, 0);
151+
}
152+
else
153+
{
154+
hoverBorderColor = Color.FromArgb(255, 145, 60);
155+
focusBorderColor = Color.FromArgb(255, 107, 0);
156+
hoverArrowColor = Color.FromArgb(255, 107, 0);
157+
}
158+
159+
currentBorderColor = targetBorderColor = borderColor;
160+
}
161+
162+
private void UpdateArrowRect()
163+
{
164+
int size = 16, margin = 8;
165+
arrowRect = new Rectangle(Width - size - margin, (Height - size) / 2, size, size);
166+
}
167+
168+
protected override void OnPaint(PaintEventArgs e)
169+
{
170+
if (isPainting) return;
171+
isPainting = true;
172+
173+
try
174+
{
175+
var g = e.Graphics;
176+
g.SmoothingMode = SmoothingMode.AntiAlias;
177+
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
178+
179+
// 清除背景
180+
using (var brush = new SolidBrush(Parent?.BackColor ?? SystemColors.Control))
181+
g.FillRectangle(brush, ClientRectangle);
182+
183+
// 绘制背景和边框
184+
using (var path = CreateRoundedRectanglePath(ClientRectangle, borderRadius))
185+
using (var brush = new SolidBrush(backgroundColor))
186+
g.FillPath(brush, path);
187+
188+
using (var path = CreateRoundedRectanglePath(new Rectangle(0, 0, Width - 1, Height - 1), borderRadius))
189+
using (var pen = new Pen(currentBorderColor, currentBorderWidth))
190+
g.DrawPath(pen, path);
191+
192+
// 绘制文本
193+
var text = SelectedItem?.ToString() ?? (Items.Count > 0 ? Items[0].ToString() : "");
194+
if (!string.IsNullOrEmpty(text))
195+
{
196+
using (var brush = new SolidBrush(ForeColor))
197+
{
198+
var textRect = new Rectangle(12, 0, Width - arrowRect.Width - 20, Height);
199+
var format = new StringFormat { LineAlignment = StringAlignment.Center, Trimming = StringTrimming.EllipsisCharacter };
200+
g.DrawString(text, Font, brush, textRect, format);
201+
}
202+
}
203+
204+
// 绘制箭头
205+
DrawArrowIcon(g, arrowRect);
206+
}
207+
finally { isPainting = false; }
208+
}
209+
210+
protected override void OnDrawItem(DrawItemEventArgs e)
211+
{
212+
if (e.Index < 0) return;
213+
214+
var isSelected = (e.State & DrawItemState.Selected) == DrawItemState.Selected;
215+
var backColor = isSelected ? Color.FromArgb(255, 235, 225) : BackColor;
216+
var textColor = isSelected ? Color.FromArgb(255, 107, 0) : ForeColor;
217+
218+
// 绘制背景
219+
e.Graphics.FillRectangle(new SolidBrush(backColor), e.Bounds);
220+
221+
// 绘制文本
222+
using (var brush = new SolidBrush(textColor))
223+
{
224+
var textRect = new Rectangle(e.Bounds.X + 8, e.Bounds.Y, e.Bounds.Width - 16, e.Bounds.Height);
225+
var format = new StringFormat { LineAlignment = StringAlignment.Center, Trimming = StringTrimming.EllipsisCharacter };
226+
e.Graphics.DrawString(GetItemText(Items[e.Index]), Font, brush, textRect, format);
227+
}
228+
229+
// 绘制焦点框
230+
if ((e.State & DrawItemState.Focus) == DrawItemState.Focus)
231+
e.DrawFocusRectangle();
232+
}
233+
234+
private void DrawArrowIcon(Graphics g, Rectangle iconRect)
235+
{
236+
if (iconRect.Width <= 0) UpdateArrowRect();
237+
238+
var color = isHovered || isFocused ? hoverArrowColor : arrowColor;
239+
240+
if (cachedArrowBitmap == null || lastArrowColor != color)
241+
{
242+
cachedArrowBitmap?.Dispose();
243+
cachedArrowBitmap = GenerateArrowIcon(iconRect.Size, color);
244+
lastArrowColor = color;
245+
}
246+
247+
if (cachedArrowBitmap != null)
248+
{
249+
var rect = new Rectangle(
250+
iconRect.X + (int)(iconRect.Width * (1 - arrowScale) / 2),
251+
iconRect.Y + (int)(iconRect.Height * (1 - arrowScale) / 2),
252+
(int)(iconRect.Width * arrowScale),
253+
(int)(iconRect.Height * arrowScale)
254+
);
255+
g.DrawImage(cachedArrowBitmap, rect);
256+
}
257+
}
258+
259+
private Bitmap GenerateArrowIcon(Size size, Color color)
260+
{
261+
try
262+
{
263+
var svg = new SvgDocument { Width = size.Width, Height = size.Height, ViewBox = new SvgViewBox(0, 0, 24, 24) };
264+
svg.Children.Add(new SvgPath
265+
{
266+
PathData = SvgPathBuilder.Parse("M7 10l5 5 5-5z"),
267+
Fill = new SvgColourServer(color)
268+
});
269+
return svg.Draw(size.Width, size.Height);
270+
}
271+
catch
272+
{
273+
return GenerateFallbackArrowIcon(size, color);
274+
}
275+
}
276+
277+
private Bitmap GenerateFallbackArrowIcon(Size size, Color color)
278+
{
279+
var bmp = new Bitmap(size.Width, size.Height);
280+
using (var g = Graphics.FromImage(bmp))
281+
{
282+
g.SmoothingMode = SmoothingMode.AntiAlias;
283+
var points = new[] {
284+
new Point(size.Width / 4, size.Height / 3),
285+
new Point(size.Width * 3 / 4, size.Height / 3),
286+
new Point(size.Width / 2, size.Height * 2 / 3)
287+
};
288+
g.FillPolygon(new SolidBrush(color), points);
289+
}
290+
return bmp;
291+
}
292+
293+
private GraphicsPath CreateRoundedRectanglePath(Rectangle rect, int radius)
294+
{
295+
var path = new GraphicsPath();
296+
float diam = radius * 2f;
297+
if (diam > rect.Width) diam = rect.Width;
298+
if (diam > rect.Height) diam = rect.Height;
299+
300+
path.AddArc(rect.X, rect.Y, diam, diam, 180, 90);
301+
path.AddArc(rect.Right - diam, rect.Y, diam, diam, 270, 90);
302+
path.AddArc(rect.Right - diam, rect.Bottom - diam, diam, diam, 0, 90);
303+
path.AddArc(rect.X, rect.Bottom - diam, diam, diam, 90, 90);
304+
path.CloseFigure();
305+
return path;
306+
}
307+
308+
// 修复下拉菜单显示问题的关键方法
309+
private void OnDropDown(object sender, EventArgs e)
310+
{
311+
// 确保下拉列表正常显示
312+
BeginInvoke(new Action(() => {
313+
if (DroppedDown)
314+
{
315+
// 强制重绘以确保下拉列表正确显示
316+
Invalidate();
317+
}
318+
}));
319+
}
320+
321+
// 重写 WndProc 以确保鼠标事件正确处理
322+
protected override void WndProc(ref Message m)
323+
{
324+
const int WM_LBUTTONDOWN = 0x201;
325+
const int WM_LBUTTONUP = 0x202;
326+
327+
if (m.Msg == WM_LBUTTONDOWN)
328+
{
329+
// 确保点击时下拉列表正常显示
330+
if (!DroppedDown)
331+
{
332+
DroppedDown = true;
333+
return;
334+
}
335+
}
336+
else if (m.Msg == WM_LBUTTONUP)
337+
{
338+
// 允许正常的鼠标释放处理
339+
base.WndProc(ref m);
340+
return;
341+
}
342+
343+
base.WndProc(ref m);
344+
}
345+
346+
protected override void OnResize(EventArgs e)
347+
{
348+
base.OnResize(e);
349+
UpdateArrowRect();
350+
cachedArrowBitmap?.Dispose();
351+
cachedArrowBitmap = null;
352+
Invalidate();
353+
}
354+
355+
protected override void Dispose(bool disposing)
356+
{
357+
if (disposing)
358+
{
359+
animationTimer?.Stop();
360+
animationTimer?.Dispose();
361+
cachedArrowBitmap?.Dispose();
362+
}
363+
base.Dispose(disposing);
364+
}
365+
}
366+
}

0 commit comments

Comments
 (0)