当前位置:   article > 正文

WPF父容器根据內部控件的拖拽自动扩展大小_wpf 拖动改变内部布局

wpf 拖动改变内部布局

流程图系列文章

第二章 WPF父容器根据內部控件的拖拽自动扩展大小
第一章 WPF下实现控件的拖拽功能



前言

我们写运动控制上位机程序的时候,经常是要考虑如何实现一个流程,我们要考虑,完成当前步骤后,下一个步骤要实现什么,判断条件,满足后的步骤是什么,不满足条件的步骤又是什么,或者需要一直等待条件满足才开始下一个步骤,也要考虑超时都不满足条件需要走另一个步骤,根据条件也有可能会跳转回原来的步骤,这些逻辑通常都是很繁琐的(使用if-elseif、switch-case),而且写好后再次修改时,容易漏改忘改等造成逻辑不稳定问题;这里,我们介绍一种全新的实现逻辑的方式:通过流程图的原理去实现这些业务性的繁琐的逻辑,实现逻辑可视化,编程拖拉拽
前面我们已经实现了第一步,完成了控件的拖拉拽,但还有以下问题点当时是没有优化的,也是为了突出强调"拖拉拽"功能的实现,接下来是完善以下功能的实现:控件只能放进指定的容器内,只能在容器内部移动,容器有默认的大小,超出大小容器要自动扩展;
在这里,我们会有技术点详解、代码展示、效果展示、源码链接!


一、控件的绘制

上文中的目标,使用的是Border源(使用VS的Blend.exe可轻松绘制不同形状),分别是不同形状的,类似于流程图,这里提供以下几个已经绘制好了的控件:

不同形状控件的Style:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:FlowDemo1.Convert" >

    <local:EnumGetDiaplayNameConvert x:Key="GetDisplayName"/>

    <Brush x:Key="Brush_FlowControl_Base">#336AA6FF</Brush>
    <Brush x:Key="Brush_FlowControl_Font">#FF000000</Brush>
    <Brush x:Key="Brush_FlowControl_Border">#FF000000</Brush>

    <!-- BaseControlStyle -->
    <Style x:Key="BaseControlStyle" TargetType="Path">
        <Setter Property="StrokeThickness" Value="1"/>
        <Setter Property="StrokeLineJoin" Value="Round"/>
        <Setter Property="Stretch" Value="Fill"/>
        <Setter Property="IsHitTestVisible" Value="False"/>
        <Setter Property="SnapsToDevicePixels" Value="True"/>
        <Setter Property="Fill" Value="{StaticResource Brush_FlowControl_Base}"/>
        <Setter Property="Stroke" Value="{StaticResource Brush_FlowControl_Border}"/>
        <Setter Property="MinWidth" Value="60"/>
        <Setter Property="MinHeight" Value="30"/>
    </Style>

    <!-- 开始控件:"跑道圆",表示一个流程的开始-->
    <Style x:Key="StartPath" TargetType="Path" BasedOn="{StaticResource BaseControlStyle}">
        <Setter Property="Data" Value="M15,30 A15,15 0 0 1 15,0 H65 A15,15 0 0 1 65,30 Z"/>
    </Style>
    <Style x:Key="StartControl" TargetType="Path" BasedOn="{StaticResource StartPath}">
        <Setter Property="IsHitTestVisible" Value="true"/>
    </Style>

    <!--结束控件:"跑道圆",表示一个流程的结束-->
    <Style x:Key="FinishPath" TargetType="Path" BasedOn="{StaticResource BaseControlStyle}">
        <Setter Property="Data" Value="M15,30 A15,15 0 0 1 15,0 H65 A15,15 0 0 1 65,30 Z"/>
    </Style>
    <Style x:Key="FinishControl" TargetType="Path" BasedOn="{StaticResource FinishPath}">
        <Setter Property="IsHitTestVisible" Value="true"/>
    </Style>

    <!--行为控件:"矩形",在这里写"工序""处理流程"-->
    <Style x:Key="ActionPath" TargetType="Path" BasedOn="{StaticResource BaseControlStyle}">
        <Setter Property="Data" Value="M0,0 H80 V30 H0 Z"/>
    </Style>
    <Style x:Key="ActionControl" TargetType="Path" BasedOn="{StaticResource ActionPath}">
        <Setter Property="IsHitTestVisible" Value="true"/>
    </Style>

    <!--条件控件:"菱形",判断条件,根据判断结果走正常分支(Y)、异常分支(N)、超时分支(T)-->
    <Style x:Key="ConditionPath" TargetType="Path" BasedOn="{StaticResource BaseControlStyle}">
        <Setter Property="Data" Value="M40,0 80,30 40,60 0,30 Z"/>
    </Style>
    <Style x:Key="ConditionControl" TargetType="Path" BasedOn="{StaticResource ConditionPath}">
        <Setter Property="IsHitTestVisible" Value="true"/>
    </Style>

    <!--文件控件:"特殊形状",区别于其他形状的特殊形状,用于输出文件或者写入、修改文件,其他的如通讯的处理也是同理-->
    <Style x:Key="FilePath" TargetType="Path" BasedOn="{StaticResource BaseControlStyle}">
        <Setter Property="Data" Value="M0,30 0,0 H80 V30 A60,60 0 0 0 40,30 A60,60 0 0 1 0,30 Z"/>
    </Style>
    <Style x:Key="FileControl" TargetType="Path" BasedOn="{StaticResource FilePath}">
        <Setter Property="IsHitTestVisible" Value="true"/>
    </Style>

    <!--模块控件:"平行四边形",跳转到另外一个流程,全部处理完成后(执行完结束)再跳转回来,继续往下走-->
    <Style x:Key="ModulePath" TargetType="Path" BasedOn="{StaticResource BaseControlStyle}">
        <Setter Property="Data" Value="M10,0 H100 L90,40 H0 Z"/>
    </Style>
    <Style x:Key="ModuleControl" TargetType="Path" BasedOn="{StaticResource ModulePath}">
        <Setter Property="IsHitTestVisible" Value="true"/>
    </Style>

    <!--预设模块控件:"双边矩形",跳转到另外一个流程(已经是实现好了的自定义模块,需要设置输入,不同自定义模块不同的颜色),全部处理完成后(执行完结束)再跳转回来,继续往下走-->
    <Style x:Key="DefineModulePath" TargetType="Path" BasedOn="{StaticResource BaseControlStyle}">
        <Setter Property="Data" Value="M0,0 H100 V40 H0 V0 Z M10,0 V40 M90,0 V40"/>
    </Style>
    <Style x:Key="DefineModuleControl" TargetType="Path" BasedOn="{StaticResource DefineModulePath}">
        <Setter Property="IsHitTestVisible" Value="true"/>
    </Style>
</ResourceDictionary>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

根据名称选择不同的控件形状Style:

<UserControl x:Class="FlowDemo1.FlowControl.BasicControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:FlowDemo1.FlowControl"
             mc:Ignorable="d" Height="Auto" Width="Auto">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/FlowDemo1;Component/FlowControl/FlowControlStyleDic.xaml" />
            </ResourceDictionary.MergedDictionaries>
            <ControlTemplate x:Key="ControlStyle" TargetType="Label">
                <Grid>
                    <Path x:Name="Path"/>
                    <Label x:Name="Desc" Content="{Binding ControlAttr.ControlName, Converter={StaticResource GetDisplayName}}" FontSize="16" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <!--数据触发器解读:当ControlAttr.ControlDesc的值==Value的值“开始”时,触发事件,将名称为Path的控件的Style样式设置为静态样式Start_DragThumb-->
                    <DataTrigger Value="开始" Binding="{Binding ControlAttr.ControlName, Converter={StaticResource GetDisplayName}}">
                        <Setter TargetName="Path" Property="Style" Value="{StaticResource StartControl}"/>
                        <Setter TargetName="Path" Property="Width" Value="80"/>
                        <Setter TargetName="Path" Property="Height" Value="30"/>
                    </DataTrigger>
                    <DataTrigger Value="结束" Binding="{Binding ControlAttr.ControlName, Converter={StaticResource GetDisplayName}}">
                        <Setter TargetName="Path" Property="Style" Value="{StaticResource FinishControl}"/>
                        <Setter TargetName="Path" Property="Width" Value="80"/>
                        <Setter TargetName="Path" Property="Height" Value="30"/>
                    </DataTrigger>
                    <DataTrigger Value="行为" Binding="{Binding ControlAttr.ControlName, Converter={StaticResource GetDisplayName}}">
                        <Setter TargetName="Path" Property="Style" Value="{StaticResource ActionControl}"/>
                        <Setter TargetName="Path" Property="Width" Value="80"/>
                        <Setter TargetName="Path" Property="Height" Value="30"/>
                    </DataTrigger>
                    <DataTrigger Value="条件" Binding="{Binding ControlAttr.ControlName, Converter={StaticResource GetDisplayName}}">
                        <Setter TargetName="Path" Property="Style" Value="{StaticResource ConditionControl}"/>
                        <Setter TargetName="Path" Property="Width" Value="80"/>
                        <Setter TargetName="Path" Property="Height" Value="60"/>
                    </DataTrigger>
                    <DataTrigger Value="文件" Binding="{Binding ControlAttr.ControlName, Converter={StaticResource GetDisplayName}}">
                        <Setter TargetName="Path" Property="Style" Value="{StaticResource FileControl}"/>
                        <Setter TargetName="Path" Property="Width" Value="80"/>
                        <Setter TargetName="Path" Property="Height" Value="40"/>
                    </DataTrigger>
                    <DataTrigger Value="模块" Binding="{Binding ControlAttr.ControlName, Converter={StaticResource GetDisplayName}}">
                        <Setter TargetName="Path" Property="Style" Value="{StaticResource ModuleControl}"/>
                        <Setter TargetName="Path" Property="Width" Value="80"/>
                        <Setter TargetName="Path" Property="Height" Value="40"/>
                    </DataTrigger>
                    <DataTrigger Value="预设模块" Binding="{Binding ControlAttr.ControlName, Converter={StaticResource GetDisplayName}}">
                        <Setter TargetName="Path" Property="Style" Value="{StaticResource DefineModuleControl}"/>
                        <Setter TargetName="Path" Property="Width" Value="100"/>
                        <Setter TargetName="Path" Property="Height" Value="40"/>
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid>
        <Label Content="{Binding ControlAttr.ControlName, Converter={StaticResource GetDisplayName}}" Template="{StaticResource ControlStyle}" HorizontalContentAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</UserControl>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

二、拉拽控件到指定画布容器

Xaml

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="pan"  Background="AliceBlue"/>
        <!--Canvas是拖拽的放置对象,需要设置:Drop事件,拖拽完成时触发;AllowDrop,允许拖拽;BackGroud,设置一个颜色,不然无法接收到事件-->
        <Grid x:Name="pan1" Grid.Column="1" Height="Auto" Width="Auto" Background="LightGray">
            <ScrollViewer x:Name="scroll" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Background="LightGray">
                <Canvas x:Name="canvas" Drop="FlowControl_Drop" AllowDrop="True" Background="LightGray" Tag="0"/>
            </ScrollViewer>
        </Grid>
    </Grid>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

类的实现

public MainWindow()
{
    InitializeComponent();

    pan.Children.Clear();
    foreach (EFlowControl value in Enum.GetValues(typeof(EFlowControl)))
    {
        BasicControl basicControl = new BasicControl(new FlowControlItem(value));
        basicControl.MouseLeftButtonDown += BorderCopy_MouseLeftButtonDown;
        basicControl.Margin = new Thickness(0, 10, 0, 10);
        pan.Children.Add(basicControl);
    }
}

private void BorderCopy_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (sender is BasicControl item)
    {
        //拖动一个窗体内的控件(border)到另一个容器控件内,而保留原来的控件。
        DragObject dragObject = new DragObject();
        dragObject.ContentObj = item.ControlAttr;
        CFuncHelper.DoDragDrop(item, dragObject, DragDropEffects.Copy); ;
    }
    else
    {
        throw new Exception("sender 转换类型Border类型异常");
    }
}

private void FlowControl_Drop(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(typeof(DragObject)))
    {
        object obj = e.Data.GetData(typeof(DragObject));
        if (obj as DragObject is DragObject item)
        {
            BasicControl basicControl = new BasicControl(item.ContentObj as FlowControlItem ?? throw new Exception("FlowControl_Drop113异常"));
            basicControl.Margin = new Thickness(0);
            //注册鼠标事件,分别是鼠标按下、鼠标移动、鼠标弹起
            basicControl.PreviewMouseDown += BorderMove_PreviewMouseDown;
            basicControl.PreviewMouseMove += BorderMove_PreviewMouseMove;
            basicControl.PreviewMouseUp += BorderMove_PreviewMouseUp;

            canvas.Children.Add(basicControl);

            //可以获取拖拽完成时,鼠标相对于canvas的位置坐标!
            Point point = e.GetPosition(canvas);
            basicControl.ControlAttr.mouseObject.MouseDownPosition = point;

            //使控件中心点与鼠标焦点对齐
            double marginLeft = point.X - basicControl.Width / 2;
            double marginTop = point.Y - basicControl.Height / 2.0;
            double marginRight = canvas.ActualWidth - marginLeft;
            double marginBottom = canvas.ActualHeight - marginTop;
            basicControl.Margin = new Thickness(marginLeft, marginTop, marginRight, marginBottom);
            basicControl.ControlAttr.mouseObject.MouseDownMargin = basicControl.Margin;

            Debug.WriteLine($"Canvas_Drop:{basicControl.Margin.Left},{basicControl.Margin.Right}");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

三、画布容器格局控件位置自动扩展大小

底层逻辑:当控件一直往下或者往右拖拽时,如果容器固定大小,就没办法去容纳更多的控件,以及无法自由调整控件位置,所以父容器必须要自动扩展大小,当控件拖拽位置到达下边界或者右边界时,父容器要自动变大,并且要有滚动条,滚动条自动跳转合适位置;反之,如果把边界处的控件拖拽回去,父容器可以自动缩小,以便去除多余的滚动条位置,这里是通过便利父容器内子控件的最大位置来实现。

#region C# wpf 实现Canvas内控件拖动
private void BorderMove_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    if (sender is BasicControl basicControl)
    {
        basicControl.ControlAttr.mouseObject.IsMouseDown = true;
        basicControl.ControlAttr.mouseObject.MouseDownPosition = e.GetPosition(this);
        basicControl.ControlAttr.mouseObject.MouseDownMargin = basicControl.Margin;
        basicControl.CaptureMouse();
    }
    else
    {
        throw new Exception("sender 转换类型Border类型异常");
    }
}

private Vector vector = new Vector();
private void BorderMove_PreviewMouseMove(object sender, MouseEventArgs e)
{
    if (sender is BasicControl basicControl && basicControl.ControlAttr.mouseObject.IsMouseDown)
    {
        Vector dp = e.GetPosition(this) - basicControl.ControlAttr.mouseObject.MouseDownPosition;
        //实现移动更加平滑
        if ((Math.Abs(vector.X - dp.X) >= 1 && Math.Abs(vector.Y - dp.Y) >= 1) || Math.Abs(vector.X - dp.X) > 1 || Math.Abs(vector.Y - dp.Y) > 1)
        {
            vector = dp;
            //每次鼠标按下的焦点为原点(0,0),往右是X+,往上是Y-
            double marginLeft = basicControl.ControlAttr.mouseObject.MouseDownMargin.Left + dp.X;
            double marginTop = basicControl.ControlAttr.mouseObject.MouseDownMargin.Top + dp.Y;
            //父容器自动往右、往下扩展
            marginLeft = marginLeft <= 0 ? 0 : marginLeft;
            if (marginLeft + basicControl.Width >= canvas.ActualWidth)
            {
                double offset = marginLeft + basicControl.Width - canvas.ActualWidth;
                canvas.Width = canvas.ActualWidth + offset;
                scroll.ScrollToHorizontalOffset(scroll.HorizontalOffset + offset);
            }
            else
            {
                canvas.Width = GetMaxWidth(canvas);
            }
            marginTop = marginTop <= 0 ? 0 : marginTop;
            if (marginTop + basicControl.Height >= canvas.ActualHeight)
            {
                double offset = marginTop + basicControl.Height - canvas.ActualHeight;
                canvas.Height = canvas.ActualHeight + offset;
                scroll.ScrollToVerticalOffset(scroll.VerticalOffset + offset);
            }
            else
            {
                canvas.Height = GetMaxHeight(canvas);
            }
            double marginRight = basicControl.ControlAttr.mouseObject.MouseDownMargin.Left + basicControl.ControlAttr.mouseObject.MouseDownMargin.Right - marginLeft;
            double marginBottom = basicControl.ControlAttr.mouseObject.MouseDownMargin.Top + basicControl.ControlAttr.mouseObject.MouseDownMargin.Bottom - marginTop;
            basicControl.Margin = new Thickness(marginLeft, marginTop, marginRight, marginBottom);
        }
    }
}

private void BorderMove_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    if (sender is BasicControl basicControl && basicControl.ControlAttr.mouseObject.IsMouseDown)
    {
        basicControl.ControlAttr.mouseObject.IsMouseDown = false;
        basicControl.ControlAttr.mouseObject.MouseDownMargin = basicControl.Margin;
        basicControl.ReleaseMouseCapture();
    }
}

private double GetMaxWidth(Canvas pan)
{
    Grid grid = (Grid)((ScrollViewer)pan.Parent).Parent;
    double maxWidth = grid.ActualWidth;
    foreach (var item in pan.Children)
    {
        if (item is BasicControl basicControl)
        {
            maxWidth = maxWidth >= (basicControl.Margin.Left + basicControl.Width) ? maxWidth : basicControl.Margin.Left + basicControl.Width;
        }
    }
    return maxWidth;
}
private double GetMaxHeight(Canvas pan)
{
    Grid grid = (Grid)((ScrollViewer)pan.Parent).Parent;
    double maxHeight = grid.ActualHeight;
    foreach (var item in pan.Children)
    {
        if (item is BasicControl basicControl)
        {
            maxHeight = maxHeight >= (basicControl.Margin.Top + basicControl.Height) ? maxHeight : basicControl.Margin.Top + basicControl.Height;
        }
    }
    return maxHeight;
}
#endregion

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

四、效果演示

在这里插入图片描述

五、源码链接

源码下载:基于流程图实现无代码编程业务逻辑(2)


总结

逻辑可视化,编程拖拉拽

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/511799
推荐阅读
相关标签
  

闽ICP备14008679号