本文是WPF学习10:基于MVVM Light 制作图形编辑工具(1)的后续
这一次的目标是完成
两个任务。
效果:
画布上,选择的方案是:直接以Image作为画布,使用RenderTargetBitmap绑定为Image的图片源,这样可以为后续的导出图片功能提供很大的便利。
对拖动栏XAML进行如下修改:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Grid.Column="1" Grid.Row="1"> <Canvas VerticalAlignment="Top" HorizontalAlignment="Left" Width="{Binding ActualWidth,ElementName=ImageBorder}" SnapsToDevicePixels="False" Height="{Binding Path=ActualHeight,ElementName=ImageBorder}" Margin="50 50 0 0 " ClipToBounds="True"> <Border Name="ImageBorder" BorderBrush="Black" BorderThickness="1"> <Image Source="{Binding DrawingBitmap}"> </Image> </Border> </Canvas> </ScrollViewer>
相应的,ViewModel中也要添加代码。
private RenderTargetBitmap _drawingBitmap; public RenderTargetBitmap DrawingBitmap { get { return _drawingBitmap; } set { _drawingBitmap = value; RaisePropertyChanged("DrawingBitmap"); } }
到这里,画布的绑定就完成了。
现在要完成调节画布大小的相关代码,首先,在XAML增加两个输入框使得长宽由界面配置:
<TextBlock VerticalAlignment="Center"><Run Text="宽:"/></TextBlock> <TextBox Width="50" Margin="0 0 10 0" Text="{Binding DrawingAreaWidth, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock VerticalAlignment="Center"><Run Text="高:"/></TextBlock> <TextBox Width="50" Margin="0 0 10 0" Text="{Binding DrawingAreaHeight, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> <Button Margin="10 0 10 0" Content="配置" Command="{Binding SetDrawingAreaSize}"/>
ViewModel部分:
private Int32 _drawingAreaWidth; public Int32 DrawingAreaWidth { get { return _drawingAreaWidth; } set { _drawingAreaWidth = value; RaisePropertyChanged("DrawingAreaWidth"); } } private Int32 _drawingAreaHeight; public Int32 DrawingAreaHeight { get { return _drawingAreaHeight; } set { _drawingAreaHeight = value; RaisePropertyChanged("DrawingAreaHeight"); } }
最后是Command SetDrawingAreaSize 的实现:
private ICommand _setDrawingAreaSize; public ICommand SetDrawingAreaSize { get { return _setDrawingAreaSize ?? (_setDrawingAreaSize = new RelayCommand(() => { DrawingBitmap = new RenderTargetBitmap(DrawingAreaWidth, DrawingAreaHeight, 96, 96, PixelFormats.Pbgra32); var drawingVisual = new DrawingVisual(); using (var context = drawingVisual.RenderOpen()) { context.DrawRectangle(Brushes.White, null, new Rect(0, 0, DrawingAreaWidth, DrawingAreaHeight)); } DrawingBitmap.Render(drawingVisual); } , () => (DrawingAreaWidth != 0 && DrawingAreaHeight != 0))); } }
至此,本节最开始的效果就完成啦。
效果如下:
XAML中需要引入两个命名空间:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:command=http://www.galasoft.ch/mvvmlight
引入后我们就可以为Image添加三个响应鼠标的命令。
<Image Source="{Binding DrawingBitmap}"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseMove"> <command:EventToCommand Command="{Binding MouseMoveCommand}" PassEventArgsToCommand="True" /> </i:EventTrigger> <i:EventTrigger EventName="MouseDown" > <command:EventToCommand Command="{Binding MouseDownCommand}" PassEventArgsToCommand="True"/> </i:EventTrigger> <i:EventTrigger EventName="MouseUp" > <command:EventToCommand Command="{Binding MouseUpCommand}" PassEventArgsToCommand="True"/> </i:EventTrigger> </i:Interaction.Triggers> </Image>
在界面上绘制Line控件,用于动态操作时的显示,操作完毕,隐藏控件,在Image上绘图。
<Line Stroke="Black" StrokeThickness="1" Visibility="{Binding LineVisibility}" X1="{Binding PositionX1}" X2="{Binding PositionX2}" Y1="{Binding PositionY1}" Y2="{Binding PositionY2}"/>
ViewModel添加以下属性,X2,Y1,Y2略。
private Visibility _lineVisibility = Visibility.Hidden; public Visibility LineVisibility { get { return _lineVisibility; } set { _lineVisibility = value; RaisePropertyChanged("LineVisibility"); } } private Double _positionX1; public Double PositionX1 { get { return _positionX1; } set { _positionX1 = value; RaisePropertyChanged("PositionX1"); } }
为了让ViewModel能知道当前的绘图状态(直线,圆,矩形)在加一些数据绑定:
<RadioButton Style="{StaticResource StatusBarButton}" IsChecked="{Binding LineModeEnable}"> <Line X1="0" Y1="0" X2="15" Y2="15" Stroke="Black" StrokeThickness="1"></Line> </RadioButton> <RadioButton Style="{StaticResource StatusBarButton}" IsChecked="{Binding RectangleModeEnable}"> <Rectangle Width="20" Height="15" Stroke="Black" StrokeThickness="1"></Rectangle> </RadioButton> <RadioButton Style="{StaticResource StatusBarButton}" IsChecked="{Binding EllipseModeEnable}"> <Ellipse Width="20" Height="20" Stroke="Black" StrokeThickness="1"></Ellipse> </RadioButton>
最后,我们编写三个鼠标相应的指令:
public ICommand SetDrawingAreaSize { get { return _setDrawingAreaSize ?? (_setDrawingAreaSize = new RelayCommand(() => { DrawingBitmap = new RenderTargetBitmap(DrawingAreaWidth, DrawingAreaHeight, 96, 96, PixelFormats.Pbgra32); var drawingVisual = new DrawingVisual(); using (var context = drawingVisual.RenderOpen()) { context.DrawRectangle(Brushes.White, null, new Rect(0, 0, DrawingAreaWidth, DrawingAreaHeight)); } DrawingBitmap.Render(drawingVisual); } , () => (DrawingAreaWidth != 0 && DrawingAreaHeight != 0))); } } private ICommand _mouseMoveCommand; public ICommand MouseMoveCommand { get { return _mouseMoveCommand ?? (_mouseMoveCommand = new RelayCommand<MouseEventArgs>((e) => { PositionX2 = e.GetPosition((IInputElement)e.Source).X; PositionY2 = e.GetPosition((IInputElement)e.Source).Y; } , (e) => true)); } } private ICommand _mouseDownCommand; public ICommand MouseDownCommand { get { return _mouseDownCommand ?? (_mouseDownCommand = new RelayCommand<MouseEventArgs>((e) => { if(LineModeEnable) LineVisibility = Visibility.Visible; PositionX1 = e.GetPosition((IInputElement)e.Source).X; PositionY1 = e.GetPosition((IInputElement)e.Source).Y; } , (e) => true)); } } private ICommand _mouseUpCommand; public ICommand MouseUpCommand { get { return _mouseUpCommand ?? (_mouseUpCommand = new RelayCommand<MouseEventArgs>((e) => { var drawingVisual = new DrawingVisual(); using (var context = drawingVisual.RenderOpen()) { //此处的-1用于消除画布边界带来的偏差,因为目前都是固定的,所以没有使用数据绑定。 if(LineModeEnable) context.DrawLine(new Pen(Brushes.Black, 1), new Point(PositionX1 - 1, PositionY1 - 1), new Point(PositionX2 - 1, PositionY2 - 1 )); } DrawingBitmap.Render(drawingVisual); LineVisibility = Visibility.Hidden; } , (e) => true)); } }
效果:
XAML代码:
<Ellipse Stroke="Black" StrokeThickness="1" Visibility="{Binding EllipseVisibility}" Canvas.Left="{Binding PositionX1}" Canvas.Top="{Binding PositionY1}" Width="{Binding ShapeWidth}" Height="{Binding ShapeHeight}"></Ellipse>
MouseDown增加:
if (EllipseModeEnable) { ShapeWidth = ShapeHeight = 0; EllipseVisibility = Visibility.Visible; }
Move增加:
ShapeWidth = Math.Abs(PositionX2 - PositionX1);
ShapeHeight = Math.Abs(PositionY2 - PositionY1);
Up增加:
if(EllipseModeEnable) context.DrawEllipse(new SolidColorBrush(Colors.White), new Pen(Brushes.Black, 1), new Point(PositionX1 + ShapeWidth / 2 - 1, PositionY1 + ShapeHeight / 2 - 1), ShapeWidth / 2, ShapeHeight / 2);
效果如上,代码与圆形相似,故省略。
下一节将会完成图形的放大、缩小、移动,颜色的填充。
开发环境VS2013, .NET4.5
源码