WPF用狀態(tài)模式開(kāi)發(fā)截圖功能
狀態(tài)模式
狀態(tài)模式是設(shè)計(jì)模式中的一種行為設(shè)計(jì)模式,對(duì)很多人來(lái)說(shuō),這個(gè)模式平時(shí)可能用不到。但是如果你做游戲開(kāi)發(fā)的話,我相信你應(yīng)該對(duì)這個(gè)模式有一個(gè)很深刻的理解。狀態(tài)模式在游戲中開(kāi)發(fā)中還是比較常見(jiàn)的。狀態(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í)際開(kāi)發(fā)中,我們一般是監(jiān)聽(tīng)鼠標(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í)候,處理開(kāi)始截圖狀態(tài)(鼠標(biāo)左鍵單擊事件),具體行為是:記錄截圖的開(kāi)始坐標(biāo),也就是截圖矩形的左上角坐標(biāo)。
當(dāng)按下鼠標(biāo)開(kāi)始移動(dòng)端時(shí)候,處于截圖中狀態(tài)(鼠標(biāo)移動(dòng)事件),具體行為是:不斷記錄截圖的實(shí)時(shí)坐標(biāo),作為截圖區(qū)域的右下角坐標(biāo),同時(shí)用戶選中的區(qū)域,背景色要設(shè)置成亮色,可以清晰可見(jiàn)。
當(dāng)放開(kāi)鼠標(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)功能,并沒(méi)有實(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; //框選開(kāi)始坐標(biāo) public Point _startPoint; //框選結(jié)束坐標(biāo) public Point _endPoint; //目前狀態(tài) public ScreenState _state; //開(kāi)始拖拽時(shí),鼠標(biāo)按下的位置 public Point _mouseDownPosition; //開(kāi)始拖拽時(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)開(kāi)始框選截圖區(qū)域,雙擊鼠標(biāo)左鍵,可以結(jié)束截圖,并且圖片會(huì)設(shè)置到操作系統(tǒng)的粘貼板上,你可以把圖片粘貼到任何位置。
到此這篇關(guān)于WPF用狀態(tài)模式開(kā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-03C#中將xml文件反序列化為實(shí)例時(shí)采用基類還是派生類的知識(shí)點(diǎn)討論
在本篇文章里小編給大家整理的是關(guān)于C#中將xml文件反序列化為實(shí)例時(shí)采用基類還是派生類的知識(shí)點(diǎn)討論,有需要的朋友們學(xué)習(xí)下。2019-11-11C#實(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