using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.Design; using System.Linq; using System.Security.Policy; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Windows.Input; using System.Windows.Media; using AIStudio.Wpf.DiagramDesigner; using AIStudio.Wpf.DiagramDesigner.Geometrys; using AIStudio.Wpf.DiagramDesigner.Helpers; using AIStudio.Wpf.DiagramDesigner.Models; using AIStudio.Wpf.DiagramDesigner.Serializable; using AIStudio.Wpf.DiagramDesigner.Serializable.ViewModels; using AIStudio.Wpf.Mind.Controls; using AIStudio.Wpf.Mind.Helpers; using AIStudio.Wpf.Mind.Models; namespace AIStudio.Wpf.Mind.ViewModels { public class MindNode : DiagramItemViewModel { public MindNode() : this(null) { } public MindNode(IDiagramViewModel root) : base(root) { } public MindNode(IDiagramViewModel root, SelectableItemBase designer) : base(root, designer) { InitLayout(false); } public MindNode(IDiagramViewModel root, SerializableItem serializableItem, string serializableType) : base(root, serializableItem, serializableType) { InitLayout(false); } public override SelectableItemBase GetSerializableObject() { return new MindNodeDesignerItem(this); } public override DiagramNode ToDiagram() { var mindNodeModel = new MindNodeModel(); mindNodeModel.MindType = MindType; mindNodeModel.MindTheme = MindTheme; mindNodeModel.Spacing = Spacing; mindNodeModel.Offset = Offset; mindNodeModel.IsExpanded = IsExpanded; mindNodeModel.LinkInfoModel = new LinkInfoModel(LinkInfo); mindNodeModel.ImageInfoModel = new ImageInfoModel(ImageInfo); mindNodeModel.Remark = Remark; mindNodeModel.Priority = Priority; mindNodeModel.Rate = Rate; if (Tags != null) { mindNodeModel.Tags = new List(Tags); } return mindNodeModel; } protected override void Init(IDiagramViewModel root, bool initNew) { base.Init(root, initNew); EnabledForConnection = false; AlwayForResized = true; CustomText = true; IsInnerConnector = true; AddChildCommand = (Root as IMindDiagramViewModel)?.AddChildCommand; AddParentCommand = (Root as IMindDiagramViewModel)?.AddParentCommand; AddPearCommand = (Root as IMindDiagramViewModel)?.AddPearCommand; DeleteCommand = (Root as IMindDiagramViewModel)?.DeleteCommand; MoveForwardCommand = (Root as IMindDiagramViewModel)?.MoveForwardCommand; MoveBackCommand = (Root as IMindDiagramViewModel)?.MoveBackCommand; ExportCommand = (Root as IMindDiagramViewModel)?.ExportCommand; ImportCommand = (Root as IMindDiagramViewModel)?.ImportCommand; BuildMenuOptions(); Tags = new ObservableCollection(); } protected override void InitNew() { this.ClearConnectors(); } public void InitLayout(bool initAppearance) { IsLoaded = false; var layout = TypeHelper.GetType(this.MindType.ToString() + "Layout"); MindLayout = layout != null ? (System.Activator.CreateInstance(layout) as IMindLayout) : new MindLayout(); MindLayout.Appearance(this, MindTheme, initAppearance); IsLoaded = true; } public void InitConnectionLayout() { var connector = Root?.Items.OfType().Where(p => p.IsFullConnection).FirstOrDefault(p => p.SinkConnectorInfoFully.DataItem == this); if (connector != null) { MindLayout?.GetOrSetConnectionViewModel(connector.SourceConnectorInfoFully.DataItem as MindNode, connector.SinkConnectorInfoFully.DataItem as MindNode, connector); } else if (ParentNode != null) { connector = MindLayout?.GetOrSetConnectionViewModel(ParentNode, this, null); Root?.Add(new SelectableDesignerItemViewModelBase[] { connector }); connector.ZIndex = -1; connector.IsSelected = false; } } public void UpdatedLayout() { MindLayout?.UpdatedLayout(GetLevel0Node()); } public void ThemeChange() { MindThemeHelper.ThemeChange(this, MindTheme); } protected override void LoadDesignerItemViewModel(SelectableItemBase designerbase) { base.LoadDesignerItemViewModel(designerbase); if (designerbase is MindNodeDesignerItem designer) { MindType = designer.MindType; MindTheme = designer.MindTheme; Spacing = designer.Spacing; Offset = designer.Offset; IsExpanded = designer.IsExpanded; if (string.IsNullOrEmpty(designer.LinkInfoItem?.Url) && string.IsNullOrEmpty(designer.LinkInfoItem?.Text)) { LinkInfo = null; } else { LinkInfo = new LinkInfo(designer.LinkInfoItem?.Url, designer.LinkInfoItem?.Text); } if (string.IsNullOrEmpty(designer.ImageInfoItem?.Url) && string.IsNullOrEmpty(designer.ImageInfoItem?.Text)) { ImageInfo = null; } else { ImageInfo = new ImageInfo(designer.ImageInfoItem?.Url, designer.ImageInfoItem?.Text); } Remark = designer.Remark; if (double.TryParse(designer.Priority ?? "", out var priority)) { Priority = priority; } if (double.TryParse(designer.Rate ?? "", out var rate)) { Rate = rate; } if (designer.Tags != null) { Tags = new ObservableCollection(designer.Tags); } } } #region 属性 private MindType _mindType = MindType.Mind; public MindType MindType { get { return GetLevel0Node()._mindType; } set { GetLevel0Node()._mindType = value; } } private MindTheme _mindTheme; public MindTheme MindTheme { get { return GetLevel0Node()._mindTheme; } set { GetLevel0Node()._mindTheme = value; } } public IMindLayout MindLayout { get; set; } public MindNode ParentNode { get { return Parent as MindNode; } } public MindNode RootNode { get { return GetLevel0Node(); } } public int NodeLevel { get { if (ParentNode == null) { return 0; } else { return ParentNode.NodeLevel + 1; } } } private bool _isExpanded = true; [CanDo] public bool IsExpanded { get { return _isExpanded; } set { SetProperty(ref _isExpanded, value); } } public SizeBase Spacing { get; set; } = new SizeBase(15, 15); private ObservableCollection _children = new ObservableCollection(); public ObservableCollection Children { get { return _children; } set { SetProperty(ref _children, value); } } public SizeBase SizeWithSpacing { get { return this.Size.Add(Spacing.Width * 2, Spacing.Height * 2); } } public SizeBase DesiredSize { get; set; } public PointBase DesiredPosition { get; set; } public PointBase DesiredMiddlePosition { get { return new PointBase(this.DesiredPosition.X + ItemWidth / 2, this.DesiredPosition.Y + ItemHeight / 2); } } public PointBase Offset { get; set; } private ConnectorOrientation _connectorOrientation = ConnectorOrientation.Left; public ConnectorOrientation ConnectorOrientation { get { return _connectorOrientation; } set { SetProperty(ref _connectorOrientation, value); } } public bool LayoutUpdating { get; set; } #endregion #region 附加信息属性 private LinkInfo _linkInfo; [CanDo] public LinkInfo LinkInfo { get { return _linkInfo; } set { SetProperty(ref _linkInfo, value); } } private ImageInfo _imageInfo; [CanDo] public ImageInfo ImageInfo { get { return _imageInfo; } set { SetProperty(ref _imageInfo, value); } } private string _remark; [CanDo] [Browsable(true)] public string Remark { get { return _remark; } set { SetProperty(ref _remark, value); } } private double? _priority; [CanDo] public double? Priority { get { return _priority; } set { SetProperty(ref _priority, value); } } private double? _rate; [CanDo] public double? Rate { get { return _rate; } set { SetProperty(ref _rate, value); } } private ObservableCollection _tags; [CanDo] public ObservableCollection Tags { get { return _tags; } set { if (_tags != null) { _tags.CollectionChanged -= _tags_CollectionChanged; } SetProperty(ref _tags, value); if (_tags != null) { _tags.CollectionChanged += _tags_CollectionChanged; } } } private void _tags_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (var item in e.NewItems.OfType()) { var width = GetTextDisplayWidthHelper.GetTextDisplayWidth(item, new FontFamily(FontViewModel.FontFamily), FontViewModel.FontStyle, FontViewModel.FontWeight, FontViewModel.FontStretch, 12) + 6; ItemWidth += width; } } if (e.OldItems != null) { foreach (var item in e.OldItems.OfType()) { var width = GetTextDisplayWidthHelper.GetTextDisplayWidth(item, new FontFamily(FontViewModel.FontFamily), FontViewModel.FontStyle, FontViewModel.FontWeight, FontViewModel.FontStretch, 12) + 6; ItemWidth -= width; } } } #endregion #region 命令 public ICommand AddParentCommand { get; private set; } public ICommand AddChildCommand { get; private set; } public ICommand AddPearCommand { get; private set; } public ICommand DeleteCommand { get; private set; } public ICommand MoveForwardCommand { get; private set; } public ICommand MoveBackCommand { get; private set; } public ICommand ExportCommand { get; private set; } public ICommand ImportCommand { get; private set; } #endregion #region 菜单 private void BuildMenuOptions() { menuOptions = new ObservableCollection(); CinchMenuItem menuItem = new CinchMenuItem(); menuItem.Text = "下级"; menuItem.Command = AddChildCommand; menuItem.CommandParameter = this; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "同级"; menuItem.Command = AddPearCommand; menuItem.CommandParameter = this; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "上级"; menuItem.Command = AddParentCommand; menuItem.CommandParameter = this; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "前移"; menuItem.Command = MoveForwardCommand; menuItem.CommandParameter = this; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "后移"; menuItem.Command = MoveBackCommand; menuItem.CommandParameter = this; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "删除"; menuItem.Command = DeleteCommand; menuItem.CommandParameter = this; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "导出节点"; menuItem.Command = ExportCommand; menuItem.CommandParameter = this; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "导入节点"; menuItem.Command = ImportCommand; menuItem.CommandParameter = this; menuOptions.Add(menuItem); } #endregion #region 操作 public void AddTo(MindNode parent, int index = -1, bool isSelected = true) { //parent为空,为根节点 if (index >= 0) { parent?.Children.Insert(index, this); } else { parent?.Children.Add(this); } this.Parent = parent; this.ParentId = parent?.Id ?? Guid.Empty; this.Offset = parent?.Offset ?? new PointBase(); this.InitLayout(true);//因为节点的层级不同的样式,所以需要Parent确定后才能初始化 this.InitConnectionLayout(); Root?.Add(this); if (isSelected) { if (parent != null) { parent.IsSelected = false; } this.IsSelected = true; } } public void RemoveFrom() { this.IsLoaded = false; if (this.ParentNode != null) { this.ParentNode.Children.Remove(this); } var connectors = Root?.Items.OfType().Where(p => p.SinkConnectorInfoFully?.DataItem == this).ToList(); Root?.Delete(this); Root?.Delete(connectors); } #endregion #region 属性改变 protected override void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (IsLoaded == false) return; if (GetLevel0Node()?.LayoutUpdating == true) return; Interlocked.Increment(ref Root.DoCommandManager.BeginDo); switch (e.PropertyName) { case nameof(Left): { if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs) { UpdateOffsetX((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue); } break; } case nameof(Top): { if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs) { UpdateOffsetY((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue); } break; } case nameof(IsExpanded): case nameof(ItemWidth): case nameof(ItemHeight): { UpdatedLayout(); break; } case nameof(NodeLevel): MindLayout?.Appearance(this); break; case nameof(Text): case nameof(Rate): case nameof(Priority): case nameof(Remark): case nameof(LinkInfo): case nameof(ImageInfo): { SetItemWidthHeight(); break; } case nameof(FontViewModel.FontSize): SetItemWidthHeight(); break; } Interlocked.Decrement(ref Root.DoCommandManager.BeginDo); } #endregion #region 布局相关 public MindNode GetLevel0Node() { var node = this; while (node.Parent is MindNode mindNode) { node = mindNode; } return node; } public MindNode GetLevel1Node() { var node = this; while (node.Parent is MindNode mindNode) { if (node.NodeLevel == 1) { break; } node = mindNode; } return node; } public List GetParent() { var node = this; List mindnode = new List(); while (node.ParentNode != null) { mindnode.Add(node.ParentNode); node = node.ParentNode; } return mindnode; } public List GetChildren(bool self = false) { List mindnode = new List(); if (self) { mindnode.Add(this); } if (this.Children != null) { foreach (var child in this.Children) { mindnode.Add(child); mindnode.AddRange(child.GetChildren()); } } return mindnode; } public string GetChildrenText(bool self = false) { StringBuilder sb = new StringBuilder(); List mindnode = new List(); if (self) { sb.AppendLine(this.Text); } if (this.Children != null) { foreach (var child in this.Children) { sb.AppendLine($"{new String('\t', child.NodeLevel)}{child.Text}"); sb.Append(child.GetChildrenText()); } } return sb.ToString(); } protected void UpdateOffsetX(double oldvalue, double newvalue) { Offset += new VectorBase(newvalue - oldvalue, 0); } protected void UpdateOffsetY(double oldvalue, double newvalue) { Offset += new VectorBase(0, newvalue - oldvalue); } private void SetItemWidthHeight() { double width = 0; double height = 0; width += GetTextDisplayWidthHelper.GetTextDisplayWidth(Text, new FontFamily(FontViewModel.FontFamily), FontViewModel.FontStyle, FontViewModel.FontWeight, FontViewModel.FontStretch, FontViewModel.FontSize) + 30; width += Rate == null ? 0 : 24; width += Priority == null ? 0 : 24; width += Remark == null ? 0 : 24; width += LinkInfo == null ? 0 : 24; var defaultTheme = MindThemeHelper.GetNodeDefaultTheme(this); if (ImageInfo != null) { width = Math.Max(width, 160); height = defaultTheme.ItemHeight / defaultTheme.FontSize * FontViewModel.FontSize + 135; } else { height = defaultTheme.ItemHeight / defaultTheme.FontSize * FontViewModel.FontSize; } ItemWidth = width; ItemHeight = height; } public override void AddToSelection(bool selected, bool clearother) { if (clearother) { foreach (SelectableDesignerItemViewModelBase item in Root.SelectedItems.ToList()) { if (item != this) { item.RemoveFromSelection(); } } } IsSelected = selected; } protected override void ExecuteExitEditCommand(object param) { Root.NextCommand.Execute(this); } #endregion } public class LinkInfo { public string Url { get; set; } public string Text { get; set; } public LinkInfo() { } public LinkInfo(string url, string text) { Url = url; Text = text; } public LinkInfo(LinkInfo info) { Url = info?.Url; Text = info?.Text; } } public class ImageInfo { public string Url { get; set; } public string Text { get; set; } public ImageInfo() { } public ImageInfo(string url, string text) { Url = url; Text = text; } public ImageInfo(ImageInfo info) { Url = info?.Url; Text = info?.Text; } } }