WPF用狀態(tài)模式開發(fā)截圖功能
狀態(tài)模式
狀態(tài)模式是設(shè)計(jì)模式中的一種行為設(shè)計(jì)模式,對(duì)很多人來(lái)說(shuō),這個(gè)模式平時(shí)可能用不到。但是如果你做游戲開發(fā)的話,我相信你應(yīng)該對(duì)這個(gè)模式有一個(gè)很深刻的理解。狀態(tài)模式在游戲中開發(fā)中還是比較常見的。狀態(tài)模式將狀態(tài)的行為封裝在獨(dú)立的狀態(tài)類中,使得狀態(tài)轉(zhuǎn)換變得更加清晰和易于管理。這樣的話,對(duì)象只負(fù)責(zé)狀態(tài)的切換,不負(fù)責(zé)具體的行為。
比如射擊類游戲:玩家模式是原地站立狀態(tài),當(dāng)用戶按下前進(jìn)的時(shí)候,玩家的狀態(tài)切換為前進(jìn)狀態(tài),當(dāng)按下后退的時(shí)候,狀態(tài)切換為后退狀態(tài)。當(dāng)按下鼠標(biāo)左鍵時(shí),玩家進(jìn)入射擊狀態(tài)。對(duì)于同一個(gè)玩家對(duì)象來(lái)說(shuō),不同的狀態(tài)下,都是由不同的行為邏輯。這些邏輯,如果全部都在玩家這個(gè)對(duì)象類中實(shí)現(xiàn)的話,將會(huì)非常復(fù)雜。狀態(tài)模式,就是用來(lái)解決這個(gè)問(wèn)題的。
狀態(tài)模式的主要組成部分包括:
(1)、上下文(Context):維護(hù)一個(gè)具體狀態(tài)的實(shí)例,這個(gè)實(shí)例定義了當(dāng)前的狀態(tài)。
(2)、狀態(tài)接口(State):定義了所有具體狀態(tài)類的公共接口。
(3)、 具體狀態(tài)類(Concrete States):實(shí)現(xiàn)狀態(tài)接口,并根據(jù)上下文的狀態(tài)改變其行為。
下面用C#簡(jiǎn)單的寫一個(gè)狀態(tài)模式的代碼,來(lái)實(shí)現(xiàn)上面的玩家類。
1、定義狀態(tài)接口IPlayerState表示玩家狀態(tài),Handle用于處理不同狀態(tài)下玩家的具體行為
public interface IPlayerState
{
void Handle(PlayerContext context);
}2、定義具體狀態(tài)類:默認(rèn)站立狀態(tài)類:DefaultState 、前進(jìn)狀態(tài):MoveForwardState 、后退狀態(tài):MoveBackwardState 、射擊狀態(tài):DesignState 、死亡狀態(tài):DeadState
public class DefaultState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家處于默認(rèn)站立狀態(tài).");
// 狀態(tài)轉(zhuǎn)換邏輯
}
}
public class MoveForwardState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家正在前進(jìn)....");
// 狀態(tài)轉(zhuǎn)換邏輯 這里可以實(shí)時(shí)渲染玩家位置
}
}
public class MoveBackwardState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家正在后退...");
// 狀態(tài)轉(zhuǎn)換邏輯 這里可以實(shí)時(shí)渲染玩家位置
}
}
public class DesignState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家正在射擊...");
// 狀態(tài)轉(zhuǎn)換邏輯 這里可以判斷玩家是否擊中對(duì)方,更新對(duì)方血條和自己血條
}
}
public class DeadState : IPlayerState
{
public void Handle(PlayerContext context)
{
Console.WriteLine("玩家死亡...");
// 狀態(tài)轉(zhuǎn)換邏輯 這里可以對(duì)本局游戲進(jìn)行玩家分?jǐn)?shù)結(jié)算
}
}從上面可以看到,不同的狀態(tài),玩家的邏輯被清晰的描述出來(lái)。這樣的話,我們就不用在玩家類Player中,通過(guò)if-else切換狀態(tài),實(shí)現(xiàn)業(yè)務(wù)邏輯,結(jié)構(gòu)也會(huì)非常清晰。
3、定義上下文類:上下文類中存儲(chǔ)了玩家的當(dāng)前狀態(tài),并且可以通過(guò)上下文切換玩家狀態(tài)。這是狀態(tài)類中最基本的元素,當(dāng)然還可以包含其他的狀態(tài)數(shù)據(jù),比如玩家的實(shí)時(shí)坐標(biāo),玩家的血條信息等等。
public class PlayerContext
{
private IPlayerState _state;
public PlayerContext()
{
_state = new DefaultState(); // 初始狀態(tài)為默認(rèn)狀態(tài)
}
public void SetState(IPlayerState state)
{
_state = state;
}
public void Request()
{
_state.Handle(this);
}
}4、在對(duì)象中,切換狀態(tài),調(diào)用對(duì)應(yīng)的狀態(tài)類邏輯。當(dāng)然我們只是在這里手動(dòng)切換狀態(tài) ,實(shí)際開發(fā)中,我們一般是監(jiān)聽鼠標(biāo)鍵盤事件或者鼠標(biāo)事件之后切換玩家狀態(tài)。
class Program
{
static void Main(string[] args)
{
PlayerContext context = new PlayerContext();
// 初始狀態(tài)為默認(rèn)狀態(tài)
context.Request();
// 切換到前進(jìn)狀態(tài)
context.SetState(new MoveForwardState());
context.Request();
// 切換到后退狀態(tài)
context.SetState(new MoveBackwardState());
context.Request();
// 切換到設(shè)計(jì)狀態(tài)
context.SetState(new DesignState());
context.Request();
// 切換到死亡狀態(tài)
context.SetState(new DeadState());
context.Request();
}
}基本原理
介紹完?duì)顟B(tài)模式的基本遠(yuǎn)離之后,接下來(lái)介紹截圖功能的基本原理:通過(guò)捕獲屏幕上的特定區(qū)域并將其保存為圖像文件,在WPF中,我們可以使用System.Drawing命名空間中的類來(lái)實(shí)現(xiàn)這一功能。具體步驟如下:
1. 捕獲屏幕區(qū)域:使用Graphics.CopyFromScreen方法從屏幕上復(fù)制指定區(qū)域的像素。
2. 保存圖像:將捕獲的像素?cái)?shù)據(jù)保存為位圖(Bitmap)格式。
3. 顯示或處理圖像:將位圖轉(zhuǎn)換為WPF可以處理的BitmapSource格式,以便在界面上顯示或進(jìn)一步處理。
我們先來(lái)看下如何保存圖像:
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;
}在截圖過(guò)程中,我們使用了狀態(tài)模式來(lái)管理截圖過(guò)程中的不同狀態(tài)。
當(dāng)我們按下快捷鍵的時(shí)候, 處于默認(rèn)狀態(tài),具體行為是:將一個(gè)WPF窗體背景設(shè)置為透明,寬高設(shè)置為和屏幕大小一致。
當(dāng)我們按下鼠標(biāo)左鍵的時(shí)候,處理開始截圖狀態(tài)(鼠標(biāo)左鍵單擊事件),具體行為是:記錄截圖的開始坐標(biāo),也就是截圖矩形的左上角坐標(biāo)。
當(dāng)按下鼠標(biāo)開始移動(dòng)端時(shí)候,處于截圖中狀態(tài)(鼠標(biāo)移動(dòng)事件),具體行為是:不斷記錄截圖的實(shí)時(shí)坐標(biāo),作為截圖區(qū)域的右下角坐標(biāo),同時(shí)用戶選中的區(qū)域,背景色要設(shè)置成亮色,可以清晰可見。
當(dāng)放開鼠標(biāo)的時(shí)候,處于截圖結(jié)束狀態(tài)(鼠標(biāo)左鍵抬起事件),具體行為是:記錄截圖的終止坐標(biāo),也就是截圖舉行的右下角坐標(biāo)。
當(dāng)下鼠標(biāo)移動(dòng)的時(shí)候,處于移動(dòng)中狀態(tài),已經(jīng)選好的截圖區(qū)域是可以移動(dòng)的,具體行為是:記錄鼠標(biāo)移動(dòng) 偏差,動(dòng)態(tài)設(shè)置選中區(qū)域的背景色。
當(dāng)然還有其他的狀態(tài)類,就不一一描述了。
首先定義我們的狀態(tài)類接口IScreentState :表示截圖狀態(tài)類
public interface IScreentState
{
void ProcessState(StateContext context);
}其次定義具體的狀態(tài)實(shí)現(xiàn)類:StartState、SelectState、SelectingState、SelectedState、MoveState、MovingState、MovedState、EndState總共有8個(gè)狀態(tài)了,這里我只實(shí)現(xiàn)了截圖和移動(dòng)功能,并沒有實(shí)現(xiàn)拖拽功能,如果需要拖拽修改截圖區(qū)域大小功能,大家可以自行實(shí)現(xiàn)。
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); //拖拽后鼠標(biāo)的位置
var dp = pos - context._mouseDownPosition; //鼠標(biāo)移動(dòng)的偏移量
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)
{
}
}最后是狀態(tài)上下文類:
public class StateContext
{
public IScreentState _currentState;
public SnapShotWindow _window;
public MouseEventArgs _args;
//框選開始坐標(biāo)
public Point _startPoint;
//框選結(jié)束坐標(biāo)
public Point _endPoint;
//目前狀態(tài)
public ScreenState _state;
//開始拖拽時(shí),鼠標(biāo)按下的位置
public Point _mouseDownPosition;
//開始拖拽時(shí),鼠標(biāo)按下控件的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主界面的布局,就不給大家貼出來(lái),源代碼已經(jīng)上傳到github:https://github.com/caoruipeng123/ScreenApp
運(yùn)行效果
接下來(lái)看下實(shí)際的運(yùn)行效果:進(jìn)入截圖頁(yè)面之后,單擊鼠標(biāo)坐標(biāo)開始框選截圖區(qū)域,雙擊鼠標(biāo)左鍵,可以結(jié)束截圖,并且圖片會(huì)設(shè)置到操作系統(tǒng)的粘貼板上,你可以把圖片粘貼到任何位置。

到此這篇關(guān)于WPF用狀態(tài)模式開發(fā)截圖功能的文章就介紹到這了,更多相關(guān)WPF截圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c# 在windows中操作IIS設(shè)置FTP服務(wù)器的示例
這篇文章主要介紹了c# 在windows中操作IIS設(shè)置FTP服務(wù)器的示例,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-03-03
C#中將xml文件反序列化為實(shí)例時(shí)采用基類還是派生類的知識(shí)點(diǎn)討論
在本篇文章里小編給大家整理的是關(guān)于C#中將xml文件反序列化為實(shí)例時(shí)采用基類還是派生類的知識(shí)點(diǎn)討論,有需要的朋友們學(xué)習(xí)下。2019-11-11
C#實(shí)現(xiàn)標(biāo)題閃爍效果的示例代碼
在Windows系統(tǒng)中,當(dāng)程序在后臺(tái)運(yùn)行時(shí),如果某個(gè)窗體的提示信息需要用戶瀏覽,該窗體就會(huì)不停地閃爍,這樣就會(huì)吸引用戶的注意,下面我們就來(lái)看看如何使用C#實(shí)現(xiàn)這一效果吧2024-04-04

