15.滚动条UIScrollBar类
滚动条的行为与滑动条是非常相像的,只不过还需要绘制两个带箭头的按钮,而且滑块可以平滑移动,而不是像滑动条那样只能间断移动。滚动条使用的图像如下:
滚动条需要实现的主要功能是:
- 绘制滑动条轨道、滑块和两个带箭头的按钮;
- 根据鼠标位置拖动滑块并改变value值,value值并不能平滑改变。
- 点击箭头按钮可以移动滑块,滑块移动的距离是由SmallChange(Winform的滚动条控件也有相同的属性)属性决定的,默认情况下一次移动一格。
- 点击滑动条轨道可以移动滑块,滑块移动的距离是由LargeChange(Winform的滚动条控件也有相同的属性)决定的,即一次移动一页,但是这个功能我还没有实现,目前点击轨道时滑块会移动到鼠标所处位置。
水平滚动条和竖直滚动条大部分行为是类似的,所以将它们的共性封装成UIScrollBarBase基类,将逻辑包含在基类中,而界面代码写在继承的UIHScrollBar类和UIVScrollBar类中。下面是基类UIScrollBarBase的代码:
/**//// <summary>
/// 滚动条基类,水平滚动条和竖直滚动条从这个类继承
/// </summary>
public abstract class UIScrollBarBase:UISceneNode
...{
成员变量和构造函数#region 成员变量和构造函数
/**//// <summary>
/// 默认轨道图像矩形
/// </summary>
protected static readonly Rectangle DefaultSliderRailRect = new Rectangle(33, 32, 14, 16);
/**//// <summary>
/// 默认滑块普通状态图像矩形
/// </summary>
protected static readonly Rectangle DefaultSliderButtonRectOrigin = new Rectangle(0, 32, 16, 16);
/**//// <summary>
/// 默认滑块高亮状态图像矩形
/// </summary>
protected static readonly Rectangle DefaultSliderButtonRectHighlight = new Rectangle(16, 32, 16, 16);
/**//// <summary>
/// 默认滑块点击状态图像矩形
/// </summary>
protected static readonly Rectangle DefaultSliderButtonRectClicked = new Rectangle(16, 32, 16, 16);
/**//// <summary>
/// 默认摩擦条图像矩形
/// </summary>
protected static readonly Rectangle DefaultSliderButtonGlyphRect = new Rectangle(48, 0, 16, 16);
/**//// <summary>
/// 滚动条按钮宽(高),即滚动条高度,默认为16
/// </summary>
protected float buttonWidth;
/**//// <summary>
/// 向左(上)按钮
/// </summary>
protected UIButton btnLeftUp;
/**//// <summary>
/// 向左(上)按钮的位置
/// </summary>
protected Vector2 btnLeftUpPositon;
/**//// <summary>
/// 向右(下)按钮
/// </summary>
protected UIButton btnRightDown;
/**//// <summary>
/// 向右(下)按钮的位置
/// </summary>
protected Vector2 btnRightDownPosition;
/**//// <summary>
/// 按钮普通状态图像矩形
/// </summary>
protected Rectangle buttonRectOrigin;
/**//// <summary>
/// 按钮高亮状态图像矩形
/// </summary>
protected Rectangle buttonRectHighlight;
/**//// <summary>
/// 按钮点击状态图像矩形
/// </summary>
protected Rectangle buttonRectClicked;
/**//// <summary>
/// 滑动条的长(高),由滚动条宽(高)度减去2倍按钮宽(高)度得出,默认为200。
/// </summary>
protected float sliderRailSizeX;
/**//// <summary>
/// 滑动条轨道图像矩形
/// </summary>
protected Rectangle sliderRailRect;
/**//// <summary>
/// 滑块位置
/// </summary>
protected Vector2 sliderButtonPosition;
/**//// <summary>
/// 滑块长(高)度,由变量sliderRailSizeX,maxStep和largeChange计算得出,默认为160。
/// </summary>
protected float sliderButtonSizeX;
/**//// <summary>
/// 滑块普通状态图像矩形
/// </summary>
protected Rectangle sliderButtonRectOrigin;
/**//// <summary>
/// 滑块高亮状态图像矩形
/// </summary>
protected Rectangle sliderButtonRectHighlight;
/**//// <summary>
/// 滑块点击状态图像矩形
/// </summary>
protected Rectangle sliderButtonRectClicked;
/**//// <summary>
/// 滑动条轨道位置
/// </summary>
protected Vector2 sliderRailPosition;
/**//// <summary>
/// 摩擦条图像矩形
/// </summary>
protected Rectangle sliderButtonGlyphRect;
/**//// <summary>
/// 可以前进的最大步数,默认为1,即滑块移动1步就可以从最左边移动到最右边。
/// </summary>
protected int maxStep=1;
/**//// <summary>
/// 当用户单击滚动箭头时,滑块变动的幅度,默认为1,即一次移动一格。
/// </summary>
protected int samllChange = 1;
/**//// <summary>
/// 当用户单击滚动条时滑块移动的幅度,即一页显示的段数,默认为4。
/// </summary>
protected int largeChange = 4;
/**//// <summary>
/// 滑动条的值,默认为0
/// </summary>
protected int value = 0;
/**//// <summary>
/// 是否处在滑动状态
/// </summary>
protected bool isScrolling = false;
/**//// <summary>
/// 创建一个滚动条抽象基类,内部方法,只能在引擎内部调用。水平滚动条和竖直滚动条是从这个基类继承的。
/// </summary>
/// <param name="engine">引擎</param>
/// <param name="setScene">所属场景</param>
/// <param name="setSizee">控件大小</param>
/// <param name="setPosition">2D屏幕位置</param>
/// <param name="setTextureName">纹理文件名称</param>
/// <param name="setSliderButtonRectOrigin">滑动条按钮普通状态矩形</param>
/// <param name="setSliderButtonRectHighlight">滑动条按钮高亮状态矩形</param>
/// <param name="setSliderButtonRectClicked">滑动条按钮点击状态矩形</param>
/// <param name="setSliderRailRect">滑动条轨道图像矩形</param>
/// <param name="setButtonRectOrigin">按钮普通状态图像矩形</param>
/// <param name="setButtonRectHighlight">按钮高亮状态图像矩形</param>
/// <param name="setButtonRectClicked">按钮点击状态图像矩形</param>
/// <param name="setSliderButtonGlypgRect">摩擦条图像矩形</param>
internal UIScrollBarBase(StunXnaGE engine, Scene setScene, Vector2 setSize,Vector2 setPosition, string setTextureName,Rectangle setButtonRectOrigin, Rectangle setButtonRectHighlight, Rectangle setButtonRectClicked, Rectangle setSliderRailRect, Rectangle setSliderButtonRectOrigin, Rectangle setSliderButtonRectHighlight, Rectangle setSliderButtonRectClicked, Rectangle setSliderButtonGlypgRect)
: base(engine ,setScene ,setPosition ,setTextureName)
...{
this.size = setSize;
//按钮图像矩形
this.buttonRectOrigin = setButtonRectOrigin;
this.buttonRectHighlight = setButtonRectHighlight;
this.buttonRectClicked = setButtonRectClicked;
//轨道图像矩形
this.sliderRailRect =setSliderRailRect;
//滑块图像矩形
this.sliderButtonRectOrigin =setSliderButtonRectOrigin ;
this.sliderButtonRectHighlight =setSliderButtonRectHighlight ;
this.sliderButtonRectClicked =setSliderButtonRectClicked;
//摩擦条图像矩形
this.sliderButtonGlyphRect = setSliderButtonGlypgRect;
}
#endregion
属性#region 属性
/**//// <summary>
/// 获取或设置按钮普通状态图像矩形
/// </summary>
public Rectangle ButtonRectOrigin
...{
get ...{ return buttonRectOrigin; }
set ...{ buttonRectOrigin = value; }
}
/**//// <summary>
/// 获取或设置按钮高亮状态图像矩形
/// </summary>
public Rectangle ButtonRectHighlight
...{
get ...{ return buttonRectHighlight; }
set ...{ buttonRectHighlight = value; }
}
/**//// <summary>
/// 获取或设置按钮点击状态图像矩形
/// </summary>
public Rectangle ButtonRectClicked
...{
get ...{ return buttonRectClicked; }
set ...{ buttonRectClicked = value; }
}
/**//// <summary>
/// 获取滑动条的长(高)
/// </summary>
public float SliderRailSizeX
...{
get ...{ return sliderRailSizeX; }
}
/**//// <summary>
/// 获取滑块长度(高)
/// </summary>
public float SliderButtonSizeX
...{
get ...{ return sliderButtonSizeX; }
}
/**//// <summary>
/// 获取或设置滑块普通状态图像矩形
/// </summary>
public Rectangle SliderButtonRectOrigin
...{
get ...{ return sliderButtonRectOrigin; }
set ...{ sliderButtonRectOrigin = value; }
}
/**//// <summary>
/// 获取或设置滑块高亮状态图像矩形
/// </summary>
public Rectangle SliderButtonRectHighlight
...{
get ...{ return sliderButtonRectHighlight; }
set ...{ sliderButtonRectHighlight = value; }
}
/**//// <summary>
/// 获取或设置滑块点击状态图像矩形
/// </summary>
protected Rectangle SliderButtonRectClicked
...{
get ...{ return sliderButtonRectClicked; }
set ...{ sliderButtonRectClicked = value; }
}
/**//// <summary>
/// 获取或设置摩擦条图像矩形
/// </summary>
public Rectangle SliderButtonGlyphRect
...{
get ...{ return sliderButtonGlyphRect; }
set ...{ sliderButtonGlyphRect = value; }
}
/**//// <summary>
/// 获取或设置滚动条的值
/// </summary>
public int Value
...{
get ...{ return this.value; }
set
...{
this.value = value;
if (this.value < 0)
this.value = 0;
else if (this.value > maxStep)
this.value = maxStep;
OnValueChanged();
}
}
/**//// <summary>
/// 获取或设置当用户单击滚动条时滑块移动的幅度,即一页显示的段数,默认为4。
/// </summary>
public int LargeChange
...{
get ...{ return largeChange; }
set
...{
largeChange = value;
Redraw();
}
}
/**//// <summary>
/// 获取或设置可以前进的最大步数,默认为2,即滑块移动2步就可以从最左边移动到最右边,当MaxStep为零时无需绘制滚动条。
/// </summary>
public int MaxStep
...{
get ...{ return maxStep; }
set
...{
maxStep = value;
if (maxStep <=0)
...{
maxStep = 0;
IsVisible = false;
}
else
IsVisible = true;
if (this.value > maxStep)
this.value = maxStep;
Redraw();
}
}
/**//// <summary>
/// 获取或设置当用户单击滚动箭头时,滑块变动的幅度,默认为1,即一次移动一格。
/// </summary>
public int SmallChange
...{
get ...{ return samllChange; }
set
...{
if (value > maxStep)
return;
samllChange = value;
Redraw();
}
}
#endregion
事件相关处理程序#region 事件相关处理程序
/**//// <summary>
/// 滑动条的值变化时引发的事件
/// </summary>
public event EventHandler ValueChanged;
/**//// <summary>
/// ValueChanged事件的处理程序,需要重新调整滑动条按钮的位置。
/// OnValueChanged方法允许继承的类处理这个事件。
/// 注意:当在继承类中覆写OnValueChanged时,一定要调用基类的OnValueChanged方法才可以注册委托
/// </summary>
protected virtual void OnValueChanged()
...{
ResetSliderButtonPosition();
if (ValueChanged != null)
ValueChanged(this.value, null);
}
/**//// <summary>
/// 点击下(右)按钮时的事件处理程序
/// </summary>
protected void btnDownRight_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
...{
if (Value < maxStep)
...{
Value += samllChange;
}
}
/**//// <summary>
/// 点击上(左)按钮时的事件处理程序
/// </summary>
protected void btnUpLeft_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
...{
if (value > 0)
...{
Value -= samllChange;
}
}
/**//// <summary>
/// 滚动条IsVisible属性改变时的处理程序
/// </summary>
protected override void OnVisibleChanged(EventArgs e)
...{
base.OnVisibleChanged(e);
btnLeftUp.IsVisible = !this.isVisible ;
btnRightDown.IsVisible = !this.isVisible ;
}
/**//// <summary>
/// 滚动条位置发生变化时需要重新绘制滚动条
/// </summary>
protected override void OnLocationChanged()
...{
base.OnLocationChanged();
Redraw();
}
/**//// <summary>
/// 滚动条大小发生变化时需要重新绘制滚动条
/// </summary>
protected override void OnSizeChanged()
...{
Redraw();
base.OnSizeChanged();
}
/**//// <summary>
/// 重新绘制滚动条
/// </summary>
protected virtual void Redraw()
...{ }
/**//// <summary>
/// 重置滑块位置
/// </summary>
protected virtual void ResetSliderButtonPosition()
...{ }
#endregion
}
而水平滚动条从基类继承,代码如下:
/**//// <summary>
/// 水平滚动条
/// </summary>
public class UIHScrollBar:UIScrollBarBase
...{
成员变量和构造函数#region 成员变量和构造函数
//默认值
private static readonly Rectangle DefaultButtonRectOrigin = new Rectangle(0, 0, 16, 16);
private static readonly Rectangle DefaultButtonRectHighlight = new Rectangle(16, 0, 16, 16);
private static readonly Rectangle DefaultButtonRectClicked = new Rectangle(32, 0, 16, 16);
/**//// <summary>
/// 默认水平滚动条大小为232*16。
/// </summary>
private static readonly Vector2 DefaultHSize = new Vector2(232, 16);
/**//// <summary>
/// 创建一个默认水平滚动条。
/// </summary>
/// <param name="engine">引擎</param>
/// <param name="setScene">所属场景</param>
public UIHScrollBar(StunXnaGE engine, Scene setScene)
: base(engine, setScene,DefaultHSize ,Vector2.Zero, "Textures/UI/UIScrollBar", DefaultButtonRectOrigin, DefaultButtonRectHighlight, DefaultButtonRectClicked,DefaultSliderRailRect, DefaultSliderButtonRectOrigin, DefaultSliderButtonRectHighlight, DefaultSliderButtonRectClicked, DefaultSliderButtonGlyphRect)
...{
//添加向左按钮
btnLeftUp = new UIButton(engine, setScene, btnLeftUpPositon, colorTextureName, new Vector2 (buttonWidth,buttonWidth ), null, engine.DefaultFont, Color.White, Color.White, buttonRectOrigin, buttonRectHighlight, buttonRectClicked);
btnLeftUp.TabStop = false;
btnLeftUp.MouseClick += new System.Windows.Forms.MouseEventHandler(btnUpLeft_MouseClick);
scene.AddNode(btnLeftUp);
//添加向右按钮
btnRightDown = new UIButton(engine, setScene, btnRightDownPosition, colorTextureName, new Vector2(buttonWidth, buttonWidth), null, engine.DefaultFont, Color.White, Color.White, ButtonRectOrigin, ButtonRectHighlight, ButtonRectClicked);
//将向右按钮水平翻转
btnRightDown.SpriteEffect = SpriteEffects.FlipHorizontally ;
btnRightDown.TabStop = false;
btnRightDown.MouseClick += new System.Windows.Forms.MouseEventHandler(btnDownRight_MouseClick);
scene.AddNode(btnRightDown);
//绘制水平滚动条
Redraw();
}
#endregion
public override void Update(GameTime gameTime)
...{
if (isVisible)
...{
//设置滑动条上按钮的位置,计算滑动条的值并引发ValueChanged事件
if (isMouseInside && Input.MouseLeftButtonPressed)
...{
isScrolling = true;
//根据鼠标移动改变滑块的水平位置
sliderButtonPosition.X = position.X + buttonWidth;
sliderButtonPosition.X += Input.MousePos.X - position.X - buttonWidth- sliderButtonSizeX / 2;
//不让滑块超出滑动条边界
if (sliderButtonPosition.X < position.X + buttonWidth)
sliderButtonPosition.X = position.X + buttonWidth;
else if (sliderButtonPosition.X > position.X + buttonWidth + sliderRailSizeX - sliderButtonSizeX)
sliderButtonPosition.X = position.X + buttonWidth + sliderRailSizeX - sliderButtonSizeX;
int value = 0;
value = (int)System.Math.Round((Input.MousePos.X - position.X - buttonWidth - sliderButtonSizeX / 2) * maxStep / (sliderRailSizeX - sliderButtonSizeX));
//不让value的值超出范围
if (value < 0)
value = 0;
else if (value > maxStep)
value = maxStep;
//如果值变化则重新设置新值并引发ValueChanged事件
if (this.value != value)
...{
this.value = value;
OnValueChanged();
}
}
else
isScrolling = false;
base.Update(gameTime);
}
}
/**//// <summary>
/// 绘制滚动条
/// </summary>
public override void Draw(GameTime gameTime, bool useReflection)
...{
if (isVisible)
...{
//获取图像淡入淡出的透明颜色
alphaTextureColor = new Color(color, scene.TransitionAlpha);
//绘制滑动条轨道
StunXnaGE.SpriteBatch.Draw(material.Textures[0], new Rectangle((int)(sliderRailPosition.X) , (int)(sliderRailPosition.Y), (int)sliderRailSizeX, (int)buttonWidth), sliderRailRect, alphaTextureColor, rotation, origin, spriteEffect, layerDepth);
//根据鼠标移入还是按下设置滑动条按钮图像源矩阵
Rectangle buttonRect = isMouseInside || (uiManager.ActiveControl == this) ? ((Input.MouseLeftButtonPressed && isMouseInside) ? sliderButtonRectClicked : sliderButtonRectHighlight) : sliderButtonRectOrigin;
//绘制滑动条上的按钮
StunXnaGE.SpriteBatch.Draw(material.Textures[0], new Rectangle((int)sliderButtonPosition.X, (int)position.Y, (int)sliderButtonSizeX, (int)buttonWidth), buttonRect, alphaTextureColor, rotation, origin, spriteEffect, layerDepth);
//绘制滑块上的摩擦条,如果滑块长度小于滚动条高度则无需绘制摩擦条
if(sliderButtonSizeX >size .Y)
StunXnaGE.SpriteBatch.Draw(material.Textures[0], new Rectangle((int)(sliderButtonPosition.X + sliderButtonSizeX / 2 - buttonWidth / 2), (int)sliderButtonPosition.Y, (int)buttonWidth, (int)buttonWidth), sliderButtonGlyphRect, color, rotation, origin, spriteEffect, layerDepth);
}
}
事件相关处理程序#region 事件相关处理程序
/**//// <summary>
/// 重新绘制水平滚动条
/// </summary>
protected override void Redraw()
...{
//按钮高(宽)度即滚动条的高度
this.buttonWidth = size.Y;
//重新设置轨道的位置和大小
sliderRailPosition = new Vector2(position.X + buttonWidth, position.Y);
//滑动条宽度即滚动条宽度减去2倍按钮高度
this.sliderRailSizeX = size.X - 2 * buttonWidth;
//重新设置滑块的位置和大小
ResetSliderButtonPosition();
//滑块宽度由largeChange,sliderRailSizeX和maxStep计算得出
this.sliderButtonSizeX = largeChange * sliderRailSizeX / (largeChange + maxStep);
//重新设置左右按钮位置和大小
btnLeftUp.Position=btnLeftUpPositon = position;
btnRightDown.Position=btnRightDownPosition = position + new Vector2(size.X- buttonWidth, 0);
btnLeftUp.Size = btnRightDown.Size = new Vector2 (buttonWidth,buttonWidth );
}
protected override void ResetSliderButtonPosition()
...{
if (!isScrolling)
...{
//根据value值重置按钮的位置
sliderButtonPosition.X = position.X+buttonWidth+value *(sliderRailSizeX-sliderButtonSizeX ) / maxStep;
sliderButtonPosition.Y = position.Y;
//不让滑块超出滑动条边界
if (sliderButtonPosition.X < position.X + buttonWidth)
sliderButtonPosition.X = position.X + buttonWidth;
else if (sliderButtonPosition.X > position.X + buttonWidth + sliderRailSizeX - sliderButtonSizeX)
sliderButtonPosition.X = position.X + buttonWidth + sliderRailSizeX - sliderButtonSizeX;
}
}
#endregion
单元测试#region 单元测试
#if DEBUG
/**//// <summary>
/// 测试UIScrollbar类
/// </summary>
public static void TestUIScrollbar()
...{
UIHScrollBar HScrollbar = null;
UIVScrollBar VScrollbar = null;
UILabel lblValueH = null;
UILabel lblValueV = null;
TestGame.Start("测试UIScrollbar类,可按空格,上下键,数字键1-6",
delegate
...{
HScrollbar = new UIHScrollBar(TestGame.engine, TestGame.scene);
TestGame.scene.AddNode(HScrollbar);
HScrollbar.MaxStep = 5;
HScrollbar.LargeChange = 4;
HScrollbar.Position = new Vector2(100, 100);
VScrollbar = new UIVScrollBar(TestGame.engine, TestGame.scene);
TestGame.scene.AddNode(VScrollbar);
VScrollbar.Position = new Vector2(100, 150);
VScrollbar.MaxStep = 10;
VScrollbar.LargeChange = 2;
lblValueH = new UILabel(TestGame.engine, TestGame.scene);
lblValueH.Position = new Vector2(350, 100);
TestGame.scene.AddNode(lblValueH);
lblValueV = new UILabel(TestGame.engine, TestGame.scene);
lblValueV.Position = new Vector2(140, 150);
TestGame.scene.AddNode(lblValueV);
//关闭相机控制器
TestGame.scene.fpsCamCtrl.Enabled = false;
},
delegate
...{
//显示水平滑动条的值
lblValueH.Text = HScrollbar.Value.ToString();
//显示竖直滑动条的值
lblValueV.Text = VScrollbar.Value .ToString();
//按空格键测试IsVisible属性
if (Input.KeyboardSpaceJustPressed)
...{
HScrollbar.IsVisible = !HScrollbar.IsVisible;
VScrollbar.IsVisible = !VScrollbar.IsVisible;
}
//按向上键测试水平滚动条的Position属性
if (Input.KeyboardUpJustPressed)
HScrollbar.Position += new Vector2(5, 5);
//按数字键1测试水平滚动条的Size属性
if (Input.KeyboardKeyJustPressed(Keys.D1))
HScrollbar.Size += new Vector2(5, 5);
if(Input.KeyboardKeyJustPressed(Keys.D2))
HScrollbar.SmallChange+=1;
if(Input.KeyboardKeyJustPressed(Keys.D3))
HScrollbar.MaxStep-=1;
if (Input.KeyboardDownJustPressed)
VScrollbar.Position += new Vector2(5, 5);
if (Input.KeyboardKeyJustPressed(Keys.D4))
VScrollbar.Size += new Vector2(5, 5);
if(Input.KeyboardKeyJustPressed(Keys.D5))
VScrollbar.SmallChange+=1;
if(Input.KeyboardKeyJustPressed(Keys.D6))
VScrollbar.MaxStep+=1;
});
}
#endif
#endregion
}
竖直滚动条的代码与水平滚动条的代码类似,不再赘述,最主要的不同是在绘制轨道和滑块时需要将它们的Rotation属性从0变为MathHelper.PiOver2,表示顺时针旋转90度,这样做可以重用轨道和滑块的图像,无需再建立一个竖直轨道和竖直滑块的图像。
单元测试截图如下:
发布时间:2010/1/28 上午8:32:23 阅读次数:7781