Вам не нужно изменять TreeViewItem
. Это о том, как спроектированы ваши структуры данных. Вам необходимо классифицировать или специализировать ваши модели данных (или узлы), например:
- условный узел, который должен содержать два дочерних узла, представляющих каждую ветвь.
- общий узел выражения, который имеет только один дочерний узел.
- узел соединения (объединение нескольких родительских узлов), у которого есть один дочерний узел, но несколько родителей (набор
узлы).
- узел цикла, который имеет дочерний узел (тело) и дополнительные атрибуты, например набор условий цикла
- узел итерации, который также может иметь дочерний узел (тело) и атрибуты, например набор условий итерации
Если вы решите остаться с TreeView
, я не рекомендую этого, тогда вы бы использовали HierachicalDataTemplate
для проектирования внешнего вида TreeView
.
Но будет более гибко оставить TreeView
в покое и нарисовать все дерево вместо использования элементов управления узла. Вы можете преобразовать представление модели дерева данных (используя специализированные классы моделей узлов) в визуальное представление (и наоборот), обойдя модель данных дерева и добавив соответствующие объекты визуальных узлов на холст для рисования (a Control
, который можно шаблонировать и стилизовать тоже, чтобы придать им желаемый вид). Если вы позволите этим элементам управления расширить элемент управления Thumb
, очень просто будет добавить перетаскивание к визуальным узлам.
Следующий код является простым (и уродливым) примером, показывающим, как соединить два объекта (типа Node
), нажав и удерживая клавишу Ctrl-Key и проведя линию между ними. Есть два Node
объекта, которые уже созданы и размещены на холсте. Позже их следует либо перетащить на холст из пула объектов узлов, либо автоматически нарисовать на холсте, преобразовав график в визуальные и связанные Node
объекты. Поскольку Node
расширяется Thumb
, вы можете перетаскивать элементы управления Node
по DrawingArea
:
Пример использования XAML
<local:DrawingArea Focusable="True">
<local:DrawingArea.Resources>
<Style TargetType="Line">
<Setter Property="Stroke" Value="Blue"/>
<Setter Property="StrokeThickness" Value="2"/>
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
</local:DrawingArea.Resources>
<local:Node CurrentPosition="0, 0" />
<local:Node CurrentPosition="150, 150" />
</local:DrawingArea>
Объект рисования Node
. Он расширяет Thumb
, чтобы поддерживать перетаскивание
class Node : Thumb
{
public static readonly DependencyProperty CurrentPositionProperty = DependencyProperty.Register(
"CurrentPosition",
typeof(Point),
typeof(Node),
new PropertyMetadata(default(Point), OnCurrentPositionChanged));
public Point CurrentPosition { get { return (Point) GetValue(Node.CurrentPositionProperty); } set { SetValue(Node.CurrentPositionProperty, value); } }
public static readonly DependencyProperty ChildNodesProperty = DependencyProperty.Register(
"ChildNodes",
typeof(ObservableCollection<Node>),
typeof(Node),
new PropertyMetadata(default(ObservableCollection<Node>)));
public ObservableCollection<Node> ChildNodes { get { return (ObservableCollection<Node>) GetValue(Node.ChildNodesProperty); } set { SetValue(Node.ChildNodesProperty, value); } }
public static readonly DependencyProperty DrawingAreaProperty = DependencyProperty.Register(
"DrawingArea",
typeof(DrawingArea),
typeof(Node),
new PropertyMetadata(default(DrawingArea)));
public DrawingArea DrawingArea { get { return (DrawingArea) GetValue(Node.DrawingAreaProperty); } set { SetValue(Node.DrawingAreaProperty, value); } }
static Node()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Node), new FrameworkPropertyMetadata(typeof(Node)));
}
public Node()
{
this.DragDelta += MoveOnDragStarted;
Canvas.SetLeft(this, this.CurrentPosition.X);
Canvas.SetTop(this, this.CurrentPosition.Y);
}
private static void OnCurrentPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var _this = d as Node;
Canvas.SetLeft(_this, _this.CurrentPosition.X);
Canvas.SetTop(_this, _this.CurrentPosition.Y);
}
private void MoveOnDragStarted(object sender, DragDeltaEventArgs dragDeltaEventArgs)
{
if (this.DrawingArea.IsDrawing)
{
return;
}
this.CurrentPosition = new Point(Canvas.GetLeft(this) + dragDeltaEventArgs.HorizontalChange, Canvas.GetTop(this) + dragDeltaEventArgs.VerticalChange);
}
}
Стиль Node
. Добавьте это в ResourceDictionary
из Generic.xaml file
<Style TargetType="local:Node">
<Setter Property="Height" Value="100"/>
<Setter Property="Width" Value="100"/>
<Setter Property="IsHitTestVisible" Value="True"/>
<Setter Property="DrawingArea" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Canvas}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Node">
<Border Background="Red"></Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
DrawingArea
, который является расширенным Canvas
, который выполняет рисование линий (открывая возможности редактора)
class DrawingArea : Canvas
{
static DrawingArea()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DrawingArea), new FrameworkPropertyMetadata(typeof(DrawingArea)));
}
public DrawingArea()
{
this.TemporaryDrawingLine = new Line();
}
#region Overrides of UIElement
/// <inheritdoc />
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key.HasFlag(Key.LeftCtrl) || e.Key.HasFlag(Key.RightCtrl))
{
this.IsDrawing = true;
}
}
/// <inheritdoc />
protected override void OnPreviewKeyUp(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key.HasFlag(Key.LeftCtrl) || e.Key.HasFlag(Key.RightCtrl))
{
this.IsDrawing = false;
this.Children.Remove(this.TemporaryDrawingLine);
}
}
/// <inheritdoc />
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (!this.IsDrawing)
{
return;
}
if (!(e.Source is Node linkedItem))
{
return;
}
this.StartObject = linkedItem;
this.TemporaryDrawingLine = new Line()
{
X1 = this.StartObject.CurrentPosition.X, Y1 = this.StartObject.CurrentPosition.Y,
X2 = e.GetPosition(this).X, Y2 = e.GetPosition(this).Y,
StrokeDashArray = new DoubleCollection() { 5, 1, 1, 1}
};
this.Children.Add(this.TemporaryDrawingLine);
}
/// <inheritdoc />
protected override void OnPreviewMouseMove(MouseEventArgs e)
{
Focus();
if (!this.IsDrawing)
{
return;
}
this.TemporaryDrawingLine.X2 = e.GetPosition(this).X;
this.TemporaryDrawingLine.Y2 = e.GetPosition(this).Y ;
}
/// <inheritdoc />
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonUp(e);
if (!this.IsDrawing)
{
return;
}
if (!(e.Source is Node linkedItem))
{
this.Children.Remove(this.TemporaryDrawingLine);
this.IsDrawing = false;
return;
}
e.Handled = true;
this.Children.Remove(this.TemporaryDrawingLine);
var line = new Line();
var x1Binding = new Binding("CurrentPosition.X") {Source = this.StartObject};
var y1Binding = new Binding("CurrentPosition.Y") { Source = this.StartObject };
line.SetBinding(Line.X1Property, x1Binding);
line.SetBinding(Line.Y1Property, y1Binding);
this.EndObject = linkedItem;
var x2Binding = new Binding("CurrentPosition.X") { Source = this.EndObject };
var y2Binding = new Binding("CurrentPosition.Y") { Source = this.EndObject };
line.SetBinding(Line.X2Property, x2Binding);
line.SetBinding(Line.Y2Property, y2Binding);
this.Children.Add(line);
this.IsDrawing = false;
}
public bool IsDrawing { get; set; }
private Node EndObject { get; set; }
private Node StartObject { get; set; }
private Line TemporaryDrawingLine { get; set; }
#endregion
}