WPF 开源 组态软件实现思路(WPF控件可视化布局)

  • 一、实现控件选中及自由拖动
  • 二、实现控件对齐功能
  • 三、实现对齐辅助线功能
  • 四、实现框选功能

GitHub地址点此

请注意

  • 属性编辑控件基于Devexpress V21.2.3 控件库,如需编译需购买及安装 Devexpress V21.2.3 开发库
  • 脚本编辑基于AvalonEdit开源库 https://github.com/icsharpcode/AvalonEdit
  • 图标控件基于MahApps.Metro.IconPacks开源 https://github.com/MahApps/MahApps.Metro.IconPacks
  • JavaScript脚本运行基于Unvell.ReoScript开源库 https://github.com/unvell/ReoScript

来张图(后续细节完善后的效果)

本文实现效果

图标使用了nuget库MahApps.Metro.IconPacks.Modern
实现拖动的方式有很多,本文使用了装饰器(Adorner)。使用装饰器的好处在与可以实现控件的选中效果显示。
布局容器使用了Canvas,比较方便实现控件的绝对定位。继承自Canvas并添加SelectedItems属性
Xaml代码:
主窗体代码:

   <Grid><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition Width="200"/></Grid.ColumnDefinitions><DockPanel><StackPanel DockPanel.Dock="Top" Height="24" Margin="8 4 8 0" Orientation="Horizontal"><Button Margin="0" Width="24" Padding="0" Click="AglinLeftBtn_Click" ToolTip="左对齐"><icon:PackIconModern Kind="AlignLeft"/></Button><Button Margin="4 0 0 0" Width="24" Padding="0" Click="AglinRightBtn_Click" ToolTip="右对齐"><icon:PackIconModern Kind="AlignRight"/></Button><Button Margin="4 0 0 0" Width="24" Padding="0" Click="AglinTopBtn_Click" ToolTip="上对齐"><icon:PackIconModern Kind="BorderTop"/></Button><Button Margin="4 0 0 0" Width="24" Padding="0" Click="AglinBottomBtn_Click" ToolTip="下对齐"><icon:PackIconModern Kind="BorderBottom"/></Button><Button Margin="16 0 0 0" Width="24" Padding="0" Click="HorizontalLayoutBtn_Click" ToolTip="水平分布"><icon:PackIconModern Kind="BorderHorizontal"/></Button><Button Margin="4 0 0 0" Width="24" Padding="0" Click="VerticalLayoutBtn_Click" ToolTip="垂直分布"><icon:PackIconModern Kind="BorderVertical"/></Button></StackPanel><Border BorderThickness="1" BorderBrush="#2B79E2" Margin="8"><local:CanvasPanel x:Name="cav" Background="WhiteSmoke" ClipToBounds="True"><Button Canvas.Left="100" Canvas.Top="100" Width="80" Height="30" Content="s"/><Button Canvas.Left="300" Canvas.Top="150" Width="80" Height="30" Content="1"/><Button Canvas.Left="300" Canvas.Top="250" Width="80" Height="30" Content="1"/></local:CanvasPanel></Border></DockPanel><Grid Grid.Column="1"><!--<dxprg:PropertyGridControl SelectedObjects="{Binding ElementName=cav, Path=SelectedItems}" Margin="8"/>--></Grid></Grid>

主窗体CodeBehind

public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}private void AglinLeftBtn_Click(object sender, RoutedEventArgs e){cav.AlignLeft();}private void AglinBottomBtn_Click(object sender, RoutedEventArgs e){cav.AlignBottom();}private void AglinTopBtn_Click(object sender, RoutedEventArgs e){cav.AlignTop();}private void AglinRightBtn_Click(object sender, RoutedEventArgs e){cav.AlignRight();}private void VerticalLayoutBtn_Click(object sender, RoutedEventArgs e){cav.VertialLayout();}private void HorizontalLayoutBtn_Click(object sender, RoutedEventArgs e){cav.HorizontalLayout();}}

CanvaPanel代码

public class CanvasPanel : Canvas{public CanvasPanel(){}#region 单击选中项处理protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved){if (visualAdded is Control ctrl){ctrl.PreviewMouseLeftButtonDown += Ctrl_MouseLeftButtonDown;}if (visualRemoved is Control ctr){ctr.PreviewMouseLeftButtonDown -= Ctrl_MouseLeftButtonDown;}base.OnVisualChildrenChanged(visualAdded, visualRemoved);}private void Ctrl_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e){if (sender is Control ctl){var cp = GetParentObject<CanvasPanel>(ctl);cp.SelectedItems = new ObservableCollection<Control>() { ctl };}}public static T GetParentObject<T>(DependencyObject obj) where T : FrameworkElement{DependencyObject parent = VisualTreeHelper.GetParent(obj);while (parent != null){if (parent is T){return (T)parent;}parent = VisualTreeHelper.GetParent(parent);}return null;}#endregion#region 绘制选择框Border selectionBorder = new Border(){Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#637DB7F4")),BorderThickness = new Thickness(1),BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFBBBBBB")),};protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e){base.OnMouseLeftButtonDown(e);this.Children.Add(selectionBorder);selectionStart = e.GetPosition(this);this.CaptureMouse();}Point selectionStart = default;protected override void OnMouseMove(MouseEventArgs e){base.OnMouseMove(e);if (e.LeftButton == MouseButtonState.Pressed){var nowPoint = e.GetPosition(this);var offsetX = nowPoint.X - selectionStart.X;var offsetY = nowPoint.Y - selectionStart.Y;Clear();selectionBorder.Width = Math.Abs(offsetX);selectionBorder.Height = Math.Abs(offsetY);// 分四种情况绘制if (offsetX >= 0 && offsetY >= 0)// 右下{SetLeft(selectionBorder, selectionStart.X);SetTop(selectionBorder, selectionStart.Y);}else if (offsetX > 0 && offsetY < 0)// 右上{SetLeft(selectionBorder, selectionStart.X);SetBottom(selectionBorder, ActualHeight - selectionStart.Y);}else if (offsetX < 0 && offsetY > 0)// 左下{SetRight(selectionBorder, ActualWidth - selectionStart.X);SetTop(selectionBorder, selectionStart.Y);}else if (offsetX < 0 && offsetY < 0)// 左上{SetRight(selectionBorder, ActualWidth - selectionStart.X);SetBottom(selectionBorder, ActualHeight - selectionStart.Y);}}}protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e){base.OnMouseLeftButtonUp(e);if (double.IsNaN(GetLeft(selectionBorder))){SetLeft(selectionBorder, ActualWidth - GetRight(selectionBorder) - selectionBorder.ActualWidth);}if (double.IsNaN(GetTop(selectionBorder))){SetTop(selectionBorder, ActualHeight - GetBottom(selectionBorder) - selectionBorder.ActualHeight);}FrameSelection(GetLeft(selectionBorder), GetTop(selectionBorder), selectionBorder.Width, selectionBorder.Height);selectionBorder.Width = 0;selectionBorder.Height = 0;this.Children.Remove(selectionBorder);this.ReleaseMouseCapture();}private void Clear(){SetLeft(selectionBorder, double.NaN);SetRight(selectionBorder, double.NaN);SetTop(selectionBorder, double.NaN);SetBottom(selectionBorder, double.NaN);}#endregion#region 框选public ObservableCollection<Control> SelectedItems{get { return (ObservableCollection<Control>)GetValue(SelectedItemsProperty); }set { SetValue(SelectedItemsProperty, value); }}public static readonly DependencyProperty SelectedItemsProperty =DependencyProperty.Register("SelectedItems", typeof(ObservableCollection<Control>), typeof(CanvasPanel), new PropertyMetadata(null, OnSelectedItemsChanged));private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => (d as CanvasPanel)?.RefreshSelection();private void RefreshSelection(){foreach (var item in Children){var ele = item as Control;if (ele == null) continue;var layer = AdornerLayer.GetAdornerLayer(ele);var arr = layer.GetAdorners(ele);//获取该控件上所有装饰器,返回一个数组if (arr != null){for (int i = arr.Length - 1; i >= 0; i--){layer.Remove(arr[i]);}}}if (SelectedItems != null){foreach (var item in SelectedItems){var layer = AdornerLayer.GetAdornerLayer(item);layer.Add(new SelectionAdorner(item));}}}/// <summary>/// 计算框选项/// </summary>private void FrameSelection(double x, double y, double width, double height){SelectedItems = new ObservableCollection<Control>();foreach (var item in Children){if (item is Control ctrl){var left = GetLeft(ctrl);var top = GetTop(ctrl);if (left >= x && left <= x + width && top >= y && top <= y + height){SelectedItems.Add(ctrl);}}}RefreshSelection();}#endregion#region 外部调用public void MoveControls(int offsetX, int offsetY){ClearAlignLine();// 获取可对齐的点List<Point> points = new List<Point>();foreach (Control ctrl in Children){if (!SelectedItems.Contains(ctrl)){Point item = new Point(GetLeft(ctrl), GetTop(ctrl));points.Add(item);}}foreach (var item in SelectedItems){SetLeft(item, GetLeft(item) + offsetX);SetTop(item, GetTop(item) + offsetY);// 计算是否显示对齐线var lefAlign = points.FirstOrDefault(x => Math.Abs(x.X - GetLeft(item)) <= 1);if (lefAlign != default){SetLeft(item, lefAlign.X);var layer = AdornerLayer.GetAdornerLayer(this);layer.Add(new SelectionAlignLine(this, lefAlign, new Point(GetLeft(item), GetTop(item))));}var topAlign = points.FirstOrDefault(x => Math.Abs(x.Y - GetTop(item)) <= 1);if (topAlign != default){SetTop(item, topAlign.Y);var layer = AdornerLayer.GetAdornerLayer(this);layer.Add(new SelectionAlignLine(this, topAlign, new Point(GetLeft(item), GetTop(item))));}}}/// <summary>/// 清除绘制的对齐线/// </summary>public void ClearAlignLine(){var arr = AdornerLayer.GetAdornerLayer(this).GetAdorners(this);if (arr != null){for (int i = arr.Length - 1; i >= 0; i--){AdornerLayer.GetAdornerLayer(this).Remove(arr[i]);}}}public void ZoomControls(int offsetX, int offsetY){foreach (var item in SelectedItems){if (item.ActualHeight + offsetY > 10){item.Height += offsetY;}if (item.ActualWidth + offsetX > 10){item.Width += offsetX;}}}#endregion#region 对齐操作public void AlignLeft(){if (SelectedItems == null || SelectedItems.Count == 0)return;var leftMin = SelectedItems.Min(x => Canvas.GetLeft(x));foreach (var item in SelectedItems){SetLeft(item, leftMin);}}public void AlignRight(){if (SelectedItems == null || SelectedItems.Count == 0)return;var rightMax = SelectedItems.Max(x => GetLeft(x) + x.ActualWidth);foreach (var item in SelectedItems){var targetLeft = rightMax - item.ActualWidth;SetLeft(item, targetLeft);}}public void AlignTop(){if (SelectedItems == null || SelectedItems.Count == 0)return;var topMin = SelectedItems.Min(x => GetTop(x));foreach (var item in SelectedItems){SetTop(item, topMin);}}public void AlignBottom(){if (SelectedItems == null || SelectedItems.Count == 0)return;var botMax = SelectedItems.Max(x => GetTop(x) + x.ActualHeight);foreach (var item in SelectedItems){var targetLeft = botMax - item.ActualHeight;SetTop(item, targetLeft);}}public void VertialLayout(){if (SelectedItems == null || SelectedItems.Count < 3)return;var topCtl = SelectedItems.Min(x => GetTop(x) + x.ActualHeight);var botCtrl = SelectedItems.Max(x => GetTop(x));var emptyHeight = botCtrl - topCtl;var orderCtrl = SelectedItems.OrderBy(x => GetTop(x)).ToList();orderCtrl.RemoveAt(0);orderCtrl.RemoveAt(orderCtrl.Count - 1);var useSpace = orderCtrl.Sum(x => x.ActualHeight);var ableSpaceAvg = (emptyHeight - useSpace) / (SelectedItems.Count - 1);double nowPostion = topCtl;foreach (var item in orderCtrl){SetTop(item, nowPostion + ableSpaceAvg);nowPostion += item.ActualHeight;}}public void HorizontalLayout(){if (SelectedItems == null || SelectedItems.Count < 3)return;var leftCtl = SelectedItems.Min(x => GetLeft(x) + x.ActualWidth);var rightCtrl = SelectedItems.Max(x => GetLeft(x));var emptyHeight = rightCtrl - leftCtl;var orderCtrl = SelectedItems.OrderBy(x => GetLeft(x)).ToList();orderCtrl.RemoveAt(0);orderCtrl.RemoveAt(orderCtrl.Count - 1);var useSpace = orderCtrl.Sum(x => x.ActualWidth);var ableSpaceAvg = (emptyHeight - useSpace) / (SelectedItems.Count - 1);double nowPostion = leftCtl;foreach (var item in orderCtrl){SetLeft(item, nowPostion + ableSpaceAvg);nowPostion += item.ActualWidth;}}#endregion}

SelectionAdorner代码

 internal class SelectionAdorner : Adorner{public SelectionAdorner(UIElement adornedEIeent) : base(adornedEIeent) { }protected override void OnRender(DrawingContext drawingContext){base.OnRender(drawingContext);Rect adornerRect = new Rect(AdornedElement.DesiredSize);SolidColorBrush renderBrush = Brushes.Transparent;Pen render = new Pen(new SolidColorBrush(Colors.OrangeRed), 1);drawingContext.DrawRectangle(renderBrush, render, new Rect(adornerRect.TopLeft.X, adornerRect.TopLeft.Y, adornerRect.Width, adornerRect.Height));MouseDown += SelectionAdorner_MouseDown;MouseMove += SelectionAdorner_MouseMove;MouseUp += SelectionAdorner_MouseUp;}private void SelectionAdorner_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e){ReleaseMouseCapture();CanvasPanel.GetParentObject<CanvasPanel>(AdornedElement).ClearAlignLine();}Point lastPoint = new Point();private void SelectionAdorner_MouseMove(object sender, System.Windows.Input.MouseEventArgs e){if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed){CaptureMouse();var nowPoint = e.GetPosition(CanvasPanel.GetParentObject<CanvasPanel>(AdornedElement));int offsetX = (int)(nowPoint.X - lastPoint.X);int offsetY = (int)(nowPoint.Y - lastPoint.Y);CanvasPanel.GetParentObject<CanvasPanel>(AdornedElement).MoveControls(offsetX, offsetY);lastPoint = nowPoint;}else if (e.RightButton == System.Windows.Input.MouseButtonState.Pressed){CaptureMouse();var nowPoint = e.GetPosition(CanvasPanel.GetParentObject<CanvasPanel>(AdornedElement));int offsetX = (int)(nowPoint.X - lastPoint.X);int offsetY = (int)(nowPoint.Y - lastPoint.Y);CanvasPanel.GetParentObject<CanvasPanel>(AdornedElement).ZoomControls(offsetX, offsetY);lastPoint = nowPoint;}}private void SelectionAdorner_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e){lastPoint = e.GetPosition(CanvasPanel.GetParentObject<CanvasPanel>(AdornedElement));}}

SelectionAlignLine代码

 public class SelectionAlignLine : Adorner{public SelectionAlignLine(UIElement adornedElement, Point start, Point end) : base(adornedElement){startPoint = start;endPoint = end;}Point startPoint = default(Point);Point endPoint = default(Point);protected override void OnRender(DrawingContext drawingContext){base.OnRender(drawingContext);Rect adornerRect = new Rect(AdornedElement.DesiredSize);Pen render = new Pen(new SolidColorBrush(Colors.Gray), 1);drawingContext.DrawLine(render, startPoint, endPoint);}}

WPF 组态软件实现思路(WPF控件可视化布局)相关推荐

  1. WPF遍历当前容器中某种控件的方法

    原文:WPF遍历当前容器中某种控件的方法 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/m0_37591671/article/details/79 ...

  2. WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

    欢迎转载,转载请注明:转载自周金根 [ http://zhoujg.cnblogs.com/ ] 由于上周主要做了项目组产品架构.给公司新员工培训以及其他会议等事情,在OpenExpressApp对建 ...

  3. WPF 的内部世界(控件与布局)

    我一开始算是比较抵触WPF的,因为用的人少吗.感觉都是窗体应用能和Winform有什么区别.可是我错了,非常感谢我的讲师,给我推荐刘铁猛的<深入浅出WPF>,让我了解到了WPF的魅力--数 ...

  4. WPF 中动态创建和删除控件

    WPF 中动态创建和删除控件 原文:WPF 中动态创建和删除控件 动态创建控件 1.容器控件.RegisterName("Name",要注册的控件)   //注册控件 2.容器控件 ...

  5. WPF 可触摸移动的ScrollViewer控件

    原文:WPF 可触摸移动的ScrollViewer控件 ListBox支持触摸滑动,而ScrollViewer默认不支持. ScrollViewer如需要添加上下/左右触摸移动,需要在Touch事件中 ...

  6. Actipro WPF Studio语法编辑器和停靠控件

    Actipro WPF Studio语法编辑器和停靠控件 对接 向选项卡式 MDI 选项卡添加了"全部浮动"菜单项,它将容器中的所有停靠窗口浮动在一起. 改进了目标坞站主机命中测试 ...

  7. WPF基础五:UI④ 条目控件ContextMenu

    派生关系: Object->DispatcherObject->DependencyObject->Visual->UIElement->FrameworkElement ...

  8. 易语言编程: 让读屏软件可获取标签控件的文本

    易语言编程: 让读屏软件可获取标签控件的文本 将易语言的非标准标签控件修改为标准的标签控件,使屏幕阅读器可获取到标签的内容 在使用易语言创建窗口控件时,我们会发现:易语言的编辑框.组合框.列表框等控件 ...

  9. 组态王曲线控件读取access_组态王,历史趋势曲线控件例程说明文档

    历史趋势曲线 1 ,功能概述 常规需求: 很多工业现场都会要求显示采集量的趋势曲线 , 包括实时曲线. 历史曲线. 组态王中的趋势曲线的实现方法: 1 )利用组态王的"工具箱"中的 ...

最新文章

  1. java 与 |与||的区别
  2. apt包管理 Android,apt软件包管理学习(示例代码)
  3. 新浪宣布2010年第四季度业绩 盘后跌4%
  4. 烂泥:文件服务器搭建与使用详解,minio文件服务器搭建(单机版)
  5. django form choice
  6. centos7安装MongoDB(亲测)
  7. delphi操作xml学习笔记 之一 入门必读
  8. Mac OS 加入域
  9. 电商峰值系统架构设计--转载
  10. MDK3358平台QT示例-ADS1110温度采集示例
  11. 电子邮件格式详细介绍
  12. Excel-利用函数获取工作表标签名称(转)
  13. 程序员怒批996背后的支持者,刘强东和马云哑口无言!
  14. 计算机考研考线代和概率论吗,关于考研数学线代和概率论的暑期复习扫尾建议...
  15. pcm输出还是源码输出_观看高清必备 如何简单实现源码输出
  16. 一张收款码,同时支持微信、云闪付、支付宝、信用卡支付
  17. pyplot 画多个图时搅合到了一起_家里来了好些小朋友,什么游戏可以让孩子们玩到一起?...
  18. VMWARE下的Ubuntu清理磁盘
  19. php7 设置404页面,zblogphp怎么修改或创建404错误页面的方法
  20. 【Android春招每日一练】(十四) 剑指4题+Android进阶

热门文章

  1. android对讲机 服务端,Android的WIFI局域网对讲机
  2. 阅读理解解题思路汇总
  3. DOM学习1.0:在页面切换图片和文字
  4. uni-app微信小程序封装一个request请求接口
  5. python画简单圣诞树_教你用Python画了一棵圣诞树!圣诞节给女朋友制作个小惊喜...
  6. Rancher2忘记admin登录密码
  7. 由于计算机通常采用向下兼容方式,计算机一级考试真题.doc
  8. 修复 win10 打印服务器失败,win10专业版中使用这些解决方案修复打印机系统错误1797...
  9. C语言中的sscanf()函数使用详解
  10. 一些RxBinding使用场景