C#开发AGV地图编辑软件(二)

AGV地图编辑软件 ------ 一步一步开始开发之主界面设计代码

概述

本次写博客采取总分总的模式,也不知大家适不适应,前篇我们介绍了AGV地图编辑软件的大概功能,接下来我们来介绍如何用代码来实现。写博客主要是为了可以认识更多优秀的志趣相投的人,共同学习、进步。

主界面设计

先来看下主画面的布局,都是基础控件,就不在此进行细述了,见下面图片说明:
在这里插入图片描述

首先,我们需要在上图的Panel中添加一个我们自己需要的自定义组件,在项目中新建一个用户自定义控件的文件夹,命名为AGVMapUControl(至于取什么名字你们随意)
在这里插入图片描述
接着,我们右键该文件夹,选择添加,然后添加一个组件,命名为:MapPanel,按F7进入代码视图,让该组件继承自控件Panel

public partial class MapPanel : Panel

为了减小组件重绘时的闪烁,我们可以在构造函数中开启双缓存

 public MapPanel()
 {
     InitializeComponent();
     SetStyle(ControlStyles.OptimizedDoubleBuffer, true);//先在缓存区绘制,而不是直接绘制到界面上,减少闪烁
     SetStyle(ControlStyles.AllPaintingInWmPaint, true);
     SetStyle(ControlStyles.UserPaint, true);//设置自行绘制,而不是系统绘制
     SetStyle(ControlStyles.DoubleBuffer, true);//双缓存开启
     this.UpdateStyles();
}

接着我们重写控件的几个事件,重绘事件:用于控件的刷新;鼠标点击事件:当有站点、停车充电点
、直线路径、贝塞尔曲线路径选中进行标记为其他颜色,属性框内对应的属性跳转为选中的单元的各个属性值;鼠标移动事件:当鼠标按下并且进行移动时,绘制拖拽框,类似于windows自带的绘图软件的拖拽框;鼠标抬起事件:确定拖拽框最终的大小

#region 重绘事件
protected override void OnPaint(PaintEventArgs pe)
{
    base.OnPaint(pe);
    var g = pe.Graphics;
}
#endregion

#region 鼠标点击时,选中其中一条直线或者曲线则将其他的全部取消标记
protected override void OnMouseClick(MouseEventArgs e)
{
    base.OnMouseClick(e);
    Focus();
    Invalidate();
}
#endregion

#region 拖拽框变量
/// <summary>
/// 记录移动位置
/// </summary>
private Point MouseStartPoint;
/// <summary>
/// 拖选坐标
/// </summary>
private Point MouseEndPoint;
/// <summary>
/// 拖放矩形
/// </summary>
private Rectangle DragRectangle;
/// <summary>
/// 判断鼠标是否按下
/// </summary>
bool isMouseDown = false;
#endregion

#region 鼠标按下事件,用于绘制拖动框
protected override void OnMouseDown(MouseEventArgs e)
{
    MouseStartPoint = e.Location;   //记录鼠标左键按下时位置
    MouseEndPoint = e.Location;     //初始化结束位置
    isMouseDown = true;		//鼠标按下了
    base.OnMouseDown(e);
}
#endregion

#region 鼠标移动时,画一个矩形框
protected override void OnMouseMove(MouseEventArgs e)
{
    base.OnMouseMove(e);
    MouseEndPoint = e.Location;//确定拖动框的终点坐标
    if (e.Button == MouseButtons.Left)
    {
        if (isMouseDown)
        {
            if (!DragRectangle.Size.IsEmpty)
            {
                #region 计算拖拽边框的大小
                var moveX = e.Location.X - MouseStartPoint.X;
                var moveY = e.Location.Y - MouseStartPoint.Y;
                if (moveX < 0)
                {
                    DragRectangle.X = MouseStartPoint.X + moveX;
                    DragRectangle.Width = Math.Abs(moveX);
                }
                else
                {
                    DragRectangle.X = MouseStartPoint.X;
                    DragRectangle.Width = Math.Abs(moveX);
                }
                if (moveY < 0)
                {
                    DragRectangle.Y = MouseStartPoint.Y + moveY;
                    DragRectangle.Height = Math.Abs(moveY);
                }
                else
                {
                    DragRectangle.Y = MouseStartPoint.Y;
                    DragRectangle.Height = Math.Abs(moveY);
                }
                #endregion
                        
                #region 画拖拽矩形框
                var p = new Pen(Color.DeepPink, 1);
                p.DashStyle = DashStyle.Solid;
                CreateGraphics().DrawRectangle(p, DragRectangle);
                if (point != MouseEndPoint)
                {
                    point = MouseEndPoint;
                    Invalidate();
               }
               #endregion
			}
		}
	}
	else
	{	
         DragRectangle.Width = 0;
         DragRectangle.Height = 0;
	}
}
#endregion

#region 鼠标抬起事件,用于确定绘制拖动框的大小,判断框内是否有站点/停车充电点被选中
protected override void OnMouseUp(MouseEventArgs e)
{
     if (e.Button == MouseButtons.Left)
     {
         isMouseDown = false;//鼠标按下标志位设置为false
         if (DragRectangle.IsEmpty)
         {
             return;
         }
     }
     //---重置绘画的边框高度和宽度
     DragRectangle.Width = 0;
     DragRectangle.Height = 0;
     base.OnMouseUp(e);		
}
#endregion

#region 鼠标移出时
protected override void OnMouseLeave(EventArgs e)
{
    base.OnMouseLeave(e);
}
#endregion

至此,已经将控件的事件进行了重写声明,接着,我们需要绑定各种属性,我们可以先添加一个基类,先添加一个文件夹,命名为Property,然后右键该文件夹->添加->类,命名为:PropertyBase,让他继承于 ICustomTypeDescriptor(为对象提供动态自定义类型信息的接口)
在这里插入图片描述
然后我们显示实现对应的接口方法,具体代码详见下面

public class PropertyBase : ICustomTypeDescriptor
{
        #region ICustomTypeDescriptor 显式接口定义
        /// <summary>
        /// 为指定组件返回特性集合
        /// </summary>
        /// <returns>具有组件的特性的 System.ComponentModel.AttributeCollection。 如果组件为 null,则此方法返回一个空集合。</returns>
        AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this, true);
        }
        /// <summary>
        /// 使用自定义类型描述符返回指定组件的类的名称。
        /// </summary>
        /// <returns>一个包含指定组件的类的名称的 System.String。</returns>
        string ICustomTypeDescriptor.GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }
        /// <summary>
        /// 使用自定义类型描述符返回指定组件的名称。
        /// </summary>
        /// <returns>为指定组件的类名,或者,如果不存在任何组件名,则为 null。</returns>
        string ICustomTypeDescriptor.GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }
        /// <summary>
        /// 为具有自定义类型描述符的指定组件类型返回一个类型转换器。
        /// </summary>
        /// <returns>指定组件的 System.ComponentModel.TypeConverter。</returns>
        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }
        /// <summary>
        /// 返回具有自定义类型描述符的组件的默认事件。
        /// </summary>
        /// <returns>带有默认事件的 System.ComponentModel.EventDescriptor;如果没有事件,则为 null。</returns>
        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }
        /// <summary>
        /// 返回指定类型组件的默认属性。
        /// </summary>
        /// <returns>具有默认属性的 System.ComponentModel.PropertyDescriptor;如果没有属性,则为 null。</returns>
        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
        {
            return null;
        }
        /// <summary>
        /// 为指定组件返回具有指定基类型和自定义类型描述符的编辑器。
        /// </summary>
        /// <param name="editorBaseType">表示要查找的编辑器的基类型的 System.Type。</param>
        /// <returns>可转换为指定编辑器类型的编辑器的一个实例,如果找不到请求类型的编辑器,则为 null。</returns>
        object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }
        /// <summary>
        /// 对具有自定义类型描述符的指定组件,返回事件集合。
        /// </summary>
        /// <returns>具有此组件的事件的 System.ComponentModel.EventDescriptorCollection。</returns>
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }
        /// <summary>
        /// 通过使用指定的属性数组作为筛选器,并使用自定义类型描述符来返回指定组件的事件集合。
        /// </summary>
        /// <param name="attributes">要用作筛选器的类型 System.Attribute 数组。</param>
        /// <returns>具有匹配此组件指定属性的事件的 System.ComponentModel.EventDescriptorCollection。</returns>
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }
        /// <summary>
        /// 使用特性数组作为筛选器,返回此组件实例的属性。
        /// </summary>
        /// <returns>一个 System.ComponentModel.PropertyDescriptorCollection,它表示此组件实例的已筛选属性。</returns>
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="attributes"></param>
        /// <returns></returns>
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
        {
            ArrayList props = new ArrayList();
            Type thisType = GetType();
            PropertyInfo[] pis = thisType.GetProperties();
            foreach (PropertyInfo p in pis)
            {
                if (p.DeclaringType == thisType || p.PropertyType.ToString() == "System.Drawing.Color")
                {
                    //判断属性是否显示
                    BrowsableAttribute Browsable = (BrowsableAttribute)Attribute.GetCustomAttribute(p, typeof(BrowsableAttribute));
                    if (Browsable != null)
                    {
                        if (Browsable.Browsable || p.PropertyType.ToString() == "System.Drawing.Color")
                        {
                            PropertyStub psd = new PropertyStub(p, attributes);
                            props.Add(psd);
                        }
                    }
                    else
                    {
                        PropertyStub psd = new PropertyStub(p, attributes);
                        props.Add(psd);
                    }
                }
            }
            PropertyDescriptor[] propArray = (PropertyDescriptor[])props.ToArray(typeof(PropertyDescriptor));
            return new PropertyDescriptorCollection(propArray);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="pd"></param>
        /// <returns></returns>
        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }
        #endregion
}

再接着我们在此类中,再生命一个类PropertyStub,让它继承于PropertyDescriptor(实现类上的属性抽象化,可以声明各种抽象概念的属性)具体详见下面代码,每个方法都有注明用途:

public class PropertyStub : PropertyDescriptor
{
        /// <summary>
        /// 发现属性 (Property) 的属性 (Attribute) 并提供对属性 (Property) 元数据的访问。
        /// </summary>
        readonly PropertyInfo info;
        /// <summary>
        /// 提供类的属性的抽象。
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <param name="attrs"></param>
        public PropertyStub(PropertyInfo propertyInfo, Attribute[] attrs)
            : base(propertyInfo.Name, attrs)
        {
            info = propertyInfo;
        }
        /// <summary>
        /// 获取用于获取此实例的类对象 MemberInfo。
        /// </summary>
        public override Type ComponentType
        {
            get { return info.ReflectedType; }
        }
        /// <summary>
        /// 获取一个值,该值指示此属性是否只读。
        /// </summary>
        public override bool IsReadOnly
        {
            get
            {
                if (info != null)
                {
                    MapControlAttibute uicontrolattibute = (MapControlAttibute)Attribute.GetCustomAttribute(info, typeof(MapControlAttibute));
                    if (uicontrolattibute != null)
                        return uicontrolattibute.IsReadOnly;
                }
                return info.CanWrite == false;
            }
        }
        /// <summary>
        /// 获取此属性的类型。
        /// </summary>
        public override Type PropertyType
        {
            get { return info.PropertyType; }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="component"></param>
        /// <returns></returns>
        public override bool CanResetValue(object component)
        {
            return false;
        }
        /// <summary>
        /// 用索引化属性的可选索引值返回指定对象的该属性值。
        /// </summary>
        /// <param name="component">将返回其属性值的对象。</param>
        /// <returns>指定对象的属性值。</returns>
        public override object GetValue(object component)
        {
            //Console.WriteLine("GetValue: " + component.GetHashCode());
            try
            {
                return info.GetValue(component, null);
            }
            catch
            {
                return null;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="component"></param>
        public override void ResetValue(object component)
        {
        }
        /// <summary>
        /// 用索引化属性的可选索引值设置指定对象的该属性值。
        /// </summary>
        /// <param name="component">将设置其属性值的对象。</param>
        /// <param name="value">新的属性值。</param>
        public override void SetValue(object component, object value)
        {
            //Console.WriteLine("SetValue: " + component.GetHashCode());
            info.SetValue(component, value, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="component"></param>
        /// <returns></returns>
        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }

        /// <summary>
        /// 通过重载下面这个属性,可以将属性在PropertyGrid中的显示设置成中文
        /// </summary>
        public override string DisplayName
        {
            get
            {
                if (info != null)
                {
                    MapControlAttibute uicontrolattibute = (MapControlAttibute)Attribute.GetCustomAttribute(info, typeof(MapControlAttibute));
                    if (uicontrolattibute != null)
                        return uicontrolattibute.PropertyName;
                    else
                    {
                        return info.Name;
                    }
                }
                else
                    return "";
            }
        }
}

自此,我们的属性基类已经定义完成,接下来,我们就可以开始定义我们自己主画面背景所需要的属性,比如背景颜色,背景大小,名称等…后续如果我们需要定义更多的其他属性,只需要在原来的基础上进行属性拓展即可。

我们现在开始声明背景的属性,我们添加一个类,命名为:MapPanelProperty,让他继承于PropertyBase

    public class MapPanelProperty : PropertyBase
    {
        /// <summary>
        /// 需要显示属性的控件
        /// </summary>
        private readonly MapPanel MapControl;
        /// <summary>
        /// 初始化
        /// </summary>
        public MapPanelProperty()
        {
        }
        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="MapControl">需要显示属性的控件</param>
        public MapPanelProperty(MapPanel MapControl)
        {
            this.MapControl = MapControl;
        }

        [MapControlAttibute("地图名称", "地图名称", "", true)]
        public string MapName
        {
            get { return MapControl.Name; }
            set
            {
                MapControl.Name = value;
            }
        }
        [MapControlAttibute("地图背景色", "地图背景色", "", false)]
        public Color BackColor
        {
            get { return MapControl.BackColor; }
            set
            {
                MapControl.BackColor = value;
            }
        }
        [MapControlAttibute("地图大小", "地图大小", "", false)]
        public Size Size
        {
            get { return MapControl.Size; }
            set { MapControl.Size = value; }
        }
    }

由于颜色、名称、大小是控件基础属性,所以我们不需要在此委托和声明相应的事件,比如贝塞尔曲线的控制点坐标的变换,当触发事件时进行相应的操作(后面会介绍)。

最后,我们在工具当中就可以看到自己定义的组件(同我们平时Winform添加普通控件一样),将该组件添加到主画面当中,接着我们来看看选中背景时的效果,我们可以看到我们自己声明的属性以及对应的值,当更改其中任何一个属性值(对应的我们上面声明的类MapPanelProperty中的属性名)主页面背景会跟着变化(颜色或者大小等):
在这里插入图片描述

结尾

到这里我们的主背景页面的属性已经绑定完成了,下一节我们将站点的编辑(添加、移动、删除、保存、属性值绑定等操作)。

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工控程序狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值