效果如图
实现了绘图,自适应缩放
核心代码如下
<Window.InputBindings> <KeyBinding Key="Z" Modifiers="Ctrl" Command="{Binding UndoCommand}" /> </Window.InputBindings> <i:Interaction.Triggers> <i:EventTrigger EventName="SizeChanged"> <i:InvokeCommandAction Command="{Binding SizeChangedCommand}" /> </i:EventTrigger> </i:Interaction.Triggers> <Grid> <Grid Background="AliceBlue"> <Grid.RowDefinitions> <RowDefinition Height="40" /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.35*" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,0,0" Grid.ColumnSpan="2"> <!--<Button Content="+" Command="{Binding DrawRectCommand}" />--> <Button Content=" + " Margin="10,0,0,0" Command="{Binding CreateCommand}" /> <Button Content="Save" Margin="10,0,0,0" Command="{Binding SaveCommand}" /> <Button Content="Clear" Margin="10,0,0,0" Command="{Binding ClearCommand}" /> <Button Content="Cancel" Margin="10,0,0,0" Command="{Binding UndoCommand}" /> <Button Content="Refresh" Margin="10,0,0,0" Command="{Binding RefreshCommand}" /> </StackPanel> <StackPanel Grid.Row="1" Grid.Column="0"> <ListBox SelectedItem="{Binding SelectedSlide}"> <ListBox.Resources> <CollectionViewSource x:Key="SlideControlParams" Source="{Binding SlideControlParams}" /> </ListBox.Resources> <ListBox.ItemsSource> <CompositeCollection> <CollectionContainer Collection="{Binding Source={StaticResource SlideControlParams}}" /> </CompositeCollection> </ListBox.ItemsSource> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemTemplate> <DataTemplate> <Grid Height="40" VerticalAlignment="Center"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="50" /> </Grid.ColumnDefinitions> <Grid.InputBindings> <MouseBinding Command="{Binding DataContext.OnListViewItemDoubleClick, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" MouseAction="LeftDoubleClick" CommandParameter="{Binding }" /> </Grid.InputBindings> <TextBlock Text="{Binding Path= Name}"> <TextBlock.VerticalAlignment>Center</TextBlock.VerticalAlignment> <TextBlock.Margin>5,0,0,0</TextBlock.Margin> </TextBlock> <TextBox Text="{Binding Name,Mode=TwoWay}" VerticalAlignment="Center" Margin="5,0,0,0" Visibility="{Binding EditStatus}" /> <Button Content="X" Grid.Column="1" Width="40" VerticalAlignment="Center" Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" CommandParameter="{Binding }" /> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel> <Image Source="{Binding SelectedSlide.ImagePath}" Grid.Row="1" x:Name="imgSlide" Grid.Column="1"> </Image> <Canvas Grid.Row="1" Name="canvas" Background="#19DAB1B1" Grid.Column="1"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonDown"> <cmd:EventToCommand Command="{Binding CmdLeftMouseDown}" PassEventArgsToCommand="True" /> </i:EventTrigger> <i:EventTrigger EventName="MouseLeftButtonUp"> <cmd:EventToCommand Command="{Binding CmdLeftMouseUp}" PassEventArgsToCommand="True" /> </i:EventTrigger> <i:EventTrigger EventName="MouseMove"> <cmd:EventToCommand Command="{Binding CmdLeftMouseMove}" PassEventArgsToCommand="True" /> </i:EventTrigger> <i:EventTrigger EventName="MouseRightButtonUp"> <cmd:EventToCommand Command="{Binding MouseRightButtonUp}" PassEventArgsToCommand="True" /> </i:EventTrigger> </i:Interaction.Triggers> </Canvas> </Grid> </Grid> </Window>
public class UCP16ConfigVM : ViewModelBase { public UCP16ConfigVM(Canvas _canvas, Image _imgSlide) { canvas = _canvas; imgSlide = _imgSlide; InitEnv(); InitCommand(); LoadConfig(); string rootPath = System.Environment.CurrentDirectory; jsonPath = System.IO.Path.Combine(rootPath, "ScanPara", "slideParms.json"); imgDir = System.IO.Path.Combine(rootPath, "ScanPara", "SlideConfigImages"); if (!Directory.Exists(imgDir)) { Directory.CreateDirectory(imgDir); } if (!File.Exists(jsonPath)) { SlideP16Parms prams = new SlideP16Parms(); var str = JsonConvert.SerializeObject(prams, Formatting.Indented); File.WriteAllText(jsonPath, str); } } #region private param /// <summary> /// 是否正在绘图状态 /// </summary> private bool isDrawing = false; /// <summary> /// 起始点 /// </summary> private Point startPoint; /// <summary> /// 结束点 /// </summary> private Point endPoint; /// <summary> /// canvas控件 /// </summary> private Canvas canvas = null; /// <summary> /// image控件 /// </summary> private Image imgSlide = null; /// <summary> /// 颜色刷 /// </summary> private Brush brushDanger; /// <summary> /// rect效果 /// </summary> private DropShadowEffect shadowEffect; /// <summary> /// 临时矩形 /// </summary> private Rectangle tempRect = null; private readonly string jsonPath; private readonly string imgDir; //图像控件相对于canva的坐标 private Point imgMarginPos; #endregion private param #region ViewModels private ObservableCollection<SlideP16ItemCombine> _SlideControlParams; /// <summary> /// 控件对应的坐标 /// </summary> public ObservableCollection<SlideP16ItemCombine> SlideControlParams { get => _SlideControlParams; set { Set(ref _SlideControlParams, value); } } ///// <summary> ///// 图像坐标,需要同步更新 ///// </summary> //public List<SlideP16Item> SlideImageParams { get; set; } private SlideP16ItemCombine _SelectedSlide; public SlideP16ItemCombine SelectedSlide { get => _SelectedSlide; set { if (value != _SelectedSlide) { Set(ref _SelectedSlide, value); foreach (var item in SlideControlParams) { if (item != value) item.EditStatus = Visibility.Collapsed; } LoadPoints(); } } } #endregion ViewModels #region Command /// <summary> /// 清空所有绘图 /// </summary> public RelayCommand ClearCommand { get; private set; } /// <summary> /// 取消,撤销上一步操作 /// </summary> public RelayCommand UndoCommand { get; private set; } public RelayCommand<MouseEventArgs> CmdLeftMouseDown { get; private set; } public RelayCommand<MouseEventArgs> CmdLeftMouseMove { get; private set; } public RelayCommand<MouseEventArgs> CmdLeftMouseUp { get; private set; } public RelayCommand<MouseEventArgs> MouseRightButtonUp { get; private set; } public RelayCommand<SlideP16ItemCombine> OnListViewItemDoubleClick { get; private set; } /// <summary> /// 刷新数据 /// </summary> public RelayCommand RefreshCommand { get; private set; } /// <summary> /// 保存 /// </summary> public RelayCommand SaveCommand { get; set; } /// <summary> /// 新建配方 /// </summary> public RelayCommand CreateCommand { get; set; } public RelayCommand<SlideP16ItemCombine> DeleteCommand { get; set; } public RelayCommand SizeChangedCommand { get; private set; } #endregion Command private static readonly string SOURCE_DEFAULT = @"pack://application:,,,/TestWPF;component/Resources/Images/upload.png"; /// <summary> /// 初始化界面主题 /// </summary> private void InitEnv() { string hexColor = "#F56C6C"; BrushConverter converter = new BrushConverter(); brushDanger = (Brush)converter.ConvertFromString(hexColor); shadowEffect = new DropShadowEffect { Color = Colors.White, // 阴影颜色 Direction = 0, // 阴影方向 BlurRadius = 3, // 模糊半径 Opacity = 0.6, // 阴影不透明度 ShadowDepth = 0 }; } /// <summary> /// 初始化绑定命令 /// </summary> private void InitCommand() { CmdLeftMouseDown = new RelayCommand<MouseEventArgs>(LeftMouseDown); CmdLeftMouseMove = new RelayCommand<MouseEventArgs>(LeftMouseMove); CmdLeftMouseUp = new RelayCommand<MouseEventArgs>(LeftMouseUp); MouseRightButtonUp = new RelayCommand<MouseEventArgs>(RightMouseUp); ClearCommand = new RelayCommand(() => { ClearRectangle(); }); UndoCommand = new RelayCommand(() => { if (canvas.Children.Count > 0) { if (canvas.Children[canvas.Children.Count - 1] is Rectangle rect) { RemoveRectangle(rect); } // canvas.Children.RemoveAt(canvas.Children.Count - 1); } }); SaveCommand = new RelayCommand(() => { SaveResult(); }); CreateCommand = new RelayCommand(() => { CreateNewItem(SOURCE_DEFAULT); }); DeleteCommand = new RelayCommand<SlideP16ItemCombine>( (SlideP16ItemCombine param) => { SlideControlParams.Remove(param); return; } ); OnListViewItemDoubleClick = new RelayCommand<SlideP16ItemCombine>( (args) => { args.EditStatus = Visibility.Visible; } ); RefreshCommand = new RelayCommand(RefreshData); SizeChangedCommand = new RelayCommand(Control_SizeChanged); } #region Command对应方法 /// <summary> /// 左键按下事件 /// </summary> /// <param name="e"></param> private void LeftMouseDown(MouseEventArgs e) { if (SelectedSlide == null) return; if (SelectedSlide.ImagePath == SOURCE_DEFAULT) { LoadLocalImage(); return; } isDrawing = true; if (e.OriginalSource is Canvas) { startPoint = e.GetPosition((IInputElement)e.Source); } else { startPoint = (e.Source as Rectangle).TranslatePoint( e.GetPosition((IInputElement)e.Source), canvas ); } // startPoint = e.GetPosition((IInputElement)e.Source); tempRect = CreateTempRect(); Trace.WriteLine("down"); canvas.Children.Add(tempRect); } private void RightMouseUp(MouseEventArgs e) { if (SelectedSlide == null) return; if (e.OriginalSource is Rectangle) { var rect = e.OriginalSource as Rectangle; RemoveRectangle(rect); } } private void LeftMouseUp(MouseEventArgs e) { if (SelectedSlide == null) return; isDrawing = false; AddRectangle(); tempRect = null; } private void LeftMouseMove(MouseEventArgs e) { if (SelectedSlide == null) return; if (!isDrawing) return; if (e.OriginalSource is Canvas) { endPoint = e.GetPosition((IInputElement)e.Source); } else { endPoint = (e.Source as Rectangle).TranslatePoint( e.GetPosition((IInputElement)e.Source), canvas ); } UpdateRectangle(); } #endregion Command对应方法 #region other func private Rectangle CreateTempRect() { Rectangle rect = new Rectangle { Width = 0, Height = 0, Stroke = brushDanger, // Brushes.Black, StrokeThickness = 2, Fill = Brushes.Transparent, IsHitTestVisible = true }; rect.Effect = shadowEffect; Canvas.SetLeft(rect, startPoint.X); Canvas.SetTop(rect, startPoint.Y); return rect; } /// <summary> /// 刷新rect轨迹,并不会添加到数组 /// </summary> private void UpdateRectangle() { AddRectangle(true); return; } private void AddRectangle(bool isUpdate = false) { try { double width = (endPoint.X - startPoint.X); double height = endPoint.Y - startPoint.Y; double left = startPoint.X; double top = startPoint.Y; if (width < 0) { left = endPoint.X; width = -width; } if (height < 0) { top = endPoint.Y; height = -height; } tempRect.Width = width; tempRect.Height = height; Canvas.SetLeft(tempRect, left); Canvas.SetTop(tempRect, top); if (!isUpdate) //如果完成,则添加到列表 { var tempPoint = new SlideP16ItemPoint { Height = height, Width = width, X = left, Y = top, IsImagePoint = false, }; SelectedSlide.ItemPoints.Add(tempPoint); AddControlPoint(SelectedSlide.ItemPoints[SelectedSlide.ItemPoints.Count - 1]); } } catch (Exception ex) { return; } } /// <summary> /// 删除界面指定rectangle对象,并从绑定的队列移除 /// </summary> /// <param name="rect"></param> private void RemoveRectangle(Rectangle rect) { var imagePoint = ConvertToImageCoordinates( new SlideP16ItemPoint { X = Canvas.GetLeft(rect), Y = Canvas.GetTop(rect), Width = rect.Width, Height = rect.Height } ); var toDeletePoint = this .SelectedSlide.ItemPoints.Where(x => x.IsImagePoint && Math.Abs(x.Width - imagePoint.Width) <= 0.1 && Math.Abs(x.Height - imagePoint.Height) <= 0.1 && Math.Abs(x.X - imagePoint.X) <= 0.1 && Math.Abs(x.Y - imagePoint.Y) <= 0.1 ) .FirstOrDefault(); if (toDeletePoint != null) SelectedSlide.ItemPoints.Remove(toDeletePoint); canvas.Children.Remove(rect); } /// <summary> /// 删除所有绘图 /// </summary> private void ClearRectangle() { canvas.Children.Clear(); this.SelectedSlide.ItemPoints.Clear(); } /// <summary> /// 控件坐标转换为图像坐标 /// </summary> /// <param name="pointControl"></param> /// <returns></returns> private SlideP16ItemPoint ConvertToImageCoordinates(SlideP16ItemPoint pointControl) { double scaleX = imgSlide.ActualWidth / (imgSlide.Source == null ? imgSlide.ActualWidth : imgSlide.Source.Width); double scaleY = imgSlide.ActualHeight / (imgSlide.Source == null ? imgSlide.ActualHeight : imgSlide.Source.Height); ; double imageRectX = pointControl.X / scaleX; double imageRectY = pointControl.Y / scaleY; double imageRectWidth = pointControl.Width / scaleX; double imageRectHeight = pointControl.Height / scaleY; return new SlideP16ItemPoint { Height = imageRectHeight, Width = imageRectWidth, X = imageRectX, Y = imageRectY, }; } //private double scaleX; //private double scaleY; /// <summary> /// 图像坐标转控件坐标 /// </summary> /// <param name="point"></param> /// <returns></returns> private SlideP16ItemPoint ConvertToControlCoordinates(SlideP16ItemPoint point) { var scaleX = imgSlide.ActualWidth / imgSlide.Source.Width; var scaleY = imgSlide.ActualHeight / imgSlide.Source.Height; // 根据缩放比例计算控件上的位置和尺寸 double controlX = point.X * scaleX; double controlY = point.Y * scaleY; double controlWidth = point.Width * scaleX; double controlHeight = point.Height * scaleY; return new SlideP16ItemPoint { Height = controlHeight, Width = controlWidth, X = controlX, Y = controlY, }; } private void LoadConfig() { string jsonPath = System.Environment.CurrentDirectory; jsonPath = System.IO.Path.Combine(jsonPath, "ScanPara", "slideParms.json"); var json = File.ReadAllText(jsonPath); SlideP16Parms rt = JsonConvert.DeserializeObject<SlideP16Parms>(json); IEnumerable<SlideP16ItemCombine> items = rt.Items.Select(x => new SlideP16ItemCombine { EditStatus = Visibility.Collapsed, ID = x.ID, ImagePath = x.ImagePath, ItemPoints = x.ItemPoints, Name = x.Name }); SlideControlParams = new ObservableCollection<SlideP16ItemCombine>(items); } /// <summary> /// 加载point坐标,并绘制到ui /// </summary> /// <param name="isControl"></param> private void LoadPoints() { imgSlide.Dispatcher.Invoke( new Action(() => { if (this.SelectedSlide == null) return; imgMarginPos = imgSlide.TranslatePoint(new Point(0, 0), canvas); canvas.Children.Clear(); foreach (SlideP16ItemPoint p in SelectedSlide.ItemPoints) { SlideP16ItemPoint t_point = null; if (p.IsImagePoint) t_point = ConvertToControlCoordinates(p); else t_point = p; Rectangle tempRect = new Rectangle { Width = t_point.Width, Height = t_point.Height, Stroke = brushDanger, // Brushes.Black, StrokeThickness = 2, Fill = Brushes.Transparent, IsHitTestVisible = true }; tempRect.Effect = shadowEffect; Canvas.SetLeft(tempRect, t_point.X); Canvas.SetTop(tempRect, t_point.Y); // Trace.WriteLine($"tempRect x:{t_point.X} y:{t_point.Y},width:{tempRect.Width},height:{tempRect.Height}"); canvas.Children.Add(tempRect); } // Trace.WriteLine("cavnas.children.count:" + canvas.Children.Count); }), DispatcherPriority.Loaded ); } private void AddControlPoint(SlideP16ItemPoint t_point) { Rectangle tempRect = new Rectangle { Width = t_point.Width, Height = t_point.Height, Stroke = brushDanger, // Brushes.Black, StrokeThickness = 2, Fill = Brushes.Transparent, IsHitTestVisible = true }; tempRect.Effect = shadowEffect; Canvas.SetLeft(tempRect, t_point.X); Canvas.SetTop(tempRect, t_point.Y); // canvas.Children.Add(tempRect); Trace.WriteLine("cavnas.children.count:" + canvas.Children.Count); } private void CreateNewItem(string imgPath) { int id = 0; if (this.SlideControlParams?.Count > 0) id = this.SlideControlParams.Max(x => x.ID) + 1; var item = new SlideP16ItemCombine() { ID = id, ImagePath = imgPath, Name = id.ToString(), ItemPoints = new List<SlideP16ItemPoint>() { }, EditStatus = Visibility.Visible }; this.SlideControlParams.Add(item); //this.SlideImageParams.Add(item); this.SelectedSlide = item; } private void LoadLocalImage() { // 创建 OpenFileDialog 对象 OpenFileDialog openFileDialog = new OpenFileDialog(); // 设置文件类型过滤器,只允许选择图片文件 openFileDialog.Filter = "Image files (*.jpg;*.jpeg;*.bmp;*.png)|*.jpg;*.jpeg;*.bmp;*.png|All files (*.*)|*.*"; // 设置默认文件类型筛选器 openFileDialog.FilterIndex = 1; // 是否允许多选 openFileDialog.Multiselect = false; // 显示对话框并获取用户的操作结果 bool? result = openFileDialog.ShowDialog(); // 如果用户点击了确认按钮 if (result == true) { try { // 获取选择的文件名 string fileName = openFileDialog.FileName; FileInfo fn = new FileInfo(fileName); string targeImgPath = System.IO.Path.Combine( imgDir, "P16_" + SelectedSlide.ID.ToString() + fn.Extension ); File.Copy(fileName, targeImgPath, true); // 将 BitmapImage 对象设置为界面上的 Image 控件的 Source SelectedSlide.ImagePath = targeImgPath; // bitmapImage; } catch (Exception ex) { // 处理异常情况 HandyControl.Controls.MessageBox.Show( $"Error loading image: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error ); } } } /// <summary> /// 保存结果到json /// </summary> private void SaveResult() { SlideP16Parms prams = new SlideP16Parms(); prams.Items = this.SlideControlParams.Select(x => (SlideP16Item)x).ToList(); foreach (var p in prams.Items) { for (int i = 0; i < p.ItemPoints.Count; i++) { if (!p.ItemPoints[i].IsImagePoint) p.ItemPoints[i] = ConvertToImageCoordinates(p.ItemPoints[i]); // ConvertToImageCoordinates(); } } var str = JsonConvert.SerializeObject(prams, Formatting.Indented); // JsonConvert.SerializeObject(prams, Formatting.Indented); File.WriteAllText(jsonPath, str); } /// <summary> /// 刷新页面数据,重新读取 /// </summary> private void RefreshData() { LoadConfig(); } private void Control_SizeChanged() { if (imgSlide.Source == null || canvas.Children.Count == 0) return; var scaleX = imgSlide.ActualWidth / imgSlide.Source.Width; var scaleY = imgSlide.ActualHeight / imgSlide.Source.Height; imgMarginPos = imgSlide.TranslatePoint(new Point(0, 0), canvas); //将Image控件的(0,0)坐标点转换为相对于canvas的坐标点。返回的Point对象就表示了Image控件在canvas中的偏移量。 var offetX = imgMarginPos.X; var offetY = imgMarginPos.Y; for (int i = 0; i < canvas.Children.Count; i++) { var element = canvas.Children[i] as Rectangle; var tempRect = SelectedSlide.ItemPoints[i]; //if (!tempRect.IsImagePoint) //{ // tempRect = ConvertToImageCoordinates(tempRect); //不可行,因为此时转换的时候,已经是size-changed之后了,无法正确转换 //} var cav = VisualTreeHelper.GetParent(imgSlide) as Canvas; double left = tempRect.X; double top = tempRect.Y; left = left * scaleX; top = top * scaleY; left += offetX; top += offetY; var width = tempRect.Width; // element.ActualWidth; var height = tempRect.Height; // element.ActualHeight; width *= scaleX; height *= scaleY; Trace.WriteLine("rq.scaleX:" + scaleX); Trace.WriteLine("rq.scaleY:" + scaleY); Trace.WriteLine("rq.element width:" + width); Trace.WriteLine("rq.element height:" + height); Canvas.SetLeft(element, left); Canvas.SetTop(element, top); element.Width = width; element.Height = height; Trace.WriteLine("rq.left:" + left); Trace.WriteLine("rq.top:" + top); Trace.WriteLine("rq.width:" + width); Trace.WriteLine("rq.height:" + height); } } #endregion other func } #region json映射类 P16玻片参数 public class SlideP16ItemCombine : SlideP16Item { private Visibility _EditStatus; public Visibility EditStatus { get => _EditStatus; set => Set(ref _EditStatus, value); } } public class SlideP16Parms { public int ApplyID { get; set; } public List<SlideP16Item> Items { get; set; } } /// <summary> /// P16玻片配置信息 /// </summary> public class SlideP16Item : ViewModelBase { public int ID { get; set; } public List<SlideP16ItemPoint> ItemPoints { get; set; } private string _Name; public string Name { get => _Name; set => Set(ref _Name, value); } private string _ImagePath; /// <summary> /// 图片路径 /// </summary> public string ImagePath { get => _ImagePath; set => Set(ref _ImagePath, value); } } /// <summary> /// p16玻片子配置 /// </summary> public class SlideP16ItemPoint { public double X { get; set; } public double Y { get; set; } public double Width { get; set; } public double Height { get; set; } /// <summary> /// 是否为图像坐标,json读写忽略 /// </summary> [JsonIgnore] public bool IsImagePoint { get; set; } = true; } #endregion json映射类 }
标签:Canvas,set,tempRect,缩放,Image,get,private,new,public From: https://www.cnblogs.com/MarsPanda/p/18186351