首次做winfrom应用,在滚动条美化上费了很大劲,网上资料新手表示要么看不懂要么不会用。这里把我的实现过程记下来,尽量写详细点,希望帮助下其他跟我一样纠结这个问题的朋友。
首先重绘控件直接复制Sunday丶若雪写的:
https://blog.csdn.net/qq_33212020/article/details/53447135
项目内新建个用户控件,全部复制进去就直接能用了,再次对Sunday丶若雪表示感谢。这里把自己摸索出来的该控件用法写给需要的朋友。
原效果:
个人实现效果:
原理:设计界面放两个panel(pn_info,pn_scroll。AutoScroll属性都设为false,最好命名和我一样,方便代码使用),pn_info放在pn_scroll内部,pn_info放信息,pn_scroll放滚动条。pn_info添加滚轮事件,带动滚动条运动。滚动条的拖动事件内带动pn_info的滚动(pn_info通过改变top值来模拟滚动)。pn_info的location设为(0,0)。我这里pn_info为动态添加数据。
源码:
SundayRXScrollBar sb = new SundayRXScrollBar();//滚动条实例
//窗口初始化
public void init()
{
history his = new history();
List<history> hisList = his.read();//获取历史记录列表
for(var i=0;i<hisList.Count;i++)
{
int timestamp = hisList[i].timestamp;
historyItem item = new historyItem();
item.setDate(toDate(timestamp));
item.chName(timestamp.ToString());//给勾选框赋值(判断勾选用)
item.Location = new System.Drawing.Point(0, i * 115); //记录位置
item.setAvgForce(hisList[i].avg_force.ToString("0.#"));
item.setMaxForce(hisList[i].max_force.ToString());
item.setMaxFrequence(hisList[i].max_frequence.ToString());
item.setPunchCount(hisList[i].punch_count.ToString());
item.setTrainTime(hisList[i].train_time.ToString());
item.setRemark(hisList[i].remark.ToString());
pn_info.Controls.Add(item);
}
//以上部分为我的pn_info添加item,不能照抄,请改为自己的添加方法
//以下内容可全部照抄
//内部容器高度要设为实际高度,这里我每个item的高度是115
pn_info.Height = hisList.Count * 115;
pn_info.Focus(); //获取焦点
sb_init(); //滚动条初始化
}
private void sb_init()
{
if (pn_info.Height <= pn_scroll.Height)
{
//内部容器高度小于滚动条容器时不生产滚动条
pn_info.Width = pn_scroll.Width;
return;
}
sb.Width = 10; //滚动条宽度(px)
pn_info.Width = pn_scroll.Width-10;
sb.Height = pn_scroll.Height;
//滑块高度=可视高度/实际高度*滚动条高度(pn_scroll.Height)
float length = (float)pn_scroll.Height / (float)pn_info.Height * (float)pn_scroll.Height;
sb.SliderHeight = (int)length;
//sb.SliderColor = Color.FromArgb(2,153,255);//滑块颜色
//sb.MouseDownSliderColor 鼠标点击后滑块颜色
//sb.MouseOverSliderColor 鼠标接触时滑块颜色
//sb.BottomColor 滚动条颜色
sb.SliderWidthPercent = 1; //滑块宽度和滚动条宽度比,1表示占满
sb.Dock = DockStyle.Right; //滚动条位置
sb.ValueChanged += sb_ValueChanged; //滚动条拖动事件
sb.GotFocus += sb_GotFocus; //滚动条获得焦点事件
pn_scroll.Controls.Add(sb); //pn_scroll添加滚动条
pn_info.MouseWheel += pn_info_MouseWheel; //pn_info添加滚轮事件
}
bool isMouseWheel = false; //防止滚轮带动的滚动条再次触发sb_ValueChanged
//pn_info滚轮事件
private void pn_info_MouseWheel(object sender, MouseEventArgs e)
{
isMouseWheel = true;
int scrollRang = 40; //每次滚动高度(px)
int sbRang = calSbRang(scrollRang); //滚动条实际滚动幅度
if (e.Delta > 0) //滚轮向上
{
if (sb.Value - sbRang<= 0)
{
//滚动到顶,直接设为默认值,防止滚动产生的误差
sb.Value = 0;
pn_info.Top = 0;
}
else
{
sb.Value -= sbRang;
pn_info.Top += scrollRang;
}
}
else//滚轮向下
{
if (sb.Value + sbRang>= 100)
{
//滚动到底,直接设为默认值,防止滚动产生的误差
sb.Value = 100;
pn_info.Top = -(pn_info.Height-pn_scroll.Height);
}
else
{
sb.Value += sbRang;
pn_info.Top -= scrollRang;
}
}
}
//因为滚动条滚动幅度可能是小数,转int后会损失精度,导致滚动到底或到底时产生一定误差
float residue = 0; //float转int后舍去的小数部分累积凑1
private int calSbRang(int scrollRang)
{
float temp = ((float)scrollRang) / (float)(pn_info.Height - pn_scroll.Height) * 100;//滚动条滚动比例
int sbRang = Convert.ToInt32(temp);//保留整数部分
residue += temp - (float)sbRang; //累积舍去的小数部分
if (residue >= 1)
{
residue -= 1;
sbRang += 1;
}
return sbRang;
}
//滚动条拖动
private void sb_ValueChanged(object sender, EventArgs e)
{
if (isMouseWheel) return;
pn_info.Top =-Convert.ToInt32((float)sb.Value / 100 * (float)(pn_info.Height-pn_scroll.Height));
}
//滚动条获取焦点时把焦点给pn_info,防止触发滚动条的滚轮事件(滚动条的滚轮滑动和容器内的滑动速度不一致)
private void sb_GotFocus(object sender, EventArgs e)
{
isMouseWheel = false;
pn_info.Focus();
}
经过几次测试,发现内容比较少的时候,滚动条滑块会伸到容器外面去。
解决办法:重绘控件代码SliderHeight方法中注释这句(原意应该是当滚动条高度-滑块高度<100的话就把滚动条高度+100,不知道作者为啥要这么处理)