博客
关于我
强烈建议你试试无所不能的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/

你可能感兴趣的文章
刘昕 hkust_在Linux上配置HKUST的sMobileNet
查看>>
win api发送键盘消息_使用Win32 API将消息发送到其他Windows
查看>>
Python“ for”循环(定迭代)
查看>>
软件测试应届生没有项目经历_为什么有些软件项目网站糟透了,而另一些却没有
查看>>
python入门测试教程_Python测试入门
查看>>
python概率编程_Python中的概率编程
查看>>
Python中的运算符和表达式
查看>>
读写csv文件python_用Python读写CSV文件
查看>>
python super_使用Python super()增强您的课程
查看>>
愚人节导入_愚人节Python恶作剧
查看>>
正则表达式科学计数法_数据科学家的正则表达式
查看>>
sql基础_SQL基础
查看>>
一个工作表可以有两个事件吗_你有两个工作
查看>>
Raul的新机器学习书!
查看>>
python制作可视化图表_可视化数据–用python覆盖图表
查看>>
双耳节拍 枕头_枕头3-0-0不在
查看>>
输入/help获取更多指令_更多HTTP / 2新闻
查看>>
rodeo python_Rodeo 1.0:台式机上的Python IDE
查看>>
MongoDB和Python简介
查看>>
django 认证_Django中的LinkedIn社会认证
查看>>