WPF自定义控件,实现含有箭头的文本或内容控件

文章目录

    • 背景
    • 效果预览
    • 方案设计
    • 分析
      • 基本布局
      • 添加控件自定义属性
      • 添加属性值监听
      • 获取点数据
    • 全部代码
      • HorizontalLineContent.xaml
      • HorizontalLineContent.xaml.cs
      • DirectionAlignment.cs
      • ContentDirectionAlignment.cs
    • 使用方法

背景

因为项目开发需要,要在WPF上绘制TCP的交互过程图,所以现在需要一个箭头控件,并且在箭头中间还可以加入文字,所以开发了这个HorizontalLineContent控件,后续可能还要开发垂直版本。😁

在这里插入图片描述

效果预览

在这里插入图片描述

在这里插入图片描述

方案设计

一开始设计的实现逻辑其实是有两种的:

  1. Polygon x 1 + Label x 1
    然后通过设置层级Index,让文本控件覆盖在图形上,但是有个致命的缺点,就是文本控件不能设置背景颜色为透明色,不然图形就会和文字重叠,所以没有采用该方案。

  2. Polygon x 2 + Label x 1
    通过计算Label的渲染宽度,然后计算两边图形的点数据,然后绘制图形,虽然该方案比上一个要繁琐一点,但是效果会好很多,后面也可以方便扩展其它的属性,比如:整体旋转、单独图形旋转、单独文字旋转等。

我这里目前只考虑了如下几个要素:

  • 直线大小
  • 控件颜色,这里没有做Text和箭头颜色的分开
  • 文本位置
  • 箭头方向
  • 箭头宽度和高度

核心的几点逻辑是:

  1. 我分为了两个Polygon去绘制图形,一个在左边,一个在右边,通过设置箭头方向然后确定哪一个Polygon去绘制箭头,另一个去绘制直线。
  2. 文本控件在中间水平居中,通过监听自定义控件属性Text的变化,然后重新计算文本控件的宽度,然后绘制控件的点数据。
  3. 我这里将控件的Foreground属性绑定在了Polygon的Fill属性上,实现图形跟文字一起变色,如果需要单独控制文本和图形的颜色,可以拆开处理。

分析

基本布局

首先我们肯定是需要一个Label控件对文本内容进行显示,其次是需要一个能调整上中下位置的Grid,还需要两个Polygon去绘制图形。
所以基本样式就有了

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
    </Grid.RowDefinitions>
    <Polygon Grid.Row="1" x:Name="pol_left" VerticalAlignment="Center" Fill="Black" Panel.ZIndex="1">
        <Polygon.Points>
            <Point X="0" Y="0"></Point>
            <Point X="35" Y="0"></Point>
            <Point X="35" Y="4"></Point>
            <Point X="0" Y="4"></Point>
        </Polygon.Points>
    </Polygon>
    <Polygon Grid.Row="1" x:Name="pol_right" VerticalAlignment="Center" Fill="Black" Panel.ZIndex="1">
        <Polygon.Points>
            <Point X="65" Y="0"></Point>
            <Point X="100" Y="0"></Point>
            <Point X="100" Y="4"></Point>
            <Point X="65" Y="4"></Point>
        </Polygon.Points>
    </Polygon>
    <Label Grid.Row="1" x:Name="txt_text" 
           FontFamily="Consolas" Padding="5 0 5 0" 
           VerticalAlignment="Center" HorizontalAlignment="Center" Panel.ZIndex="2"></Label>
</Grid>

在设计器中的效果如下图:

在这里插入图片描述

添加控件自定义属性

// 内容
public new object Content
{
    get { return (string)GetValue(ContentProperty); }
    set { SetValue(ContentProperty, value); }
}

public static new readonly DependencyProperty ContentProperty =
    DependencyProperty.Register("Content", typeof(object), typeof(HorizontalLineContent), new PropertyMetadata(string.Empty));

// 箭头方向
public DirectionAlignment Direction
{
    get { return (DirectionAlignment)GetValue(DirectionProperty); }
    set { SetValue(DirectionProperty, value); }
}
public static readonly DependencyProperty DirectionProperty =
    DependencyProperty.Register("Direction", typeof(DirectionAlignment), typeof(HorizontalLineContent), new PropertyMetadata(DirectionAlignment.None));

// 文本位置
public ContentDirectionAlignment ContentDirection
{
    get { return (ContentDirectionAlignment)GetValue(ContentDirectionProperty); }
    set { SetValue(ContentDirectionProperty, value); }
}
public static readonly DependencyProperty ContentDirectionProperty =
    DependencyProperty.Register("ContentDirection", typeof(ContentDirectionAlignment), typeof(HorizontalLineContent), new PropertyMetadata(ContentDirectionAlignment.Center));

// 直线宽度
public double LineSize
{
    get { return (double)GetValue(LineSizeProperty); }
    set { SetValue(LineSizeProperty, value); }
}
public static readonly DependencyProperty LineSizeProperty =
    DependencyProperty.Register("LineSize", typeof(double), typeof(HorizontalLineContent), new PropertyMetadata(4.0));

// 箭头宽度
public double ArrowWidth
{
    get { return (double)GetValue(ArrowWidthProperty); }
    set { SetValue(ArrowWidthProperty, value); }
}
public static readonly DependencyProperty ArrowWidthProperty =
    DependencyProperty.Register("ArrowWidth", typeof(double), typeof(HorizontalLineContent), new PropertyMetadata(10.0));

// 箭头高度
public double ArrowHeight
{
    get { return (double)GetValue(ArrowHeightProperty); }
    set { SetValue(ArrowHeightProperty, value); }
}
public static readonly DependencyProperty ArrowHeightProperty =
    DependencyProperty.Register("ArrowHeight", typeof(double), typeof(HorizontalLineContent), new PropertyMetadata(12.0));

添加属性值监听

// 监听整体控件宽度属性变化
ActualWidthPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Label));
ActualWidthPropertyDescriptor.AddValueChanged(txt_text, (o, e) =>
{
    RefreshUI();
});

// 监听文本内容属性变化
ContentPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ContentProperty, typeof(HorizontalLineContent));
ContentPropertyDescriptor.AddValueChanged(this, (o, e) =>
{
    RefreshUI();
});

// 监听箭头方向属性变化
DirectionPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(DirectionProperty, typeof(HorizontalLineContent));
DirectionPropertyDescriptor.AddValueChanged(this, (o, e) =>
{
    RefreshUI();
});

// 监听文本位置属性变化
ContentDirectionPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ContentDirectionProperty, typeof(HorizontalLineContent));
ContentDirectionPropertyDescriptor.AddValueChanged(this, (o, e) =>
{
    RefreshUI();
});

// 监听直线宽度属性变化
LineSizePropertyDescriptor = DependencyPropertyDescriptor.FromProperty(LineSizeProperty, typeof(HorizontalLineContent));
LineSizePropertyDescriptor.AddValueChanged(this, (o, e) =>
{
    RefreshUI();
});

// 监听箭头高度属性变化
ArrowHeightPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ArrowHeightProperty, typeof(HorizontalLineContent));
ArrowHeightPropertyDescriptor.AddValueChanged(this, (o, e) =>
{
    RefreshUI();
});

// 监听箭头宽度属性变化
ArrowWidthPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ArrowWidthProperty, typeof(HorizontalLineContent));
ArrowWidthPropertyDescriptor.AddValueChanged(this, (o, e) =>
{
    RefreshUI();
});

获取点数据

public List<Point> GetPoints(DirectionAlignment direction, double left, double mid_left, double mid_right, double right)
{
    var points = new List<Point>();
    if (ContentDirection != ContentDirectionAlignment.Center && ContentDirection != ContentDirectionAlignment.None)
    {
        mid_left = mid_right = this.ActualWidth / 2.0;
    }
    var mid_width = this.ActualWidth / 2.0;
    var lineSize = LineSize < 1 ? 1 : LineSize;
    var arrowWidth = ArrowWidth < 5 ? 5 : ArrowWidth;
    var arrowHeight = ArrowHeight < 6 ? 6 : ArrowHeight;
    if (arrowHeight < lineSize)
    {
        arrowHeight = lineSize;
    }

    var lineSize_0 = 0; // 0
    var lineSize_1 = arrowHeight / 2.0 - lineSize / 2.0; // 4
    var lineSize_2 = arrowHeight / 2.0; // 6
    var lineSize_3 = arrowHeight / 2.0 + lineSize / 2.0; // 8
    var lineSize_4 = arrowHeight; // 12

    switch (direction)
    {
        case DirectionAlignment.None:
            // Left Points
            points.Add(new Point(0, lineSize_0));
            points.Add(new Point(mid_left, lineSize_0));
            points.Add(new Point(mid_left, lineSize));
            points.Add(new Point(0, lineSize));

            // Right Points
            points.Add(new Point(mid_right, lineSize_0));
            points.Add(new Point(right, lineSize_0));
            points.Add(new Point(right, lineSize));
            points.Add(new Point(mid_right, lineSize));
            break;
        case DirectionAlignment.Left:
            // Left Points
            points.Add(new Point(mid_left, lineSize_1));
            points.Add(new Point(arrowWidth, lineSize_1));
            points.Add(new Point(arrowWidth, lineSize_0));
            points.Add(new Point(0, lineSize_2));
            points.Add(new Point(arrowWidth, lineSize_4));
            points.Add(new Point(arrowWidth, lineSize_3));
            points.Add(new Point(mid_left, lineSize_3));

            // Right Points
            points.Add(new Point(mid_right, lineSize_0));
            points.Add(new Point(right, lineSize_0));
            points.Add(new Point(right, lineSize));
            points.Add(new Point(mid_right, lineSize));
            break;
        case DirectionAlignment.Right:
            // Left Points
            points.Add(new Point(0, lineSize_0));
            points.Add(new Point(mid_left, lineSize_0));
            points.Add(new Point(mid_left, lineSize));
            points.Add(new Point(0, lineSize));

            // Right Points
            points.Add(new Point(mid_right, lineSize_1));
            points.Add(new Point(right - arrowWidth, lineSize_1));
            points.Add(new Point(right - arrowWidth, lineSize_0));
            points.Add(new Point(right, lineSize_2));
            points.Add(new Point(right - arrowWidth, lineSize_4));
            points.Add(new Point(right - arrowWidth, lineSize_3));
            points.Add(new Point(mid_right, lineSize_3));
            break;
    }
    return points;
}

全部代码

共4个文件

HorizontalLineContent.xaml

<Window x:Class="WpfApp3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp3"
        mc:Ignorable="d" FontFamily="微软雅黑"
        Title="MainWindow" Height="450" Width="1024">
    <StackPanel Margin="20">
        <Grid>
            <TextBlock Grid.Row="0" HorizontalAlignment="Left" Text="客户端"></TextBlock>
            <TextBlock Grid.Row="0" HorizontalAlignment="Right" Text="服务端"></TextBlock>
        </Grid>
        <local:HorizontalLineContent ContentDirection="TopLeft" LineSize="2" ArrowHeight="6" ArrowWidth="5" Direction="Right" 
                                       Content="文本位置【Top】,直线宽度【2】,箭头方向【None(默认)】"></local:HorizontalLineContent>

        <local:HorizontalLineContent Margin="0 20 0 0" LineSize="4" ArrowHeight="12" ArrowWidth="10" Direction="Left" Foreground="Red" 
                                       Content="文本位置【None、Center】,直线宽度【4(默认)】,箭头方向【None】,ArrowHeight【12(默认)】,ArrowWidth【10(默认)】"></local:HorizontalLineContent>

        <local:HorizontalLineContent Margin="0 20 0 0" ContentDirection="Bottom" Direction="Right" Foreground="Green" 
                                       Content="文本位置【Bottom】,Foreground【Green】"></local:HorizontalLineContent>

        <local:HorizontalLineContent Margin="0 20 0 0" FontSize="16" ContentDirection="TopLeft" Direction="Right">
            <local:HorizontalLineContent.Content>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="All Default" VerticalAlignment="Center"></TextBlock>
                    <StackPanel Margin="10 0 0 0">
                        <Button Content="【ASN.1】"></Button>
                        <Line Height="5"></Line>
                        <Button Content="【证书】"></Button>
                    </StackPanel>
                </StackPanel>
            </local:HorizontalLineContent.Content>
        </local:HorizontalLineContent>
    </StackPanel>
</Window>

HorizontalLineContent.xaml.cs

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Shapes;

#region 文件版本信息
/*
 * 创建者:Zekang Hao
 * 邮箱:admin@haozekang.com
 * 创建时间:2024/6/27 15:11:07
 * 版本:1.0.0.0
 * 描述:HorizontalLineContent.xaml 的交互逻辑
 */
#endregion

namespace WpfApp3
{
    public partial class HorizontalLineContent : UserControl
    {
        private DependencyPropertyDescriptor ActualWidthPropertyDescriptor;
        private DependencyPropertyDescriptor ContentPropertyDescriptor;
        private DependencyPropertyDescriptor DirectionPropertyDescriptor;
        private DependencyPropertyDescriptor ContentDirectionPropertyDescriptor;
        private DependencyPropertyDescriptor LineSizePropertyDescriptor;
        private DependencyPropertyDescriptor ArrowWidthPropertyDescriptor;
        private DependencyPropertyDescriptor ArrowHeightPropertyDescriptor;

        public new object Content
        {
            get { return (string)GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }

        public static new readonly DependencyProperty ContentProperty =
            DependencyProperty.Register("Content", typeof(object), typeof(HorizontalLineContent), new PropertyMetadata(string.Empty));



        public DirectionAlignment Direction
        {
            get { return (DirectionAlignment)GetValue(DirectionProperty); }
            set { SetValue(DirectionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Direction.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DirectionProperty =
            DependencyProperty.Register("Direction", typeof(DirectionAlignment), typeof(HorizontalLineContent), new PropertyMetadata(DirectionAlignment.None));



        public ContentDirectionAlignment ContentDirection
        {
            get { return (ContentDirectionAlignment)GetValue(ContentDirectionProperty); }
            set { SetValue(ContentDirectionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TextDirection.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ContentDirectionProperty =
            DependencyProperty.Register("ContentDirection", typeof(ContentDirectionAlignment), typeof(HorizontalLineContent), new PropertyMetadata(ContentDirectionAlignment.Center));



        public double LineSize
        {
            get { return (double)GetValue(LineSizeProperty); }
            set { SetValue(LineSizeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LineSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LineSizeProperty =
            DependencyProperty.Register("LineSize", typeof(double), typeof(HorizontalLineContent), new PropertyMetadata(4.0));



        public double ArrowWidth
        {
            get { return (double)GetValue(ArrowWidthProperty); }
            set { SetValue(ArrowWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ArrowWidth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ArrowWidthProperty =
            DependencyProperty.Register("ArrowWidth", typeof(double), typeof(HorizontalLineContent), new PropertyMetadata(10.0));



        public double ArrowHeight
        {
            get { return (double)GetValue(ArrowHeightProperty); }
            set { SetValue(ArrowHeightProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ArrowHeight.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ArrowHeightProperty =
            DependencyProperty.Register("ArrowHeight", typeof(double), typeof(HorizontalLineContent), new PropertyMetadata(12.0));




        public HorizontalLineContent()
        {
            this.DataContext = this;

            InitializeComponent();

            txt_text.SetBinding(
                Label.ContentProperty,
                new Binding
                {
                    Path = new PropertyPath(nameof(Content)),
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                    Mode = BindingMode.OneWay
                }
            );

            txt_text.SetBinding(
                ForegroundProperty,
                new Binding
                {
                    Path = new PropertyPath(nameof(Foreground)),
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                    Mode = BindingMode.OneWay
                }
            );

            pol_left.SetBinding(
                Polygon.FillProperty,
                new Binding
                {
                    Path = new PropertyPath(nameof(Foreground)),
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                    Mode = BindingMode.OneWay
                }
            );

            pol_right.SetBinding(
                Polygon.FillProperty,
                new Binding
                {
                    Path = new PropertyPath(nameof(Foreground)),
                    UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                    Mode = BindingMode.OneWay
                }
            );

            ActualWidthPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Label));
            ActualWidthPropertyDescriptor.AddValueChanged(txt_text, (o, e) =>
            {
                RefreshUI();
            });

            ContentPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ContentProperty, typeof(HorizontalLineContent));
            ContentPropertyDescriptor.AddValueChanged(this, (o, e) =>
            {
                RefreshUI();
            });

            DirectionPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(DirectionProperty, typeof(HorizontalLineContent));
            DirectionPropertyDescriptor.AddValueChanged(this, (o, e) =>
            {
                RefreshUI();
            });

            ContentDirectionPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ContentDirectionProperty, typeof(HorizontalLineContent));
            ContentDirectionPropertyDescriptor.AddValueChanged(this, (o, e) =>
            {
                RefreshUI();
            });

            LineSizePropertyDescriptor = DependencyPropertyDescriptor.FromProperty(LineSizeProperty, typeof(HorizontalLineContent));
            LineSizePropertyDescriptor.AddValueChanged(this, (o, e) =>
            {
                RefreshUI();
            });

            ArrowHeightPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ArrowHeightProperty, typeof(HorizontalLineContent));
            ArrowHeightPropertyDescriptor.AddValueChanged(this, (o, e) =>
            {
                RefreshUI();
            });

            ArrowWidthPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ArrowWidthProperty, typeof(HorizontalLineContent));
            ArrowWidthPropertyDescriptor.AddValueChanged(this, (o, e) =>
            {
                RefreshUI();
            });
        }

        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            RefreshUI();
        }

        private void RefreshUI()
        {
            if (pol_left == null || pol_right == null || txt_text == null)
            {
                return;
            }
            double control_middle = this.ActualWidth / 2.0;
            double txt_width_middle = txt_text.ActualWidth / 2.0;
            double mid_left = control_middle - txt_width_middle;
            double mid_right = control_middle + txt_width_middle;
            if (Direction != DirectionAlignment.None && ContentDirection == ContentDirectionAlignment.Center)
            {
                if (mid_left < 10)
                {
                    pol_left.Visibility = Visibility.Collapsed;
                    pol_right.Visibility = Visibility.Collapsed;
                }
                else
                {
                    pol_left.Visibility = Visibility.Visible;
                    pol_right.Visibility = Visibility.Visible;
                }
            }
            switch (ContentDirection)
            {
                case ContentDirectionAlignment.None:
                case ContentDirectionAlignment.Center:
                    txt_text.Padding = new Thickness(0, 0, 0, 0);
                    txt_text.SetValue(Grid.RowProperty, 1);
                    break;
                case ContentDirectionAlignment.Top:
                case ContentDirectionAlignment.TopLeft:
                case ContentDirectionAlignment.TopRight:
                    if (Direction == DirectionAlignment.None)
                    {
                        txt_text.Padding = new Thickness(0, 0, 0, 4);
                    }
                    else
                    {
                        txt_text.Padding = new Thickness(0, 0, 0, 0);
                    }
                    txt_text.SetValue(Grid.RowProperty, 0);
                    break;
                case ContentDirectionAlignment.Bottom:
                case ContentDirectionAlignment.BottomLeft:
                case ContentDirectionAlignment.BottomRight:
                    if (Direction == DirectionAlignment.None)
                    {
                        txt_text.Padding = new Thickness(0, 4, 0, 0);
                    }
                    else
                    {
                        txt_text.Padding = new Thickness(0, 0, 0, 0);
                    }
                    txt_text.SetValue(Grid.RowProperty, 2);
                    break;
            }
            switch (ContentDirection)
            {
                case ContentDirectionAlignment.TopLeft:
                case ContentDirectionAlignment.BottomLeft:
                    txt_text.HorizontalAlignment = HorizontalAlignment.Left;
                    break;
                case ContentDirectionAlignment.TopRight:
                case ContentDirectionAlignment.BottomRight:
                    txt_text.HorizontalAlignment = HorizontalAlignment.Right;
                    break;
                default:
                    txt_text.HorizontalAlignment = HorizontalAlignment.Center;
                    break;
            }
            pol_left.Points.Clear();
            pol_right.Points.Clear();
            var points = GetPoints(Direction, 0, mid_left, mid_right, this.ActualWidth);
            if (Direction == DirectionAlignment.None)
            {
                for ( int i = 0; i < 4; i++)
                {
                    pol_left.Points.Add(points[i]);
                }
                for ( int i = 4; i < 8; i++)
                {
                    pol_right.Points.Add(points[i]);
                }
            }
            else if (Direction == DirectionAlignment.Left)
            {
                for (int i = 0; i < 7; i++)
                {
                    pol_left.Points.Add(points[i]);
                }
                for (int i = 7; i < 11; i++)
                {
                    pol_right.Points.Add(points[i]);
                }
            }
            else if(Direction == DirectionAlignment.Right)
            {
                for (int i = 0; i < 4; i++)
                {
                    pol_left.Points.Add(points[i]);
                }
                for (int i = 4; i < 11; i++)
                {
                    pol_right.Points.Add(points[i]);
                }
            }
        }

        public List<Point> GetPoints(DirectionAlignment direction, double left, double mid_left, double mid_right, double right)
        {
            var points = new List<Point>();
            if (ContentDirection != ContentDirectionAlignment.Center && ContentDirection != ContentDirectionAlignment.None)
            {
                mid_left = mid_right = this.ActualWidth / 2.0;
            }
            var mid_width = this.ActualWidth / 2.0;
            var lineSize = LineSize < 1 ? 1 : LineSize;
            var arrowWidth = ArrowWidth < 5 ? 5 : ArrowWidth;
            var arrowHeight = ArrowHeight < 6 ? 6 : ArrowHeight;
            if (arrowHeight < lineSize)
            {
                arrowHeight = lineSize;
            }

            var lineSize_0 = 0; // 0
            var lineSize_1 = arrowHeight / 2.0 - lineSize / 2.0; // 4
            var lineSize_2 = arrowHeight / 2.0; // 6
            var lineSize_3 = arrowHeight / 2.0 + lineSize / 2.0; // 8
            var lineSize_4 = arrowHeight; // 12

            switch (direction)
            {
                case DirectionAlignment.None:
                    // Left Points
                    points.Add(new Point(0, lineSize_0));
                    points.Add(new Point(mid_left, lineSize_0));
                    points.Add(new Point(mid_left, lineSize));
                    points.Add(new Point(0, lineSize));

                    // Right Points
                    points.Add(new Point(mid_right, lineSize_0));
                    points.Add(new Point(right, lineSize_0));
                    points.Add(new Point(right, lineSize));
                    points.Add(new Point(mid_right, lineSize));
                    break;
                case DirectionAlignment.Left:
                    // Left Points
                    points.Add(new Point(mid_left, lineSize_1));
                    points.Add(new Point(arrowWidth, lineSize_1));
                    points.Add(new Point(arrowWidth, lineSize_0));
                    points.Add(new Point(0, lineSize_2));
                    points.Add(new Point(arrowWidth, lineSize_4));
                    points.Add(new Point(arrowWidth, lineSize_3));
                    points.Add(new Point(mid_left, lineSize_3));

                    // Right Points
                    points.Add(new Point(mid_right, lineSize_0));
                    points.Add(new Point(right, lineSize_0));
                    points.Add(new Point(right, lineSize));
                    points.Add(new Point(mid_right, lineSize));
                    break;
                case DirectionAlignment.Right:
                    // Left Points
                    points.Add(new Point(0, lineSize_0));
                    points.Add(new Point(mid_left, lineSize_0));
                    points.Add(new Point(mid_left, lineSize));
                    points.Add(new Point(0, lineSize));

                    // Right Points
                    points.Add(new Point(mid_right, lineSize_1));
                    points.Add(new Point(right - arrowWidth, lineSize_1));
                    points.Add(new Point(right - arrowWidth, lineSize_0));
                    points.Add(new Point(right, lineSize_2));
                    points.Add(new Point(right - arrowWidth, lineSize_4));
                    points.Add(new Point(right - arrowWidth, lineSize_3));
                    points.Add(new Point(mid_right, lineSize_3));
                    break;
            }
            return points;
        }
    }
}

DirectionAlignment.cs

#region 文件版本信息
/*
 * 创建者:Zekang Hao
 * 邮箱:admin@haozekang.com
 * 创建时间:2024/6/27 17:01:16
 * 版本:1.0.0.0
 * 描述:
 */
#endregion

namespace WpfApp3
{
    /// <summary>
    /// 描述
    /// </summary>
    public enum DirectionAlignment
    {
        None,
        Left,
        Right,
    }
}

ContentDirectionAlignment.cs

#region 文件版本信息
/*
 * 创建者:Zekang Hao
 * 邮箱:admin@haozekang.com
 * 创建时间:2024/6/28 11:17:07
 * 版本:1.0.0.0
 * 描述:
 */
#endregion

namespace WpfApp3
{
    /// <summary>
    /// 描述
    /// </summary>
    public enum ContentDirectionAlignment
    {
        None,
        Top,
        Center,
        Bottom,
        TopLeft,
        TopRight,
        BottomLeft,
        BottomRight,
    }
}

使用方法

<Window x:Class="WpfApp3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp3"
        mc:Ignorable="d" FontFamily="微软雅黑"
        Title="MainWindow" Height="450" Width="1024">
    <StackPanel Margin="20">
        <Grid>
            <TextBlock Grid.Row="0" HorizontalAlignment="Left" Text="客户端"></TextBlock>
            <TextBlock Grid.Row="0" HorizontalAlignment="Right" Text="服务端"></TextBlock>
        </Grid>
        <local:HorizontalLineContent ContentDirection="TopLeft" LineSize="2" ArrowHeight="6" ArrowWidth="5" Direction="Right" 
                                     Content="文本位置【Top】,直线宽度【2】,箭头方向【None(默认)】"></local:HorizontalLineContent>

        <local:HorizontalLineContent Margin="0 20 0 0" LineSize="4" ArrowHeight="12" ArrowWidth="10" Direction="Left" Foreground="Red" 
                                     Content="文本位置【None、Center】,直线宽度【4(默认)】,箭头方向【None】,ArrowHeight【12(默认)】,ArrowWidth【10(默认)】"></local:HorizontalLineContent>

        <local:HorizontalLineContent Margin="0 20 0 0" ContentDirection="Bottom" Direction="Right" Foreground="Green" 
                                     Content="文本位置【Bottom】,Foreground【Green】"></local:HorizontalLineContent>

        <local:HorizontalLineContent Margin="0 20 0 0" FontSize="16" ContentDirection="TopLeft" Direction="Right">
            <local:HorizontalLineContent.Content>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="All Default" VerticalAlignment="Center"></TextBlock>
                    <StackPanel Margin="10 0 0 0">
                        <Button Content="【ASN.1】"></Button>
                        <Line Height="5"></Line>
                        <Button Content="【证书】"></Button>
                    </StackPanel>
                </StackPanel>
            </local:HorizontalLineContent.Content>
        </local:HorizontalLineContent>
    </StackPanel>
</Window>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/764519.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue选择上下周,拖拽列表,随机背景色

安装拖拽插件 npm install vuedraggable <template><!--排产计划--><div class"app-container"><div class"mainbox"><div class"table-container table-fullscreen"><div class"title-name">…

打假“AI换脸”,外滩大会·全球Deepfake攻防挑战赛启动报名

近日&#xff0c;外滩大会全球Deepfake攻防挑战赛正式启动报名。该赛事提供百万级的数据集&#xff0c;针对“AI换脸”的欺诈风险进行攻防实战演练&#xff0c;并设立100万元人民币的奖金池&#xff0c;鼓励推动AI向善的技术人才。 大赛由蚂蚁集团主办、蚂蚁数科承办&#xff0…

JeecgFlow定时器

概念 定时器事件&#xff08;Timer Events&#xff09;是由定义的计时器触发的事件。它们可以用作启动事件、中间事件或边界事件。边界事件可以中断&#xff0c;也可以不中断。 Camunda定时器事件包括&#xff1a;Timer Start Event&#xff08;定时启动事件&#xff09;、Time…

Unity解决报错:Execution failed for task ‘:unityLibrary:BuildIl2CppTask‘

目录 编辑器版本2020.3.33f1 及 2021.3.15f1 直接导出apk或aar报错(虽然会自动生成temp的AS工程&#xff0c;经过打开验证 也是无解的)&#xff1b; 唯一解决办法&#xff1a;Unity导出As工程没问题&#xff1b; 编辑器版本2020.3.33f1 及 2021.3.15f1 直接导出apk或aar报…

野外/工地车流计数摄像头,单人即可安装,简单低成本

在野外或工地这样的特殊环境中&#xff0c;对车流进行准确计数对于交通管理、资源调配以及安全保障都具有重要意义。而野外/工地车流计数摄像头的出现&#xff0c;以其单人即可安装、简单低成本的特点&#xff0c;为解决这些场景中的车流统计问题提供了理想的解决方案。 一、野…

《Linux开发笔记》C语言编译过程

C语言编译过程 编译过程主要分为四步&#xff1a;预处理、编译、汇编、链接 预处理&#xff1a;主要用于查找头文件、展开宏 编译&#xff1a;把.i文件编译成.s文件 汇编&#xff1a;把.s文件汇编为.o文件 链接&#xff1a;把多个.o文件链接成一个app 以上四个步骤主要由3个命…

外贸企业选择什么网络?

随着全球化的深入发展&#xff0c;越来越多的国内企业将市场拓展到海外。为了确保外贸业务的顺利进行&#xff0c;企业需要建立一个稳定、安全且高速的网络。那么&#xff0c;外贸企业应该选择哪种网络呢&#xff1f;本文将为您详细介绍。 外贸企业应选择什么网络&#xff1f; …

d3dx9_43.dll丢失怎么解决?d3dx9_43.dll怎么安装详细教程

在使用计算机中&#xff0c;如果遇到d3dx9_43.dll丢失或许找不到d3dx9_43.dll无法运行打开软件怎么办&#xff1f;这个是非常常见问题&#xff0c;下面我详细介绍一下d3dx9_43.dll是什么文件与d3dx9_43.dll的各种问题以及d3dx9_43.dll丢失的多个解决方法&#xff01; 一、d3dx9…

四川赤橙宏海商务信息咨询有限公司是真的吗?

在数字经济的浪潮下&#xff0c;电商行业日新月异&#xff0c;各种创新模式层出不穷。其中&#xff0c;抖音电商以其独特的社交属性和短视频传播优势&#xff0c;迅速崛起成为电商领域的一匹黑马。在这个风起云涌的市场中&#xff0c;四川赤橙宏海商务信息咨询有限公司凭借其专…

AI网络爬虫004:从东方财富网批量获取上市公司的全部新闻资讯

文章目录 一、目标二、输入内容三、输出内容一、目标 用户输入一个上市公司名称,然后程序自动从东方财富网批量获取上市公司的全部新闻资讯 查看相关元素在源代码中的位置: 新闻标题:<a href="http://finance.eastmoney.com/a/202405233084538683.html" targ…

vue3 elementplus Springboot 课程购买系统案例源码

系统演示 项目获取地址 Springboot vue3 elementplus 课程购买系统案例源码 附带系统演示&#xff0c;环境搭建教程,开发工具 技术栈:SpringBoot Vue3 ElementPlus MybatisPlus 开发工具:idea 后端构建工具:Maven 前端构建工具:vite 运行环境:Windows Jdk版本:1.8 Nod…

云数据中心运维新纪元:让Linux服务器如虎添翼

文章目录 一、Linux系统管理的高级技巧1. 性能调优与监控&#xff1a;2. 自动化与脚本编写&#xff1a;3. 文件系统与存储管理&#xff1a; 二、服务器配置优化的策略1. 硬件选型与配置&#xff1a;2. 网络配置与优化&#xff1a;3. 应用部署与调优&#xff1a; 三、安全策略的…

Pytest+Allure+Yaml+PyMsql+Jenkins+Gitlab接口自动化(四)Jenkins配置

一、背景 Jenkins&#xff08;本地宿主机搭建&#xff09; 拉取GitLab(服务器)代码到在Jenkins工作空间本地运行并生成Allure测试报告 二、框架改动点 框架主运行程序需要先注释掉运行代码&#xff08;可不改&#xff0c;如果运行报allure找不到就直接注释掉&#xff09; …

Nacos 2.x 系列【19】元数据管理

文章目录 1. 概述2. 元数据管理2.1 服务元数据2.2 实例元数据2.2.1 控制台2.2.2 客户端2.2.3 Open API 1. 概述 元信息&#xff1a;Nacos数据&#xff08;如配置和服务&#xff09;描述信息&#xff0c;如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签 (…

大数据之Zookeeper部署

文章目录 集群规划环境准备集群部署参考资料 集群规划 确定使用Hadoop101、hadoop102和hadoop103三台服务器来构建Zookeeper集群。 hadoop101hadoop102hadoop103zookeeperzookeeperzookeeper 环境准备 安装zookeeper前需要确保下面的环境配置成功&#xff0c;具体可以参考大…

DSPy:变革式大模型应用开发

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径&#xff1a;AI代理工作流大模型应用开发实用开源项目汇总大模…

DX-11A信号继电器 0.5A 柜内板前接线 约瑟JOSEF

DX-11,11A,11B,11C型信号继电器 DX-11信号继电器 DX-11B信号继电器 DX-11A信号继电器 DX-11C信号继电器 1 用途 该继电器用于直流操作的保护线路中&#xff0c;作为信号指示器。 2 结构和原理 该继电器具有电磁铁和带公共点的三付动合触点及一个信号牌&#xff0c;为电…

【哈尔滨等保测评标准解析】

哈尔滨信息安全等级保护测评&#xff08;等保测评&#xff09;标准解析如下&#xff1a; 一、总体概述 哈尔滨市在进行等保测评时&#xff0c;遵循国家统一的标准框架&#xff0c;并结合本省的实际情况&#xff0c;形成了具有地方特色的安全防护模式。等保测评的主要目的是确…

笔记101:OSQP求解器的底层算法 -- ADMM算法

前言1&#xff1a;这篇博客仅限于介绍拉格朗日乘子法&#xff0c;KKT条件&#xff0c;ALM算法&#xff0c;ADMM算法等最优化方法的使用以及简版代码实现&#xff0c;但不会涉及具体的数学推导&#xff1b;不过在下面我会给出具体数学推导的相关文章和截图&#xff0c;供学有余力…

数据结构_1.0

一、数据结构概述 1.1 概念 在计算机科学中&#xff0c;数据结构是一种数据组织、管理和存储的格式 。它是相互之间存在一种或多种特定关系的数据元素的集合。通常情况下&#xff0c;精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技…