如何将第二个 ItemCollection(可绑定)添加到 TreeViewItem?
How do I add a second ItemCollection (bindable) to a TreeViewItem?
我需要在 WPF 中获得第二个 ItemCollection
到 TreeViewItem
。为此,我想自定义 TreeViewItem
(继承正常的 TreeViewItem
)并添加使用 C# 硬编码的所需属性。执行此操作的步骤是什么?
为什么我需要这个?我正在尝试为现有的 WPF 程序(使用现有的 TreeView
)制作 FlowChartEditor
。
对于我的 if
元素,我需要一个 true 和一个 false 集合才能绑定。之后应该是这样的:
我制作了一个 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 中编写了我自己的样式后,我得到了这样的结果:
箭头绘制在 Canvas
上,用作 TreeView
上的 ItemsPanel
。明确地说,这是我想要得到的观点。 唯一的问题 是如何为 if
.
那么像第一张图片中的 if
项目需要哪些属性?以前有人做过吗?
您不需要修改 TreeViewItem
。这是关于你的数据结构是如何设计的。您需要对数据模型(或节点)进行分类或专门化,例如:
- 条件节点,必须包含两个子节点,代表每个分支。
- 普通表达式节点,只有一个子节点。
- 一个连接节点(多个父节点的连接),它有一个子节点但多个父节点(集合
节点)。
- 一个循环节点,它有一个子节点(正文)和附加属性,比如一组循环条件
- 一个迭代节点,它可能还有一个子节点(正文)和属性,比如一组迭代条件
如果你决定继续使用 TreeView
,我不建议这样做,那么你会使用 HierachicalDataTemplate
来设计 TreeView
的外观。
但是单独使用 TreeView
并使用节点控件绘制整棵树会更加灵活。您可以通过遍历树数据模型并将相应的视觉节点对象添加到绘图 canvas(a Control
你也可以模板和样式,给他们想要的外观)。当你让这些控件扩展Thumb
控件时,添加拖拽到可视化节点会非常容易。
以下代码是一个简单(丑陋)的示例,展示了如何通过按住 Ctrl-Key[=68= 来连接两个对象(Node
类型) ] 并在它们之间画一条线。有两个 Node
对象,它们已经创建并位于 canvas 上。稍后它们应该从节点对象池中拖到 canvas 或通过将图形转换为可视和连接的 Node
对象自动绘制到 canvas 。由于 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
风格。将此添加到 Generic.xaml 文件
的 ResourceDictionary
<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
}
我需要在 WPF 中获得第二个 ItemCollection
到 TreeViewItem
。为此,我想自定义 TreeViewItem
(继承正常的 TreeViewItem
)并添加使用 C# 硬编码的所需属性。执行此操作的步骤是什么?
为什么我需要这个?我正在尝试为现有的 WPF 程序(使用现有的 TreeView
)制作 FlowChartEditor
。
对于我的 if
元素,我需要一个 true 和一个 false 集合才能绑定。之后应该是这样的:
我制作了一个 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 中编写了我自己的样式后,我得到了这样的结果:
箭头绘制在 Canvas
上,用作 TreeView
上的 ItemsPanel
。明确地说,这是我想要得到的观点。 唯一的问题 是如何为 if
.
那么像第一张图片中的 if
项目需要哪些属性?以前有人做过吗?
您不需要修改 TreeViewItem
。这是关于你的数据结构是如何设计的。您需要对数据模型(或节点)进行分类或专门化,例如:
- 条件节点,必须包含两个子节点,代表每个分支。
- 普通表达式节点,只有一个子节点。
- 一个连接节点(多个父节点的连接),它有一个子节点但多个父节点(集合 节点)。
- 一个循环节点,它有一个子节点(正文)和附加属性,比如一组循环条件
- 一个迭代节点,它可能还有一个子节点(正文)和属性,比如一组迭代条件
如果你决定继续使用 TreeView
,我不建议这样做,那么你会使用 HierachicalDataTemplate
来设计 TreeView
的外观。
但是单独使用 TreeView
并使用节点控件绘制整棵树会更加灵活。您可以通过遍历树数据模型并将相应的视觉节点对象添加到绘图 canvas(a Control
你也可以模板和样式,给他们想要的外观)。当你让这些控件扩展Thumb
控件时,添加拖拽到可视化节点会非常容易。
以下代码是一个简单(丑陋)的示例,展示了如何通过按住 Ctrl-Key[=68= 来连接两个对象(Node
类型) ] 并在它们之间画一条线。有两个 Node
对象,它们已经创建并位于 canvas 上。稍后它们应该从节点对象池中拖到 canvas 或通过将图形转换为可视和连接的 Node
对象自动绘制到 canvas 。由于 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
风格。将此添加到 Generic.xaml 文件
ResourceDictionary
<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
}