前言
做wpf界面的时候有时需要撤销重做功能,虽然一些wpf控件自带撤销功能,但是在一些复杂的场景还是需要自己实现撤销重做功能,比如自定义的白板或者视频剪辑器等等,本文将介绍如何简单的实现一个撤销重做对象,并提供使用示例。
一、具体实现
由于实现相对简单,这里就具体对原理进行说明了。用一个List集合加一个下标index即可以实现所有功能。
1、完整代码
Undoable.cs
using System;
using System.Collections.Generic;
namespace AC
{
/// <summary>
/// 撤销重做对象
/// ceate by xin 2022.6.26
/// 2023.9.13去除Step的定义替换为KeyValuePair简化实现
/// </summary>
public class Undoable
{
/// <summary>
/// 撤销
/// </summary>
public void Undo()
{
if (index > -1) _steps[index--].Key();
}
/// <summary>
/// 重做
/// </summary>
public void Redo()
{
if (index < _steps.Count - 1) _steps[++index].Value();
}
/// <summary>
/// 清除
/// </summary>
public void Clear()
{
_steps.Clear();
index = -1;
}
/// <summary>
/// 添加操作
/// </summary>
/// <param name="undo">撤销</param>
/// <param name="redo">重做</param>
public void Add(Action undo, Action redo)
{
if (++index < _steps.Count) _steps.RemoveRange(index, _steps.Count - index);
_steps.Add(new KeyValuePair<Action, Action>(undo, redo));
}
//记录的步骤
List<KeyValuePair<Action, Action>> _steps = new List<KeyValuePair<Action, Action>>();
//当前操作的下标
int index = -1;
}
}
二、使用示例
1、拖动控件
(1)MainWindow.xaml
<Window x:Class="WpfApp3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<Button Width="70" Height="20" Content="撤销" Click="Button_Click"></Button>
<Button Width="70" Height="20" Content="重做" Click="Button_Click_1"></Button>
</StackPanel>
<Rectangle Width="240" Height=" 120" RadiusX="20" RadiusY="20" Fill="RoyalBlue" PreviewMouseDown="Rectangle_PreviewMouseDown"
PreviewMouseMove="Rectangle_PreviewMouseMove"
PreviewMouseUp="Rectangle_PreviewMouseUp"></Rectangle>
<Ellipse HorizontalAlignment="Left" Width="120" Height=" 120" Fill="GreenYellow" PreviewMouseDown="Rectangle_PreviewMouseDown"
PreviewMouseMove="Rectangle_PreviewMouseMove"
PreviewMouseUp="Rectangle_PreviewMouseUp"></Ellipse>
</Grid>
</Window>
(2)MainWindow.xaml.cs
using AC;
using System.Windows;
using System.Windows.Input;
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Undoable _undoable=new Undoable();
//鼠标是否按下
bool _isMouseDown = false;
//鼠标按下的位置
Point _mouseDownPosition;
//鼠标按下控件的Margin
Thickness _mouseDownMargin;
public MainWindow()
{
InitializeComponent();
}
private void Rectangle_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var c = sender as FrameworkElement;
_isMouseDown = true;
_mouseDownPosition = e.GetPosition(this);
_mouseDownMargin = c.Margin;
c.CaptureMouse();
}
private void Rectangle_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_isMouseDown)
{
var c = sender as FrameworkElement;
var pos = e.GetPosition(this);
var dp = pos - _mouseDownPosition;
c.Margin = new Thickness(_mouseDownMargin.Left + dp.X, _mouseDownMargin.Top + dp.Y, _mouseDownMargin.Right - dp.X, _mouseDownMargin.Bottom - dp.Y);
}
}
private void Rectangle_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
var c = sender as FrameworkElement;
_isMouseDown = false;
c.ReleaseMouseCapture();
var oldMargin = _mouseDownMargin;
var newMargin = c.Margin;
_undoable.Add(() => {
//撤销
c.Margin = oldMargin;
}, () => {
//重做
c.Margin = newMargin;
});
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_undoable.Undo();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
_undoable.Redo();
}
}
}
(3)效果预览
总结
以上就是今天要讲的内容,本文简单介绍了撤销重做的实现,总的来说还是比较容易的,尤其是是使用Action来记录操作,这样就会比较灵活,可以直接使用lambda作为参数。