状态模式

状态模式是设计模式中的一种行为设计模式,对很多人来说,这个模式平时可能用不到。但是如果你做游戏开发的话,我相信你应该对这个模式有一个很深刻的理解。状态模式在游戏中开发中还是比较常见的。状态模式将状态的行为封装在独立的状态类中,使得状态转换变得更加清晰和易于管理。这样的话,对象只负责状态的切换,不负责具体的行为。

比如射击类游戏:玩家模式是原地站立状态,当用户按下前进的时候,玩家的状态切换为前进状态,当按下后退的时候,状态切换为后退状态。当按下鼠标左键时,玩家进入射击状态。对于同一个玩家对象来说,不同的状态下,都是由不同的行为逻辑。这些逻辑,如果全部都在玩家这个对象类中实现的话,将会非常复杂。状态模式,就是用来解决这个问题的。

状态模式的主要组成部分包括:
(1)、上下文(Context):维护一个具体状态的实例,这个实例定义了当前的状态。
(2)、状态接口(State):定义了所有具体状态类的公共接口。
(3)、 具体状态类(Concrete States):实现状态接口,并根据上下文的状态改变其行为。

下面用C#简单的写一个状态模式的代码,来实现上面的玩家类。

1、定义状态接口IPlayerState表示玩家状态,Handle用于处理不同状态下玩家的具体行为

public interface IPlayerState
{
void Handle(PlayerContext context);
}

2、定义具体状态类:默认站立状态类:DefaultState 、前进状态:MoveForwardState 、后退状态:MoveBackwardState 、射击状态:DesignState 、死亡状态:DeadState

public class DefaultState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家处于默认站立状态.");
// 状态转换逻辑
}
} public class MoveForwardState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家正在前进....");
// 状态转换逻辑 这里可以实时渲染玩家位置
}
} public class MoveBackwardState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家正在后退...");
// 状态转换逻辑 这里可以实时渲染玩家位置
}
} public class DesignState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家正在射击...");
// 状态转换逻辑 这里可以判断玩家是否击中对方,更新对方血条和自己血条
}
} public class DeadState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家死亡...");
// 状态转换逻辑 这里可以对本局游戏进行玩家分数结算
}
}

从上面可以看到,不同的状态,玩家的逻辑被清晰的描述出来。这样的话,我们就不用在玩家类Player中,通过if-else来来回回切换状态,实现业务逻辑,结构也会非常清晰。

3、定义上下文类:上下文类中存储了玩家的当前状态,并且可以通过上下文切换玩家状态。这是状态类中最基本的元素,当然还可以包含其他的状态数据,比如玩家的实时坐标,玩家的血条信息等等。

public class PlayerContext
{
private IPlayerState _state; public PlayerContext()
{
_state = new DefaultState(); // 初始状态为默认状态
} public void SetState(IPlayerState state)
{
_state = state;
} public void Request()
{
_state.Handle(this);
}
}

4、在对象中,切换状态,调用对应的状态类逻辑。当然我们只是在这里手动切换状态 ,实际开发中,我们一般是监听鼠标键盘事件或者鼠标事件之后切换玩家状态。

class Program
{
static void Main(string[] args)
{
PlayerContext context = new PlayerContext(); // 初始状态为默认状态
context.Request(); // 切换到前进状态
context.SetState(new MoveForwardState());
context.Request(); // 切换到后退状态
context.SetState(new MoveBackwardState());
context.Request(); // 切换到设计状态
context.SetState(new DesignState());
context.Request(); // 切换到死亡状态
context.SetState(new DeadState());
context.Request();
}
}

基本原理

介绍完状态模式的基本远离之后,接下来介绍截图功能的基本原理:通过捕获屏幕上的特定区域并将其保存为图像文件,在WPF中,我们可以使用System.Drawing命名空间中的类来实现这一功能。具体步骤如下:

1.    捕获屏幕区域:使用Graphics.CopyFromScreen方法从屏幕上复制指定区域的像素。

2.    保存图像:将捕获的像素数据保存为位图(Bitmap)格式。

3.    显示或处理图像:将位图转换为WPF可以处理的BitmapSource格式,以便在界面上显示或进一步处理。

我们先来看下如何保存图像:

public static System.Drawing.Bitmap Snapshot(int x, int y, int width, int height)
{
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(x, y, 0, 0, new System.Drawing.Size(width, height), System.Drawing.CopyPixelOperation.SourceCopy);
}
return bitmap;
}

在截图过程中,我们使用了状态模式来管理截图过程中的不同状态。

当我们按下快捷键的时候, 处于默认状态,具体行为是:将一个WPF窗体背景设置为透明,宽高设置为和屏幕大小一致。

当我们按下鼠标左键的时候,处理开始截图状态(鼠标左键单击事件),具体行为是:记录截图的开始坐标,也就是截图矩形的左上角坐标。

当按下鼠标开始移动端时候,处于截图中状态(鼠标移动事件),具体行为是:不断记录截图的实时坐标,作为截图区域的右下角坐标,同时用户选中的区域,背景色要设置成亮色,可以清晰可见。

当放开鼠标的时候,处于截图结束状态(鼠标左键抬起事件),具体行为是:记录截图的终止坐标,也就是截图举行的右下角坐标。

当下鼠标移动的时候,处于移动中状态,已经选好的截图区域是可以移动的,具体行为是:记录鼠标移动 偏差,动态设置选中区域的背景色。

当然还有其他的状态类,就不一一描述了。

首先定义我们的状态类接口IScreentState :表示截图状态类

public interface IScreentState
{
void ProcessState(StateContext context);
}

其次定义具体的状态实现类:StartState、SelectState、SelectingState、SelectedState、MoveState、MovingState、MovedState、EndState总共有8个状态了,这里我只实现了截图和移动功能,并没有实现拖拽功能,如果需要拖拽修改截图区域大小功能,大家可以自行实现。

public class StartState : IScreentState
{
public void ProcessState(StateContext context)
{
SnapShotWindow win = context._window;
win.clipRect.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#8000"));
win.Cursor = Cursors.Arrow;
win.leftPanel.Width = 0;
win.topPanel.Height = 0;
win.rightPanel.Width = 0;
win.bottomPanel.Height = 0;
}
} public class SelectState : IScreentState
{
public void ProcessState(StateContext context)
{
context._startPoint = context._args.GetPosition(context._window);
}
} public class SelectingState : IScreentState
{
public void ProcessState(StateContext context)
{
SnapShotWindow win = context._window;
win.clipRect.Background = Brushes.Transparent;
context._endPoint = context._args.GetPosition(win);
win.leftPanel.Width = context._startPoint.X;
win.topPanel.Height = context._startPoint.Y;
win.rightPanel.Width = win.ActualWidth - context._endPoint.X;
win.bottomPanel.Height = win.ActualHeight - context._endPoint.Y;
win.snapShotInfo.Text = context.GetText();
}
} public class SelectedState : IScreentState
{
public void ProcessState(StateContext context)
{
context._endPoint = context._args.GetPosition(context._window);
}
} public class MoveState : IScreentState
{
public void ProcessState(StateContext context)
{
SnapShotWindow win = context._window;
context._mouseDownPosition = context._args.GetPosition(win);
context._mouseDownMargin = new Thickness(win.leftPanel.ActualWidth, win.topPanel.ActualHeight, win.rightPanel.ActualWidth, win.bottomPanel.Height); ; var relativePosition = context._args.GetPosition(win);
if (relativePosition.X >= 0 && relativePosition.X <= win.clipRect.ActualWidth &&
relativePosition.Y >= 0 && relativePosition.Y <= win.clipRect.ActualHeight)
{
context._allowMove = true;
win.Cursor = Cursors.SizeAll;
}
}
} public class MovingState : IScreentState
{
public void ProcessState(StateContext context)
{
SnapShotWindow win = context._window;
win.Cursor = Cursors.SizeAll;
//todo 隐藏操作按钮
var pos = context._args.GetPosition(win); //拖拽后鼠标的位置
var dp = pos - context._mouseDownPosition; //鼠标移动的偏移量
Thickness newThickness = new Thickness(context._mouseDownMargin.Left + dp.X,
context._mouseDownMargin.Top + dp.Y,
context._mouseDownMargin.Right - dp.X,
context._mouseDownMargin.Bottom - dp.Y);
win.leftPanel.Width = newThickness.Left < 0 ? 0 : newThickness.Left;
win.topPanel.Height = newThickness.Top < 0 ? 0 : newThickness.Top;
win.rightPanel.Width = newThickness.Right < 0 ? 0 : newThickness.Right;
win.bottomPanel.Height = newThickness.Bottom < 0 ? 0 : newThickness.Bottom;
win.snapShotInfo.Text = context.GetText();
}
} public class MovedState : IScreentState
{
public void ProcessState(StateContext context)
{
//todo 显示操作按钮
}
} public class EndState : IScreentState
{
public void ProcessState(StateContext context)
{
}
}

最后是状态上下文类:

public class StateContext
{
public IScreentState _currentState; public SnapShotWindow _window;
public MouseEventArgs _args;
//框选开始坐标
public Point _startPoint;
//框选结束坐标
public Point _endPoint;
//目前状态
public ScreenState _state;
//开始拖拽时,鼠标按下的位置
public Point _mouseDownPosition;
//开始拖拽时,鼠标按下控件的Margin
public Thickness _mouseDownMargin;
public bool _allowMove = false; private readonly IScreentState _startState;
private readonly IScreentState _selectState;
private readonly IScreentState _selectingState;
private readonly IScreentState _selectedState;
private readonly IScreentState _moveState;
private readonly IScreentState _movingState;
private readonly IScreentState _movedState;
private readonly IScreentState _endState; public StateContext(SnapShotWindow window)
{
_window = window;
_startState = new StartState();
_selectState = new SelectState();
_selectingState = new SelectingState();
_selectedState = new SelectedState();
_moveState = new MoveState();
_movingState = new MovingState();
_movedState = new MovedState();
_endState = new EndState(); _state = ScreenState.Start;
_currentState = _startState;
SetNewState(_state);
} public void SetNewState(ScreenState state)
{
_state = state;
switch (state)
{
case ScreenState.Start:
_currentState = _startState;
break;
case ScreenState.Select:
_currentState = _selectState;
break;
case ScreenState.Selecting:
_currentState = _selectingState;
break;
case ScreenState.Selected:
_currentState = _selectedState;
break;
case ScreenState.Move:
_currentState = _moveState;
break;
case ScreenState.Moving:
_currentState = _movingState;
break;
case ScreenState.Moved:
_currentState = _movedState;
break;
case ScreenState.End:
_currentState = _endState;
break;
}
_currentState.ProcessState(this);
} public void SetNewState(ScreenState state, MouseEventArgs args)
{
_args = args;
var point = args.GetPosition(_window);
SetNewState(state);
} public string GetText(double offsetX = 0, double offsetY = 0)
{
Point leftTop = _window.clipRect.PointToScreen(new Point(0, 0));
Point rightBottom = _window.clipRect.PointToScreen(new Point(_window.clipRect.ActualWidth, _window.clipRect.ActualHeight)); double width = Math.Round(Math.Abs(rightBottom.X - leftTop.X) + offsetX);
double height = Math.Round(Math.Abs(rightBottom.Y - leftTop.Y) + offsetY); return $"{leftTop} {width}×{height}";
} public bool IsInClipRect(Point point)
{
var relativePosition = point;
if (relativePosition.X >= 0 && relativePosition.X <= _window.clipRect.ActualWidth &&
relativePosition.Y >= 0 && relativePosition.Y <= _window.clipRect.ActualHeight)
{
return true;
}
return false;
}
}

两外XAML主界面的布局,就不给大家贴出来,源代码已经上传到github:https://github.com/caoruipeng123/ScreenApp

接下来看下实际的运行效果:进入截图页面之后,单击鼠标坐标开始框选截图区域,双击鼠标左键,可以结束截图,并且图片会设置到操作系统的粘贴板上,你可以把图片粘贴到任何位置。

用状态模式开发一个基于WPF的截图功能的更多相关文章

  1. 用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(1)

    最近使用vscode比较多. 学习了一下如何在mac上使用vscode开发asp.netcore项目. 这里是我写的关于vscode的一篇文章: https://www.cnblogs.com/cgz ...

  2. 用Vue开发一个实时性时间转换功能,看这篇文章就够了

    前言 最近有一个说法,如果你看见某个网站的某个功能,你就大概能猜出背后的业务逻辑是怎么样的,以及你能动手开发一个一毛一样的功能,那么你的前端技能算是进阶中高级水平了.比如咱们今天要聊的这个话题:如何用 ...

  3. WPF C#截图功能 仿qq截图

    原文:WPF C#截图功能 仿qq截图 先上效果图 源码下载地址:http://download.csdn.net/detail/candyvoice/9788099 描述:启动程序,点击窗口butt ...

  4. 开发一个基于 Android系统车载智能APP

    很久之前就想做一个车载相关的app.需要实现如下功能: (1)每0.2秒更新一次当前车辆的最新速度值. (2)可控制性记录行驶里程. (3)不连接网络情况下获取当前车辆位置.如(北京市X区X路X号) ...

  5. 利用MVC编程模式-开发一个简易记事本app

    学了极客学院一个开发记事本的课程,利用自己对MVC编程模式的简单理解重写了一遍该app. github地址:https://github.com/morningsky/MyNote MVC即,模型(m ...

  6. 准备开发一个基于canvas的图表库,记录一些东西(一)

    开源的图表库已经有很多了,这里从头写个自己的,主要还是 提高自己js的水平,增加复杂代码组织的经验 首先写一个画图的库,供以后画图表使用.经过2天的开发,算是能拿出点东西了,虽然功能还很弱,但是有了一 ...

  7. 用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(3)

    第一部分: http://www.cnblogs.com/cgzl/p/8478993.html 第二部分: http://www.cnblogs.com/cgzl/p/8481825.html 由于 ...

  8. [系统开发] 一个基于Django和PureCSS的内容管理系统

    这是我刚开发的一套基于Django和PureCSS的内容管理系统,目标是优雅.简洁.实用,目前功能还在完善中. 系统参考了网上的教程,除了文章管理.搜索.RSS,还增加了类别管理.用户管理,以及评论管 ...

  9. 用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(2)

    第一部分: http://www.cnblogs.com/cgzl/p/8478993.html 为Domain Model添加约束 前一部分, 我们已经把数据库创建出来了. 那么我们先看看这个数据库 ...

  10. 如何开发一个基于 Docker 的 Python 应用

    前言 Python 家族成员繁多,解决五花八门的业务需求.这里将通过 Python 明星项目 IPython Notebook,使其容器化,让大家掌握基础的 Docker 使用方法. IPython ...

随机推荐

  1. [转]CMake与Make最简单直接的区别

    写程序大体步骤为: 1.用编辑器编写源代码,如.c文件. 2.用编译器编译代码生成目标文件,如.o. 3.用链接器连接目标代码生成可执行文件,如.exe. 但如果源文件太多,一个一个编译时就会特别麻烦 ...

  2. 基于Netty,从零开发IM(四):编码实践篇(系统优化)

    本文由作者"大白菜"分享,有较多修订和改动.注意:本系列是给IM初学者的文章,IM老油条们还望海涵,勿喷! 1.引言 前两篇<编码实践篇(单聊功能)>.<编码实践 ...

  3. 特殊数据类型的深度分析:JSON、数组和 HSTORE 的实用价值

    title: 特殊数据类型的深度分析:JSON.数组和 HSTORE 的实用价值 date: 2025/1/4 updated: 2025/1/4 author: cmdragon excerpt: ...

  4. 冷水机超频AMD 2600X 4.5G

    整个测试大概持续了一个半小时,期间水温保持在2度左右(±1摄氏度).后来开始冷凝,而且超频也到了4.5G瓶颈,所以停了下来. 全核4.4G过R154.5G过CPU-Z认证(点击查看)温度表现,一般吧. ...

  5. nvm的安装与使用,多个node版本同时使用

    nvm的介绍 nvm全英文也叫node.js version management,是一个nodejs的版本管理工具.nvm和npm都是node.js版本管理工具,为了解决node.js各种版本存在不 ...

  6. nginx 简单实践:静态资源部署、URL 重写【nginx 实践系列之一】

    〇.前言 本文为 nginx 简单实践系列文章之一,主要简单实践了两个内容:静态资源部署.重写,仅供参考. 关于 Nginx 基础,以及安装和配置详解,可以参考博主过往文章: https://www. ...

  7. 通过WebView2获取HTTP-only cookie

    通过WebView2获取HTTP-only cookie可以使用`WebView2.CookieManager`类的方法.以下是一个示例代码,演示如何获取HTTP-only cookie: using ...

  8. cpa-审计

    1.审计概述 2.审计计划 3.审计证据 4.审计抽样方法 5.信息技术对审计的影响 6.审计工作底稿 7.风险评估 8.风险应对 9.销售与收款循环的审计 10.采购与付款循环的审计 11.生产与存 ...

  9. Java并发包常用类用法及原理

    com.java.util.concurrent包是java5时添加的,专门处理多线程提供的工具类 一.Atomic 二.Lock 三.BlockingQueue 四.BlockDeque 五.Con ...

  10. SSH 跳板机原理与配置:实现无缝跳板连接,一步直达目标主机

    前言 在日常运维或开发工作中,我们常常需要访问部署在内网的服务器.然而出于安全策略或网络拓扑的限制,内网服务器并不会直接向外部暴露端口,导致我们无法"直连"它们.此时,跳板机(Ju ...