博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
软件易容术-----换肤
阅读量:2400 次
发布时间:2019-05-10

本文共 14338 字,大约阅读时间需要 47 分钟。

1, 看看成果: 

换肤前 
单击显示全图,Ctrl+滚轮缩放图片 

换肤后 
单击显示全图,Ctrl+滚轮缩放图片 

(说明: 这里仅仅借用的是"千千静听"中皮肤包中的图片, 本示例程序中的皮肤包文件格式以及换肤方案均为作者原创) 


2  如何实现: 

2.1 皮肤配置文件: 

在研究如何实现换肤之前,应该仔细看看以下XML文件,它是皮肤配置文件,由它来组织皮肤包中的图片并告诉软件如何来绘制皮肤: 
复制
  
保存
player_skin.bmp
1
1
17
17
icon.ico
164
39
183
58
play.bmp
164
39
183
58
pause.bmp
145
39
164
58
stop.bmp
202
39
221
58
next.bmp
183
39
202
58
prev.bmp
249
39
268
58
mute.bmp
221
39
240
58
open.bmp
152
92
188
114
equalizer.bmp
188
92
224
114
playlist.bmp
224
92
260
114
lyric.bmp
18
24
124
84
240
3
247
10
minimize.bmp
253
3
260
10
minimode.bmp
266
3
273
10
close.bmp
145
77
267
90
progress_thumb.bmp
progress_fill.bmp
274
20
283
84
volume_fill.bmp
true
147
20
268
31
#ff9933
simsong
9
MiddleLeft
175
63
235
74
number.bmp
MiddleRight
13
98
55
109
#ff9933
simsong
9
MiddleLeft
59
98
129
109
#ff9933
simsong
9
MiddleLeft
Orange
#ff00ff

很明显地,该配置文件中规定了皮肤中的各个元素的位置大小等相关信息. 比如: 
复制
  
保存
164
39
183
58
play.bmp

规定的"播放"按钮在主窗口中的位置以及其对应的图片. 

很简单地,我们只要解析该XML文件并将其对应的图片绘制在指定的位置就可以了(当然此时还只是视觉上的,因为按钮还有事件等,稍后讨论) 


2.2 如何组织窗口上的元素 

"窗口上的元素"指的是窗口上的"按钮","滚动条"等. 注意:它们不是"Button" "ScrollBar"等控件,它们只是在指定位置上的图片,并响应相应事件) 
为更好地组织各个元素,我们设计了一个"皮肤元素接口",让所有元素都实现该接口: 
复制
  
保存
public interface ISkinElement {
string Name {
get; } Position Position {
get; set; } ElementStatus Status {
get; set; } void Paint(Graphics g); event SkinElementStatusChangedHandler SkinElementStatusChanged; }

其中Name表示该元素的名称. 

Position规定了该元素的大小和位置,它是这样一个结构: 
复制
  
保存
///  /// 位置信息,由左上角X、Y坐标和右下角X、Y坐标组成 ///  public struct Position {
public int Left; public int Top; public int Right; public int Bottom; public Position(int left, int top, int right, int bottom) {
this.Left = left; this.Top = top; this.Right = right; this.Bottom = bottom; } }

Status表示该元素的当前状态,它是这样一个枚举: 
复制
  
保存
///  /// 元素状态 ///  public enum ElementStatus {
Normal, MouseHover, MouseDown, Disabled }

void Paint(Graphics g)是该元素的绘制函数 
event SkinElementStatusChangedHandler SkinElementStatusChanged;是该元素状态发生改变时所引发的事件. 

然后,比如"播放"按钮,就可以是如下的一个类: 
复制
  
保存
///  /// 播放元素(播放按钮) ///  public class PlayElement : ISkinElement {
private Position position; private string image = string.Empty; [NonSerialized] private ElementStatus status; #region SkinElement 成员 /// /// 获取该元素的名称 /// public string Name {
get { return "PlayElement"; } } /// /// 获取或设置该元素的位置 /// public Position Position {
get { return this.position; } set { this.position = value; } } /// /// 绘制该元素 /// public void Paint(Graphics g) {
SkinPainter.PaintSkinElement(g, this.position, this.image, this.status); } /// /// 获取或设置该对象的状态 /// public ElementStatus Status {
get { return this.status; } set {
if (this.status != value) {
this.status = value; this.OnSkinElementStatusChanged(new SkinElementStatusChangedArges(value)); } } } /// /// 当元素状态改变时发生 /// public event SkinElementStatusChangedHandler SkinElementStatusChanged; #endregion protected void OnSkinElementStatusChanged(SkinElementStatusChangedArges arg) {
if (this.SkinElementStatusChanged != null) {
this.SkinElementStatusChanged(this, arg); } } /// /// 获取或设置该对象对应的图片文件(相对于皮肤文件夹的相对路径) /// public string Iamge {
get { return this.image; } set { this.image = value; } } }

其它元素同理. 

2.3 如何绘制元素 

以按钮为例: 
一张按钮图片由四部分组成 , 单击显示全图,Ctrl+滚轮缩放图片它分别代表按钮的不同状态(Normal,MouseEnter,MouseDown, Disable), 我们只需要根据按钮的当前状态切取图片的不同部分并绘制在指定位置上便可.比如: 
复制
  
保存
///  /// 绘制普通的类似于按钮的皮肤元素。 /// 进度条、音量控制等不应该采用此函数 ///  /// 用其进行绘制 /// 绘制的位置 /// 绘制的图片的路径(相对路径,在此之前应该确定SkinRootDir属性已经被设置) /// 皮肤元素的当前状态 public static void PaintSkinElement(Graphics g, Position pos, string imgPath, ElementStatus status) {
ImageAttributes imgAttributes = new ImageAttributes(); imgAttributes.SetColorKey(transparentKey, transparentKey); try {
Image img = Image.FromFile(SkinRootDir + System.IO.Path.DirectorySeparatorChar + imgPath); Rectangle destRect = new Rectangle(pos.Left, pos.Top, pos.Right - pos.Left, pos.Bottom - pos.Top); int x = 0; int y = 0; int width = img.Width / 4; int height = img.Height; switch (status) {
case ElementStatus.Normal: x = 0; break; case ElementStatus.MouseHover: x = width; break; case ElementStatus.MouseDown: x = 2 * width; break; case ElementStatus.Disabled: x = 3 * width; break; default: break; } g.DrawImage(img, destRect, x, y, width, height, GraphicsUnit.Pixel, imgAttributes); } catch {
} }


2.4 如何响应键盘鼠标等事件 

以鼠标事件为例: 
其实皮肤上的元素并没有这些事件,我们只是当用户点击主窗口时,根据鼠标点击的位置来确定点击在了哪个元素指上,并引发该元素所对应的事件. 
查找鼠标点击的元素: 
复制
  
保存
///  /// 确定在指定的皮肤元素集合中,指定的点包含在哪个元素中. /// 如果同时包含在多个元素中,则以最内层的那个为准. /// 
注意:由于元素之间没有Z轴层次关系,所以不应该让两个元素处在相交却不包含的关系中
///
/// 元素集合,在它们中间查找 /// 要进行判断的点 ///
如果不包含在指定的任一元素中,则返回null,否则返回包含该点的元素
public static ISkinElement FindSkinElementFormPosition(List
elementList, Point loc) {
Rectangle rect = new Rectangle(); Rectangle lastRect = Rectangle.Empty; ISkinElement res = null; foreach (ISkinElement element in elementList) {
rect.X = element.Position.Left; rect.Y = element.Position.Top; rect.Width = element.Position.Right - element.Position.Left; rect.Height = element.Position.Bottom - element.Position.Top; if (rect.Contains(loc)) {
if (lastRect == Rectangle.Empty || lastRect.Contains(rect)) {
lastRect = rect; res = element; } } } return res; }

比如,我们鼠标点击落在"关闭"元素内时,将关闭窗口: 
复制
  
保存
private void FormMain_MouseClick(object sender, MouseEventArgs e) {
ISkinElement element = Helper.FindSkinElementFormPosition(this.skinMainFormElementList, e.Location); if (element != null) {
switch (element.Name) {
case "ExitElement": this.Close(); break; default: break; } } }


2.5 局部区域更新 

比如鼠标移动到"播放"按钮上时,其状态将转换为"MouseEnter",将重新绘制该按钮以响应鼠标.此时只需要更新该按钮所对应的区域便可: 
复制
  
保存
private void FormMain_MouseMove(object sender, MouseEventArgs e) {
ISkinElement element = Helper.FindSkinElementFormPosition(this.skinMainFormElementList, e.Location); if (element != null && element.Status != ElementStatus.MouseHover && element.Status != ElementStatus.Disabled) {
element.Status = ElementStatus.MouseHover; this.UpdateSkinElement(element); } }

其中UpdateSkinElement(ISkinElement element): 
复制
  
保存
///  /// 更新指定皮肤元素(重新绘制皮肤的指定区域) ///  /// 要被重绘的元素 private void UpdateSkinElement(ISkinElement element) {
Rectangle updateRect = new Rectangle( element.Position.Left, element.Position.Top, element.Position.Right - element.Position.Left, element.Position.Bottom - element.Position.Top); this.Invalidate(updateRect); this.Update(); }


3 DEMO下载: 

转载地址:http://fjiob.baihongyu.com/

你可能感兴趣的文章
问题实录(二)(转)
查看>>
when i run tasksel,system give me following report,what's means?(转)
查看>>
问题实录(三)(转)
查看>>
Java Servlet和JSP教程(1)(转)
查看>>
Java Servlet和JSP教程(3)(转)
查看>>
Debian 安全手册 第 4 章 - 安装后(转)
查看>>
Java Servlet和JSP教程(4)(转)
查看>>
xp上的shutdown(转)
查看>>
转换RM为MP3(转)
查看>>
Java中文问题详解(转)
查看>>
制作多系统安装盘(转)
查看>>
Java Servlet和JSP教程(2)(转)
查看>>
问题实录(四)(转)
查看>>
ADO数据库编程入门(转)
查看>>
跑一圈就进入xp(转)
查看>>
Java Servlet和JSP教程(8)(转)
查看>>
在Win9x/2000下配置Apache1.3.22+Tomcat4.0.1(转)
查看>>
端口大全(转)
查看>>
Java Servlet和JSP教程(10)(转)
查看>>
怎样制作恢复光盘(转)
查看>>