WPF learning 11: Drawing editing tool based on MVVM Light (2), wpfmvvm
This article is about how to create a graphic editing tool based on MVVM Light (1 ).
This time the goal is to complete
Two tasks.
Canvas
Effect:
On the canvas, the selection scheme is: directly use the Image as the canvas, and bind the RenderTargetBitmap to the Image source, which can greatly facilitate subsequent Image export functions.
Modify the drag column XAML as follows:
<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>
Correspondingly, code must also be added to ViewModel.
private RenderTargetBitmap _drawingBitmap;public RenderTargetBitmap DrawingBitmap{ get { return _drawingBitmap; } set { _drawingBitmap = value; RaisePropertyChanged("DrawingBitmap"); }}
At this point, the canvas binding is complete.
Now you need to complete the code for adjusting the canvas size. First, add two input boxes in XAML to configure the length and width from the interface:
<TextBlock VerticalAlignment = "Center"> <Run Text = "width: "/> </TextBlock> <TextBox Width =" 50 "Margin =" 0 0 10 0 "Text =" {Binding DrawingAreaWidth, ValidatesOnDataErrors = True, updateSourceTrigger = PropertyChanged} "/> <TextBlock verticalignment =" Center "> <Run Text =" high: "/> </TextBlock> <TextBox Width =" 50 "Margin =" 0 0 10 0 "Text =" {Binding DrawingAreaHeight, ValidatesOnDataErrors = True, updateSourceTrigger = PropertyChanged} "/> <Button Margin =" 10 0 10 0 "Content =" configuration "Command =" {Binding SetDrawingAreaSize} "/>
ViewModel section:
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"); }}
Finally, the implementation of Command SetDrawingAreaSize is as follows:
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))); }}
At this point, the effect at the beginning of this section is complete.
Straight Line
The effect is as follows:
Two namespaces need to be introduced in XAML:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"xmlns:command=http://www.galasoft.ch/mvvmlight
After the introduction, we can add three mouse-responsive commands for the 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>
Draw the Line control on the interface, which is used for display during dynamic operations. After the operation is completed, hide the control and draw on the Image.
<Line Stroke="Black" StrokeThickness="1" Visibility="{Binding LineVisibility}" X1="{Binding PositionX1}" X2="{Binding PositionX2}"Y1="{Binding PositionY1}" Y2="{Binding PositionY2}"/>
Add the following attributes to 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"); }}
To enable ViewModel to know the current drawing status (straight line, circle, rectangle), add some data binding:
<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>
Finally, we will write three commands corresponding to the mouse:
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) ;}, () => (Drawin GAreaWidth! = 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 () {// here-1 is used to eliminate the deviation caused by the canvas boundary, because it is currently fixed, so no data binding is used. 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 ));}}
Circle
Effect:
XAML code:
<Ellipse Stroke="Black" StrokeThickness="1" Visibility="{Binding EllipseVisibility}" Canvas.Left="{Binding PositionX1}" Canvas.Top="{Binding PositionY1}" Width="{Binding ShapeWidth}" Height="{Binding ShapeHeight}"></Ellipse>
Added MouseDown:
if (EllipseModeEnable){ ShapeWidth = ShapeHeight = 0; EllipseVisibility = Visibility.Visible;}
Move added:
ShapeWidth = Math.Abs(PositionX2 - PositionX1);ShapeHeight = Math.Abs(PositionY2 - PositionY1);
Add 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);
Rectangle
The code is similar to the circle, so it is omitted.
The next section will enlarge, zoom out, move, and fill the color of the image.
Development Environment VS2013,. NET4.5
Source code