// *********************************************************************** // Assembly : Construction // Author : flythink // Created : 06-05-2020 // // Last Modified By : flythink // Last Modified On : 09-01-2020 // *********************************************************************** // // Copyright (c) jindongfang. All rights reserved. // // // *********************************************************************** namespace WellWorkDataUI.CustomControls { using DevExpress.Diagram.Core; using DevExpress.Diagram.Core.Native; using DevExpress.Utils; using DevExpress.Utils.Svg; using DevExpress.XtraDiagram; using Fk.Utils; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; /// /// 导入数据时匹配列控件 /// public partial class MatchColumnControl : DevExpress.XtraEditors.XtraUserControl { private static readonly Color[] Colors = new Color[] { Color.FromArgb(243, 159, 68), //Color.FromArgb(255, 131, 96), Color.FromArgb(125, 206, 130), //Color.FromArgb(106, 15, 73), Color.FromArgb(0, 255, 245), //Color.FromArgb(254, 95, 85), Color.FromArgb(13, 59, 102), //Color.FromArgb(240, 182, 127), Color.FromArgb(244, 211, 94), //Color.FromArgb(238, 150, 75), Color.FromArgb(180, 122, 234), }; private int colorIndex = 0; private DiagramContainer fromContainer; private Orientation orientation = Orientation.Vertical; private SlowDownTimer timer = new SlowDownTimer(200); private DiagramContainer toContainer; private CustomDiagramShape lastSelection; /// /// Initializes a new instance of the class. /// public MatchColumnControl() { this.InitializeComponent(); // diagramControl1.OptionsView.ScrollMargin = new Padding(Int32.MaxValue); this.diagramControl.Margin = new Padding(0); this.diagramControl.Padding = new Padding(0); this.diagramControl.AddingNewItem += this.DiagramControl1_AddingNewItem; this.diagramControl.ItemsDeleting += this.DiagramControl1_ItemsDeleting; this.diagramControl.ConnectionChanged += this.DiagramControl1_ConnectionChanged; this.diagramControl.OptionsBehavior.ActiveTool = new CustomDiagramTool(); this.diagramControl.SelectionChanged += this.DiagramControl_SelectionChanged; this.CreateContainer(); } private void DiagramControl_SelectionChanged(object sender, DiagramSelectionChangedEventArgs e) { if (this.diagramControl.SelectedItems.FirstOrDefault() is CustomDiagramShape shape) { if (shape.IsBegin) { this.lastSelection = shape; } else if (this.lastSelection?.IsBegin == true) { this.ConnectShape(this.lastSelection, shape); this.lastSelection = null; } } } /// /// Gets or sets the orientation. /// /// The orientation. public Orientation Orientation { get => this.orientation; set { this.orientation = value; this.MoveContainer(); } } private float ShapeHeight => 20; private float ShapeHeightSpace => this.ShapeHeight + 3; public float ShapeWidth { get; set; } = 120; private float ShapeWidthSpace => this.ShapeWidth + 3; /// /// Adds the left item. /// /// The name. /// The tag. /// if set to true [multi]. public void AddFromItem(string name, object tag = null, bool multi = false, bool must = false) { this.CreateBeginShape(BasicShapes.Rectangle, name, tag, multi, must); } /// /// Adds the right item. /// /// The name. /// The tag. public void AddToItem(string name, object tag = null) { this.CreateEndShape(BasicShapes.Rectangle, name, tag); } /// /// UpdateMatch /// public void UpdateMatch() { this.MoveContainer(); } /// /// Clears this instance. /// public void Clear() { this.fromContainer.Items.Clear(); this.toContainer.Items.Clear(); this.ClearConnection(); } /// /// Clears the connection. /// public void ClearConnection() { List connectionList = this.diagramControl.Items.OfType().ToList(); foreach (DiagramConnector item in connectionList) { this.diagramControl.Items.Remove(item); } } /// /// Connects the specified from item. /// /// From item. /// To item. public void Connect(MatchItem fromItem, MatchItem toItem) { CustomDiagramShape a = fromItem.Shape; CustomDiagramShape b = toItem.Shape; this.ConnectShape(a, b); } /// /// Connects the specified from name. /// /// From name. /// To name. public void Connect(string fromName, string toName) { CustomDiagramShape a = this.fromContainer.Items.OfType().FirstOrDefault(r => r.Content == fromName); CustomDiagramShape b = this.toContainer.Items.OfType().FirstOrDefault(r => r.Content == fromName); this.ConnectShape(a, b); } /// /// Creates the container. /// public void CreateContainer() { this.fromContainer = this.CreateContainer(CustomContainers.Empty, new RectangleF(0, 0, 500, 2000)); this.toContainer = this.CreateContainer(CustomContainers.Empty, new RectangleF(300, 0, 500, 2000)); this.fromContainer.Padding = this.toContainer.Padding = new Padding(0); this.diagramControl.Items.Add(this.fromContainer); this.diagramControl.Items.Add(this.toContainer); this.MoveContainer(); } /// /// Creates the test data. /// public void CreateTestData() { for (int i = 0; i < 10; i++) { this.AddFromItem($"数据库字段{i + 1}", null, true); } for (int i = 0; i < 10; i++) { this.AddToItem($"文件字段{i + 1}", null); } this.UpdateMatch(); } /// /// Gets from items. /// /// IEnumerable<MatchItem>. public IEnumerable GetFromItems() { for (int i = 0; i < this.fromContainer.Items.Count; i++) { CustomDiagramShape item = this.fromContainer.Items[i] as CustomDiagramShape; yield return new MatchFromItem { Shape = item, Name = item.Content, Index = i, Tag = item.Tag, }; } } /// /// Gets the matches. /// /// List<MatchLeftItem>. public List GetMatches() { List list = new List(); for (int i = 0; i < this.fromContainer.Items.Count; i++) { CustomDiagramShape item = this.fromContainer.Items[i] as CustomDiagramShape; MatchFromItem lhs = new MatchFromItem { Shape = item, Name = item.Content, Index = i, Tag = item.Tag, }; foreach (IDiagramConnector conn in item.OutgoingConnectors) { if (conn.EndItem is CustomDiagramShape right) { MatchToItem rhs = new MatchToItem { Shape = right, Name = right.Content, Index = this.toContainer.Items.IndexOf(right), Tag = right.Tag, }; rhs.Connections.Add(lhs); lhs.Connections.Add(rhs); } } list.Add(lhs); } return list; } /// /// Gets to items. /// /// IEnumerable<MatchItem>. public IEnumerable GetToItems() { for (int i = 0; i < this.toContainer.Items.Count; i++) { CustomDiagramShape item = this.toContainer.Items[i] as CustomDiagramShape; yield return new MatchToItem { Shape = item, Name = item.Content, Index = i, Tag = item.Tag, }; } } /// /// Raises the event. /// /// An that contains the event data. protected override void OnClientSizeChanged(EventArgs e) { base.OnClientSizeChanged(e); this.MoveContainer(); } /// /// Processes a command key. /// /// A , passed by reference, that represents the window message to process. /// One of the values that represents the key to process. /// if the character was processed by the control; otherwise, . protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { void MoveView(Func action) { LayersHostController controller = ((IDiagramControl)this.diagramControl).Controller.LayersHost.Controller; System.Windows.Point pos = controller.VisibleArea.TopLeft; pos = action(pos); if (pos.X + controller.VisibleArea.Width > controller.Location.X + controller.Extent.Width) { pos.X = controller.Location.X + controller.Extent.Width - controller.VisibleArea.Width; } if (pos.X < controller.Location.X) { pos.X = controller.Location.X; } if (pos.Y + controller.VisibleArea.Height > controller.Location.Y + controller.Extent.Height) { pos.Y = controller.Location.Y + controller.Extent.Height - controller.VisibleArea.Height; } if (pos.Y < controller.Location.Y) { pos.Y = controller.Location.Y; } this.diagramControl.ScrollToPoint(pos, HorizontalAlignmentKind.Left, VerticalAlignmentKind.Top); } if (this.orientation == Orientation.Horizontal) { if (keyData == Keys.Right) { MoveView( pos => { pos.X += this.Width * 0.8f; return pos; }); } if (keyData == Keys.Left) { MoveView( pos => { pos.X -= this.Width * 0.8f; return pos; }); } } else { if (keyData == Keys.Up) { MoveView( pos => { pos.Y -= this.Height * 0.8f; return pos; }); } if (keyData == Keys.Down) { MoveView( pos => { pos.Y += this.Height * 0.8f; return pos; }); } } return base.ProcessCmdKey(ref msg, keyData); } /// /// Connects the shape. /// /// a. /// The b. private void ConnectShape(CustomDiagramShape a, CustomDiagramShape b) { if (a != null && b != null) { CustomDiagramConnector c = new CustomDiagramConnector(ConnectorType.Straight, a, b); b.Appearance.BackColor = a.Appearance.BackColor; b.Appearance.ForeColor = a.Appearance.ForeColor; c.Appearance.BorderColor = a.Appearance.BackColor; this.diagramControl.Items.Add(c); this.SortItemsOnce(); } } private void CreateBeginShape(ShapeDescription shape, string content, object tag, bool multi, bool must = false) { RectangleF bounds = this.GetShapeBounds(this.fromContainer.Items.Count); CustomDiagramShape item = new CustomDiagramShape(shape, bounds, content, true); item.CanCopy = false; item.CanEdit = false; item.CanDelete = false; item.CanMove = false; item.CanResize = false; item.CanRotate = false; //item.CanAttachConnectorEndPoint = false; item.CanAttachMultiConnector = multi; item.Appearance.BackColor = this.GetNextColor(); if (must) { item.Appearance.BackColor = Color.FromArgb(255, 90, 90); } item.Appearance.ForeColor = this.GetForeColor(item.Appearance.BackColor); item.CreateBeginPoints(this.orientation); item.Tag = tag; this.fromContainer.Items.Add(item); } private DiagramContainer CreateContainer(ContainerShapeDescription shape, RectangleF bounds) { return new DiagramContainer(bounds) { Shape = shape, CanSelect = false, CanMove = false, CanResize = false, CanDelete = false, CanCopy = false, CanAttachConnectorBeginPoint = false, CanAttachConnectorEndPoint = false, }; } private void CreateEndShape(ShapeDescription shape, string content, object tag) { RectangleF bounds = this.GetShapeBounds(this.toContainer.Items.Count); CustomDiagramShape item = new CustomDiagramShape(shape, bounds, content, false); item.CanCopy = false; item.CanEdit = false; item.CanDelete = false; item.CanMove = false; item.CanResize = false; item.CanRotate = false; //item.CanAttachConnectorBeginPoint = false; item.CreateEndPoints(this.orientation); item.Tag = tag; this.toContainer.Items.Add(item); } /// /// Diagrams the control1_ adding new item. /// /// The sender. /// The e. private void DiagramControl1_AddingNewItem(object sender, DiagramAddingNewItemEventArgs e) { if (e.Item is CustomDiagramConnector dc) { CustomDiagramShape shape1 = dc.BeginItem as CustomDiagramShape; CustomDiagramShape shape2 = dc.EndItem as CustomDiagramShape; if (shape1 != null && shape2 != null && shape1.IsBegin != shape2.IsBegin) { CustomDiagramShape left = shape1.IsBegin ? shape1 : shape2; CustomDiagramShape right = !shape1.IsBegin ? shape1 : shape2; if (left.CanAttachMultiConnector && this.diagramControl.SelectedItems.Contains(right)) { CustomDiagramConnector[] array = this.diagramControl.SelectedItems.OfType().Select( r => { CustomDiagramConnector c = new CustomDiagramConnector(ConnectorType.Straight, left, r); r.Appearance.BackColor = left.Appearance.BackColor; r.Appearance.ForeColor = left.Appearance.ForeColor; c.Appearance.BorderColor = left.Appearance.BackColor; return c; }).ToArray(); this.diagramControl.Items.AddRange(array); } else { CustomDiagramConnector c = new CustomDiagramConnector(ConnectorType.Straight, left, right); right.Appearance.BackColor = left.Appearance.BackColor; right.Appearance.ForeColor = left.Appearance.ForeColor; c.Appearance.BorderColor = left.Appearance.BackColor; this.diagramControl.Items.Add(c); } this.SortItemsOnce(); } e.Cancel = true; } } private void DiagramControl1_ConnectionChanged(object sender, DiagramConnectionChangedEventArgs e) { DiagramConnector dc = e.Connector; if (e.OldItem == e.NewItem) { return; } if (dc.BeginItem is DiagramShape left && dc.EndItem is DiagramShape right) { right.Appearance.BackColor = left.Appearance.BackColor; right.Appearance.ForeColor = left.Appearance.ForeColor; dc.Appearance.BorderColor = left.Appearance.BackColor; e.OldItem?.Appearance.Reset(); this.SortItemsOnce(); } else { dc.EndItem = e.OldItem; } } private void DiagramControl1_CustomDrawBackground(object sender, CustomDrawBackgroundEventArgs e) { using (SolidBrush brush = new SolidBrush(SkinHelper.CurrentBackground)) { e.Graphics.FillRectangle(brush, e.TotalBounds); } } private void DiagramControl1_ItemsDeleting(object sender, DiagramItemsDeletingEventArgs e) { foreach (DiagramItem item in e.Items) { if (item is CustomDiagramConnector dc && dc.EndItem is CustomDiagramShape end) { end.Appearance.Reset(); } } this.SortItemsOnce(); } private void DiagramControl1_SizeChanged(object sender, EventArgs e) { this.diagramControl.AlignPage(HorzAlignment.Near, VertAlignment.Top); } private Color GetForeColor(Color backColor, bool byBrightness = true) { float GetBrightness(Color c) => ((c.R * 0.299f) + (c.G * 0.587f) + (c.B * 0.114f)) / 256f; Color GetInvertColor(Color c) => Color.FromArgb(c.ToArgb() ^ 0xffffff); if (byBrightness) { return GetBrightness(backColor) < 0.55 ? Color.White : Color.Black; } return GetInvertColor(backColor); } private Color GetNextColor() { if (this.colorIndex >= Colors.Length) { this.colorIndex = 0; } return Colors[this.colorIndex++]; } private RectangleF GetShapeBounds(int index) { if (this.orientation == Orientation.Vertical) { return new RectangleF(0, index * this.ShapeHeightSpace, this.ShapeWidth, this.ShapeHeight); } else { return new RectangleF(index * this.ShapeWidthSpace, 0, this.ShapeWidth, this.ShapeHeight); } } private void MoveContainer() { if (this.fromContainer == null) { return; } if (this.toContainer == null) { return; } int count = Math.Max(this.fromContainer.Items.Count, this.toContainer.Items.Count); if (this.orientation == Orientation.Vertical) { var width = this.Size.Width; var height = Math.Max(this.Size.Height, count * this.ShapeHeightSpace); this.diagramControl.OptionsView.PageSize = new SizeF(width, height); float w = width / 2.5f; float h = height; this.fromContainer.Bounds = new RectangleF(0, 0, w, h); this.toContainer.Bounds = new RectangleF(w, 0, w, h); } else { var width = Math.Max(this.Size.Width, count * this.ShapeWidthSpace); var height = this.Size.Height; this.diagramControl.OptionsView.PageSize = new SizeF(width, height); float w = width; float h = height / 2.5f; this.fromContainer.Bounds = new RectangleF(0, 0, w, h); this.toContainer.Bounds = new RectangleF(0, h, w, h); } this.MoveItems(); } private void MoveItems() { foreach (CustomDiagramShape item in this.fromContainer.Items) { item.CreateBeginPoints(this.orientation); } foreach (CustomDiagramShape item in this.toContainer.Items) { item.CreateEndPoints(this.orientation); } this.SortItemsOnce(true); } private void SortItemsOnce(bool immediate = false) { void SortLeft() { List list = this.fromContainer.Items.ToList(); int i = 0; foreach (DiagramItem item in this.fromContainer.Items) { if (item.OutgoingConnectors.Any()) { item.Bounds = this.GetShapeBounds(i++); list.Remove(item); } } foreach (DiagramItem left in list) { left.Bounds = this.GetShapeBounds(i++); } } void SortRight() { List list = this.toContainer.Items.ToList(); int i = 0; foreach (DiagramItem item in this.fromContainer.Items) { foreach (IDiagramConnector conn in item.GetAttachedConnectors()) { if (conn.EndItem is CustomDiagramShape right && list.Contains(right)) { right.Bounds = this.GetShapeBounds(i++); list.Remove(right); } } } foreach (DiagramItem right in list) { right.Bounds = this.GetShapeBounds(i++); } } void SortItems() { this.diagramControl.BeginUpdate(); SortLeft(); SortRight(); this.diagramControl.EndUpdate(); } if (immediate) { SortItems(); } else { this.timer.Invoke(SortItems); } } } /// /// Class MatchItem. /// public abstract class MatchItem { /// /// Gets or sets the index. /// /// The index. public int Index { get; set; } /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } /// /// Gets or sets the tag. /// /// The tag. public object Tag { get; set; } /// /// Gets or sets the Shape. /// /// The Shape. internal CustomDiagramShape Shape { get; set; } } /// /// Class MatchLeftItem. /// Implements the /// /// public class MatchFromItem : MatchItem { /// /// Gets the connections. /// /// The connections. public List Connections { get; set; } = new List(); } /// /// Class MatchRightItem. /// Implements the /// /// public class MatchToItem : MatchItem { /// /// Gets the connections. /// /// The connections. public List Connections { get; } = new List(); } /// /// Class CustomContainers. /// public static class CustomContainers { /// /// The empty /// public static readonly ContainerShapeDescription Empty = ContainerShapeDescription.Create( "Empty", "Empty", CreateEmptyShape, null, null, null, null, DiagramShapeStyleId.Variant3, true); private static ShapeGeometry CreateEmptyShape(double width, double height, IEnumerable parameters) { SvgGroup svgGroup = new SvgGroup(); return CreateSvgImage(width, height, svgGroup); } private static SvgImage CreateSvgImage(double width, double height, SvgElement childElement) { SvgRoot svgRoot = SvgRoot.Create( new SvgElementProperties(), null, null, new SvgUnit(width), new SvgUnit(height)); svgRoot.Elements.Add(childElement); return SvgImage.Create(svgRoot); } } /// /// Class CustomDiagramShape. /// Implements the /// /// public class CustomDiagramShape : DiagramShape { /// /// Initializes a new instance of the class with the specified settings. /// /// The shape kind. This object is used to initialize the property. /// A object that represents the shape bounds. /// The text displayed within the shape. /// is begin. public CustomDiagramShape(ShapeDescription shape, RectangleF bounds, string content, bool isBegin) : base(shape, bounds, content) { this.IsBegin = isBegin; } /// /// IsBegin /// public bool IsBegin { get; set; } /// /// Specifies whether end-users can attach a connector's beginning point to the item. /// /// true to allow end-users to attach the begin point of a connector to the item; otherwise, false. public override bool? CanAttachConnectorBeginPoint { get { if (!this.CanAttachMultiConnector && this.OutgoingConnectors.Any()) { return false; } return base.CanAttachConnectorBeginPoint; } set => base.CanAttachConnectorBeginPoint = value; } /// /// Gets or sets a value indicating whether this instance can attach multi connector. /// /// true if this instance can attach multi connector; otherwise, false. public bool CanAttachMultiConnector { get; set; } = true; /// /// Creates the begin points. /// /// The orientation. public void CreateBeginPoints(Orientation orientation) { List list = this.CreateOriginPoints(); if (orientation == Orientation.Vertical) { float maxX = list.Max(pt => pt.X); this.ConnectionPoints = new PointCollection(list.Where(pt => pt.X == maxX).ToList()); } else { float maxY = list.Max(pt => pt.Y); this.ConnectionPoints = new PointCollection(list.Where(pt => pt.Y == maxY).ToList()); } } /// /// Creates the end points. /// /// The orientation. public void CreateEndPoints(Orientation orientation) { List list = this.CreateOriginPoints(); if (orientation == Orientation.Vertical) { float minX = list.Min(pt => pt.X); this.ConnectionPoints = new PointCollection(list.Where(pt => pt.X == minX).ToList()); } else { float minY = list.Min(pt => pt.Y); this.ConnectionPoints = new PointCollection(list.Where(pt => pt.Y == minY).ToList()); } } private List CreateOriginPoints() { List list = new List(); list.AddRange( this.Shape.GetConnectionPoints(new System.Windows.Size(1d, 1d), this.Shape.DefaultParameters) .Select(x => new PointFloat((float)x.X, (float)x.Y))); return list; } } /// /// Class CustomDiagramConnector. /// Implements the /// /// public class CustomDiagramConnector : DiagramConnector { /// /// Initializes a new instance of the class. /// public CustomDiagramConnector() : base() { this.Init(); } /// /// Initializes a new instance of the class with the specified settings. /// /// The connector type. This value is used to initialize the property. public CustomDiagramConnector(ConnectorType connectorType) : base(connectorType) { this.Init(); } /// /// Initializes a new instance of the class with the specified settings. /// /// The connector type. This value is used to initialize the property. /// The connector's starting item. This value is used to initialize the property. /// The connector's ending item. This value is used to initialize the property. public CustomDiagramConnector(ConnectorType connectorType, DiagramItem beginItem, DiagramItem endItem) : base(connectorType) { this.Init(); this.SetItems(beginItem, endItem); } /// /// Sets the items. /// /// The begin item. /// The end item. public new void SetItems(DiagramItem beginItem, DiagramItem endItem) { base.SetItems(beginItem, endItem); this.BeginItemPointIndex = 0; this.EndItemPointIndex = 0; } /// /// Sets the points. /// /// The begin point. /// The end point. public new void SetPoints(PointF beginPoint, PointF endPoint) { base.SetPoints(beginPoint, endPoint); } /// /// Sets the points. /// /// The begin point. /// The end point. public new void SetPoints(PointFloat beginPoint, PointFloat endPoint) { base.SetPoints(beginPoint, endPoint); } private void Init() { // this.CanDragEndPoint = false; this.CanEdit = false; this.CanCopy = false; this.CanDragBeginPoint = false; this.CanChangeRoute = false; } } /// /// Class CustomDiagramTool. /// Implements the /// /// public class CustomDiagramTool : FactoryConnectorTool { private PointerTool pointerTool = new PointerTool(); /// /// Initializes a new instance of the class. /// public CustomDiagramTool() : base("CustomDiagramTool", () => "CustomDiagramTool", CreateConnector) { } /// /// Creates the state of the active input. /// /// The diagram. /// The item. /// The mouse arguments. /// The previously created item. /// InputState. public override InputState CreateActiveInputState( IDiagramControl diagram, IInputElement item, IMouseButtonArgs mouseArgs, IDiagramItem previouslyCreatedItem) { if (mouseArgs.ChangedButton == MouseButtonKind.Right) { return this.pointerTool.CreateActiveInputState(diagram, item, mouseArgs, previouslyCreatedItem); } return base.CreateActiveInputState(diagram, item, mouseArgs, previouslyCreatedItem); } /// /// Gets the default input state cursor. /// /// The diagram. /// The item. /// The mouse arguments. /// The previously created item. /// DiagramCursor. public override DiagramCursor GetDefaultInputStateCursor( IDiagramControl diagram, IInputElement item, IInputArgs mouseArgs, IDiagramItem previouslyCreatedItem) { return this.pointerTool.GetDefaultInputStateCursor(diagram, item, mouseArgs, previouslyCreatedItem); } private static IDiagramConnector CreateConnector(IDiagramControl diagram) { CustomDiagramConnector connector = new CustomDiagramConnector() { Type = ConnectorType.Straight }; return connector; } } }