Как добавить вторую коллекцию ItemCollection (bindable) в TreeViewItem? - PullRequest
0 голосов
/ 25 июня 2019

Мне нужно получить от ItemCollection второго до TreeViewItem в WPF. Чтобы сделать это, я хочу сделать пользовательский TreeViewItem (наследующий нормальный TreeViewItem) и добавить необходимые свойства, жестко запрограммированные в C #. Какие шаги сделать это?

Зачем мне это нужно? Я пытаюсь сделать FlowChartEditor для существующей программы WPF (с существующей TreeView).

Для моего элемента if мне нужно, чтобы коллекция true и false была привязываемой. Это должно выглядеть так потом:

'If' element diagram

Я сделал LoopItem, например, так:

public class LoopItem : TreeViewItem
{
    [Bindable(false)]
    [Browsable(false)]
    public bool HasFooter
    {
        get { return (bool)GetValue(HasFooterProperty); }
        private set { SetValue(HasFooterProperty, value); }
    }
    public static readonly DependencyProperty HasFooterProperty =
        DependencyProperty.Register(
            "HasFooter",
            typeof(bool),
            typeof(LoopItem),
            new PropertyMetadata(false
        );

    [Browsable(true)]
    [Bindable(true)]
    public object Footer
    {
        get { return (object)GetValue(FooterProperty); }
        set { SetValue(FooterProperty, value); }
    }
    public static readonly DependencyProperty FooterProperty =
        DependencyProperty.Register(
            "Footer",
            typeof(object),
            typeof(LoopItem),
            new PropertyMetadata(null)
        );
}

Теперь я смог связать не только заголовок, но и нижний колонтитул. После написания собственного стиля в XAML у меня получился такой результат:

Loop element diagram

Стрелки нарисованы на Canvas, который используется как ItemsPanel на TreeView. Чтобы было ясно, это мнение, которое я хотел получить. единственный вопрос - как это сделать для if.

Итак, какие свойства мне нужны для элемента if, как на первом изображении? Кто-нибудь делал это раньше?

1 Ответ

0 голосов
/ 25 июня 2019

Вам не нужно изменять 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
}
...