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

这篇具有很好参考价值的文章主要介绍了WPF 组态软件实现思路(WPF控件可视化布局)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

来张图(后续细节完善后的效果)
WPF 组态软件实现思路(WPF控件可视化布局)
WPF 组态软件实现思路(WPF控件可视化布局)

本文实现效果
WPF 组态软件实现思路(WPF控件可视化布局)

图标使用了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代码文章来源地址https://www.toymoban.com/news/detail-439569.html

 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控件可视化布局)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 开源web组态软件

       最新版本  演示地址 : http://www.byzt.net 一、应用案例 可以广泛应用于化工、石化、制药、冶金、建材、市政、环保、电力等几十个行业。                                                                    电力行业                                        

    2024年01月20日
    浏览(51)
  • web组态软件开发详细流程介绍

    组态软件是一种用于创建、配置和管理各种工业控制系统的应用程序。它通过用户友好的图形界面和先进的功能,使工程师能够实时监控和控制复杂的工业过程。 一、组态软件的基本概念 1.1组态软件的作用 组态软件在工业控制系统中起到关键的作用。 它可以实时获取和展示

    2024年02月20日
    浏览(46)
  • 国内外组态软件对比分析(InTouch、WinCC、iFix、iNeuOS)

         在我国自动化控制领域应用较广泛的工业自动化组态软件有Wonderware公司InTouch、西门子公司Wincc、GE公司iFix。国内也有一些传统组态软件厂商,使用的功能和形式基本上十分类似,受当时开发环境和组态软件框架的限制,也很难做较大的改变。      国内工业软件领域

    2024年02月06日
    浏览(42)
  • iot-Scada免费Scada组态软件系列教程4-二次开发与版本部署

     iot-Scada免费Scada组态软件系列教程 iot-Scada免费Scada组态软件系列教程1-初识iot-Scada iot-Scada免费Scada组态软件系列教程2-架构设计 iot-Scada免费Scada组态软件系列教程3-各模块详细介绍  iot-Scada免费Scada组态软件系列教程4-二次开发与版本部署 iot-Scada(意为:Internet of things Scada(物联

    2024年02月10日
    浏览(42)
  • 基于Visual Studio扩展的WPF工业组态UI控件-ConPipe

    本文的组态控件是由《轻量而敏捷的工业组态软件UI设计工具-ConPipe Studio 2022》 和 《轻量而敏捷的工业组态软件UI设计工具-机械组态篇》两篇文章中的方案全新升级而来的,升级控件依然继承了“程序员自己能干的事情绝不麻烦美工”的思想。最大的不同就是由ConPipe Studio工

    2023年04月16日
    浏览(49)
  • 界面控件DevExpress WPF Chart组件——拥有超快的数据可视化库!

    DevExpress WPF Chart组件拥有超大的可视化数据集,并提供交互式仪表板与高性能WPF图表库。DevExpress Charts提供了全面的2D / 3D图形集合,包括数十个UI定制和数据分析/数据挖掘选项。 PS:DevExpress WPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。

    2024年02月13日
    浏览(51)
  • WPF 实现 Message 消息提醒控件

    WPF 实现 Message 消息提醒控件 控 件:Message 作 者:WPFDevelopersOrg - 驚鏵 原文链接:https://github.com/WPFDevelopersOrg/WPFDevelopers 框架使用 .NET4 至 .NET6 ; Visual Studio 2022 ; 接着上一篇 1)新增 MessageListBoxItem.cs 代码如下: 新增了名为 MessageType 的依赖属性,类型为 MessageBoxImage ,默认值为

    2024年02月16日
    浏览(35)
  • WPF实现html中的table控件

    前言 相信很多做WPF开发的小伙伴都遇到过表格类的需求,虽然现有的Grid控件也能实现,但是使用起来的体验感并不好,比如要实现一个Excel中的表格效果,估计你能想到的第一个方法就是套Border控件,用这种方法你需要控制每个Border的边框,并且在一堆Bordr中找到Grid.Row,G

    2024年03月26日
    浏览(44)
  • 可视化web组态开发工具

    BY组态是一款功能强大的基于Web的可视化组态编辑器Q,采用标准HTML5技术,基于B/S架构进行开发,支持WEB端呈现,支持在浏览器端完成便捷的人机交互,简单的拖拽即可完成可视化页面的设计。可快速构建和部署可扩展的SCADA、HMI、仪表板或IIoT系统。使用BY组态编辑器,可以创

    2024年02月01日
    浏览(53)
  • C# wpf 实现Canvas内控件拖动

    第一章 Grid内控件拖动 第二章 Canvas内控件拖动(本章) 第三章 任意控件拖动 第四章 窗口拖动 第五章 附加属性实现任意拖动 第六章 拓展更多拖动功能 有一些业务场景中我们需要拖动控件,上一篇文章《C# wpf 实现Grid内控件拖动》在Grid中实现了控件拖动,在Canvas中也可以实

    2024年02月05日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包