mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-03-03 00:00:57 +08:00
674 lines
18 KiB
C#
674 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using AIStudio.Wpf.DiagramDesigner;
|
|
using AIStudio.Wpf.DiagramDesigner.Algorithms;
|
|
using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
|
using AIStudio.Wpf.DiagramDesigner.Helpers;
|
|
using AIStudio.Wpf.DiagramDesigner.Models;
|
|
using AIStudio.Wpf.Mind.Helpers;
|
|
|
|
namespace AIStudio.Wpf.Mind.ViewModels
|
|
{
|
|
public class MindNode : DesignerItemViewModelBase
|
|
{
|
|
public MindNode(NodeLevel nodeLevel, MindType mindType = MindType.Mind) : this(null, nodeLevel, mindType)
|
|
{
|
|
}
|
|
|
|
public MindNode(IDiagramViewModel root, NodeLevel nodeLevel, MindType mindType = MindType.Mind) : base(root)
|
|
{
|
|
NodeLevel = nodeLevel;
|
|
MindType = mindType;
|
|
|
|
InitLayout();
|
|
MindLayout.Appearance(this);
|
|
}
|
|
|
|
public MindNode(IDiagramViewModel root, SelectableItemBase designer) : base(root, designer)
|
|
{
|
|
InitLayout();
|
|
}
|
|
|
|
public MindNode(IDiagramViewModel root, SerializableItem serializableItem, string serializableType) : base(root, serializableItem, serializableType)
|
|
{
|
|
InitLayout();
|
|
}
|
|
|
|
protected override void Init(IDiagramViewModel root)
|
|
{
|
|
base.Init(root);
|
|
|
|
EnabledForConnection = false;
|
|
|
|
AddChildCommand = new SimpleCommand(Command_Enable, ExecuteAddChildCommand);
|
|
AddParentCommand = new SimpleCommand(Level_Enable, ExecuteAddParentCommand);
|
|
AddPeerCommand = new SimpleCommand(Level_Enable, ExecuteAddPeerCommand);
|
|
DeleteCommand = new SimpleCommand(Level_Enable, ExecuteDeleteCommand);
|
|
MoveForwardCommand = new SimpleCommand(Command_Enable, ExecuteMoveForwardCommand);
|
|
MoveBackCommand = new SimpleCommand(Command_Enable, ExecuteMoveBackCommand);
|
|
BuildMenuOptions();
|
|
|
|
this.PropertyChanged += this.Item_PropertyChanged;
|
|
}
|
|
|
|
protected void InitLayout()
|
|
{
|
|
var layout = GlobalType.AllTypes.Where(p => typeof(IMindLayout).IsAssignableFrom(p)).FirstOrDefault(p => p.Name == MindType.ToString() + "Layout");
|
|
MindLayout = layout != null ? (System.Activator.CreateInstance(layout) as IMindLayout) : new MindLayout();
|
|
}
|
|
|
|
|
|
private bool Level_Enable(object obj)
|
|
{
|
|
if (Command_Enable(obj) == false) return false;
|
|
|
|
return NodeLevel != NodeLevel.Level1;
|
|
}
|
|
|
|
#region 属性
|
|
public IMindLayout MindLayout
|
|
{
|
|
get;set;
|
|
}
|
|
[Browsable(false)]
|
|
private NodeLevel _nodeLevel;
|
|
public NodeLevel NodeLevel
|
|
{
|
|
get
|
|
{
|
|
return _nodeLevel;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _nodeLevel, value);
|
|
}
|
|
}
|
|
|
|
private MindType _mindType;
|
|
public MindType MindType
|
|
{
|
|
get
|
|
{
|
|
return _mindType;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _mindType, value);
|
|
}
|
|
}
|
|
|
|
private CornerRadius _cornerRadius = new CornerRadius(3);
|
|
public CornerRadius CornerRadius
|
|
{
|
|
get
|
|
{
|
|
return _cornerRadius;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _cornerRadius, value);
|
|
}
|
|
}
|
|
|
|
private Thickness _borderThickness = new Thickness(1);
|
|
public Thickness BorderThickness
|
|
{
|
|
get
|
|
{
|
|
return _borderThickness;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _borderThickness, value);
|
|
}
|
|
}
|
|
|
|
private bool _isExpanded = true;
|
|
public bool IsExpanded
|
|
{
|
|
get
|
|
{
|
|
return _isExpanded;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _isExpanded, value);
|
|
}
|
|
}
|
|
|
|
public SizeBase Spacing
|
|
{
|
|
get; set;
|
|
} = new SizeBase(15, 15);
|
|
|
|
|
|
private ObservableCollection<MindNode> _children = new ObservableCollection<MindNode>();
|
|
public ObservableCollection<MindNode> 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 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;
|
|
public LinkInfo LinkInfo
|
|
{
|
|
get
|
|
{
|
|
return _linkInfo;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _linkInfo, value);
|
|
}
|
|
}
|
|
|
|
private ImageInfo _imageInfo;
|
|
public ImageInfo ImageInfo
|
|
{
|
|
get
|
|
{
|
|
return _imageInfo;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _imageInfo, value);
|
|
}
|
|
}
|
|
|
|
private string _remark;
|
|
public string Remark
|
|
{
|
|
get
|
|
{
|
|
return _remark;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _remark, value);
|
|
}
|
|
}
|
|
|
|
private string _grade;
|
|
public string Grade
|
|
{
|
|
get
|
|
{
|
|
return _grade;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _grade, value);
|
|
}
|
|
}
|
|
|
|
private double _completionRate;
|
|
public double CompletionRate
|
|
{
|
|
get
|
|
{
|
|
return _completionRate;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _completionRate, value);
|
|
}
|
|
}
|
|
|
|
private ObservableCollection<string> _tags;
|
|
public ObservableCollection<string> Tags
|
|
{
|
|
get
|
|
{
|
|
return _tags;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _tags, value);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 命令
|
|
public SimpleCommand AddParentCommand
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
public SimpleCommand AddChildCommand
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
public SimpleCommand AddPeerCommand
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
public SimpleCommand DeleteCommand
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
public SimpleCommand MoveForwardCommand
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
public SimpleCommand MoveBackCommand
|
|
{
|
|
get; private set;
|
|
}
|
|
#endregion
|
|
|
|
#region 菜单
|
|
private void BuildMenuOptions()
|
|
{
|
|
menuOptions = new ObservableCollection<CinchMenuItem>();
|
|
CinchMenuItem menuItem = new CinchMenuItem();
|
|
menuItem.Text = "下级";
|
|
menuItem.Command = AddChildCommand;
|
|
menuOptions.Add(menuItem);
|
|
menuItem = new CinchMenuItem();
|
|
menuItem.Text = "同级";
|
|
menuItem.Command = AddPeerCommand;
|
|
menuOptions.Add(menuItem);
|
|
menuItem = new CinchMenuItem();
|
|
menuItem.Text = "上级";
|
|
menuItem.Command = AddParentCommand;
|
|
menuOptions.Add(menuItem);
|
|
menuItem = new CinchMenuItem();
|
|
menuItem.Text = "前移";
|
|
menuItem.Command = MoveForwardCommand;
|
|
menuOptions.Add(menuItem);
|
|
menuItem = new CinchMenuItem();
|
|
menuItem.Text = "后移";
|
|
menuItem.Command = MoveBackCommand;
|
|
menuOptions.Add(menuItem);
|
|
menuItem = new CinchMenuItem();
|
|
menuItem.Text = "删除";
|
|
menuItem.Command = DeleteCommand;
|
|
menuOptions.Add(menuItem);
|
|
}
|
|
#endregion
|
|
|
|
#region 操作
|
|
public void ExecuteAddChildCommand(object obj)
|
|
{
|
|
if (obj is MindNode node)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
node = new MindNode(Root, (NodeLevel)Math.Min((int)NodeLevel + 1, (int)NodeLevel.Level3), this.MindType) { Text = "分支主题" };
|
|
}
|
|
AddChild(node);
|
|
|
|
LayoutUpdated();
|
|
}
|
|
|
|
public void ExecuteAddParentCommand(object obj)
|
|
{
|
|
if (Parent is MindNode parent)
|
|
{
|
|
if (obj is MindNode node)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
if (NodeLevel == NodeLevel.Level1)
|
|
{
|
|
return;
|
|
}
|
|
else if (NodeLevel == NodeLevel.Level2)
|
|
node = new MindNode(Root, NodeLevel.Level2, this.MindType) { Text = "分支主题" };
|
|
else
|
|
node = new MindNode(Root, NodeLevel.Level3, this.MindType) { Text = "分支主题" };
|
|
}
|
|
|
|
parent.RemoveChild(this);
|
|
int index = parent.Children.IndexOf(this);
|
|
parent.AddChild(node, index + 1);
|
|
|
|
node.AddChild(this);
|
|
|
|
LayoutUpdated();
|
|
}
|
|
}
|
|
|
|
public void ExecuteAddPeerCommand(object obj)
|
|
{
|
|
if (Parent is MindNode parent)
|
|
{
|
|
if (obj is MindNode node)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
if (NodeLevel == NodeLevel.Level1)
|
|
{
|
|
return;
|
|
}
|
|
else if (NodeLevel == NodeLevel.Level2)
|
|
node = new MindNode(Root, NodeLevel.Level2, this.MindType) { Text = "分支主题" };
|
|
else
|
|
node = new MindNode(Root, NodeLevel.Level3, this.MindType) { Text = "分支主题" };
|
|
}
|
|
int index = parent.Children.IndexOf(this);
|
|
parent.AddChild(node, index + 1);
|
|
|
|
LayoutUpdated();
|
|
}
|
|
}
|
|
|
|
private void ExecuteMoveBackCommand(object obj)
|
|
{
|
|
if (Parent is MindNode parent)
|
|
{
|
|
int index = parent.Children.IndexOf(this);
|
|
if (index < parent.Children.Count - 1)
|
|
{
|
|
parent.RemoveChild(this);
|
|
parent.AddChild(this, index + 1);
|
|
LayoutUpdated();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ExecuteMoveForwardCommand(object obj)
|
|
{
|
|
if (Parent is MindNode parent)
|
|
{
|
|
int index = parent.Children.IndexOf(this);
|
|
if (index > 0)
|
|
{
|
|
parent.RemoveChild(this);
|
|
parent.AddChild(this, index - 1);
|
|
LayoutUpdated();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ExecuteDeleteCommand(object obj)
|
|
{
|
|
if (Parent is MindNode parent)
|
|
{
|
|
parent.RemoveChild(this, true);
|
|
LayoutUpdated();
|
|
}
|
|
}
|
|
|
|
public void AddChild(MindNode item, int index = -1)
|
|
{
|
|
if (this.NodeLevel == NodeLevel.Level1)
|
|
{
|
|
item.NodeLevel = NodeLevel.Level2;
|
|
}
|
|
else
|
|
{
|
|
item.NodeLevel = NodeLevel.Level3;
|
|
}
|
|
if (index >= 0)
|
|
{
|
|
this.Children.Insert(index, item);
|
|
}
|
|
else
|
|
{
|
|
this.Children.Add(item);
|
|
}
|
|
item.Parent = this;
|
|
Root?.DirectAddItemCommand.Execute(item);
|
|
|
|
ConnectionViewModel connector = MindLayout?.GetConnectionViewModel(this, item);
|
|
Root?.DirectAddItemCommand.Execute(connector);
|
|
Root?.ClearSelectedItemsCommand.Execute(new SelectableDesignerItemViewModelBase[] { connector });
|
|
|
|
Root?.BringForwardCommand.Execute(new DesignerItemViewModelBase[] { item });
|
|
}
|
|
|
|
public void RemoveChild(MindNode item, bool removeall = false)
|
|
{
|
|
item.PropertyChanged -= Item_PropertyChanged;
|
|
|
|
this.Children.Remove(item);
|
|
|
|
var connectors = Root?.Items.OfType<ConnectionViewModel>().Where(p => p.SinkConnectorInfoFully?.DataItem == item).ToList();
|
|
|
|
Root?.DirectRemoveItemCommand.Execute(item);
|
|
Root?.DirectRemoveItemCommand.Execute(connectors);
|
|
|
|
if (removeall)
|
|
{
|
|
if (item.Children?.Count > 0)
|
|
{
|
|
foreach (var child in item.Children.ToList())
|
|
{
|
|
item.RemoveChild(child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (GetLevel1Node()?.LayoutUpdating == true) return;
|
|
|
|
switch (e.PropertyName)
|
|
{
|
|
case nameof(IsExpanded):
|
|
case nameof(ItemWidth):
|
|
case nameof(ItemHeight):
|
|
{
|
|
LayoutUpdated();
|
|
break;
|
|
}
|
|
case nameof(NodeLevel):
|
|
MindLayout?.Appearance(this);
|
|
break;
|
|
//case nameof(MindType):
|
|
// if (NodeLevel == NodeLevel.Level1)
|
|
// {
|
|
// MindLayout?.Appearance(this);
|
|
// LayoutUpdated();
|
|
// }
|
|
// else
|
|
// {
|
|
// GetLevel1Node().MindType = MindType;
|
|
// }
|
|
// break;
|
|
case nameof(Left):
|
|
{
|
|
if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs)
|
|
{
|
|
if (NodeLevel == NodeLevel.Level1)
|
|
{
|
|
LayoutUpdated();
|
|
}
|
|
else
|
|
{
|
|
UpdateOffsetX((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue);
|
|
LayoutUpdated();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case nameof(Top):
|
|
{
|
|
if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs)
|
|
{
|
|
if (NodeLevel == NodeLevel.Level1)
|
|
{
|
|
LayoutUpdated();
|
|
}
|
|
else
|
|
{
|
|
UpdateOffsetY((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue);
|
|
LayoutUpdated();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case nameof(Text):
|
|
{
|
|
ItemWidth = Math.Max(ItemWidth, GetTextDisplayWidthHelper.GetTextDisplayWidth(Text, new FontFamily(FontViewModel.FontFamily), FontViewModel.FontStyle, FontViewModel.FontWeight, FontViewModel.FontStretch, FontViewModel.FontSize) + 30);
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#region 布局相关
|
|
public MindNode GetLevel1Node()
|
|
{
|
|
var node = this;
|
|
while (node.Parent is MindNode mindNode)
|
|
{
|
|
node = mindNode;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
public MindNode GetLevel2Node()
|
|
{
|
|
var node = this;
|
|
while (node.Parent is MindNode mindNode && mindNode.NodeLevel == NodeLevel.Level2)
|
|
{
|
|
node = mindNode;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public void LayoutUpdated()
|
|
{
|
|
MindLayout?.LayoutUpdated(GetLevel1Node());
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
public class LinkInfo : BindableBase
|
|
{
|
|
private string _url;
|
|
public string Url
|
|
{
|
|
get
|
|
{
|
|
return _url;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _url, value);
|
|
}
|
|
}
|
|
|
|
private string _text;
|
|
public string Text
|
|
{
|
|
get
|
|
{
|
|
return _text;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _text, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class ImageInfo : BindableBase
|
|
{
|
|
private string _url;
|
|
public string Url
|
|
{
|
|
get
|
|
{
|
|
return _url;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _url, value);
|
|
}
|
|
}
|
|
|
|
private string _text;
|
|
public string Text
|
|
{
|
|
get
|
|
{
|
|
return _text;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _text, value);
|
|
}
|
|
}
|
|
}
|
|
}
|