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