在 WPF 中,我需要有一组绘图对象的集合
in WPF I need to have a collection of a collection of drawing objects
我有一个 WPF 项目,它在一个面板中绘制了几个东西。对于下一个版本,除了现有的东西之外,我还需要添加另一种类型的东西来绘制。目前我有一个包含 ItemsControl 的网格,其中包含一个 ItemsPanel 和一个 ItemsSource。现有的 ItemsSource 看起来像这样:
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=DottedLines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=BarrierLines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=ProjectedLines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=ProjectedCranes,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=CraneConfigs,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Sightlines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=CraneCenters,
Mode=OneWay}"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
大部分集合都是直线或多边形。我定义了 DataTemplates 以将绘图对象的属性绑定到支持对象。 BarrierLine 对象的示例如下所示:
<DataTemplate DataType="{x:Type c:BarrierLineArt}">
<Line
X1="{Binding Path=AX}"
Y1="{Binding Path=AY}"
X2="{Binding Path=BX}"
Y2="{Binding Path=BY}"
Stroke="{Binding Path=LineColor}"
StrokeThickness="{Binding Path=ScaledWeight}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round">
</Line>
</DataTemplate>
一切正常。现在,除了现有的东西之外,我还需要添加要绘制的东西的集合。这个新东西有线的集合和平移旋转值。不幸的是,我需要收集这些新事物。每个实例都有自己的平移和旋转以及线条集合。实际上,我现在有一组行的集合。有什么方法可以嵌套 CollectionContainer 吗?我应该尝试向 DataTemplate 添加一个集合吗?我不知道该往哪个方向走。
编辑:
好的,我创建了一个概念验证程序,希望能满足 Peter 的要求。我将在下面提供代码。它由五个文件组成:
LineArt.cs
ObstacleArt.cs
MainWindowResource.cs
MainWindow.xaml.cs
MainWindow.xaml
LineArt 对象表示绘制单条线所需的数据。 ObstacleArt 对象表示线条的集合以及这些线条的平移和旋转。我希望能够绘制一组线(没有平移或旋转)加上一组障碍,每个障碍都有一组线。
我尝试使用 Peter 的建议将 CompositeCollection 放入 CompositeCollection 中的 CollectionContainer 的 Collection 属性 中。您可以在靠近底部的 xaml 文件中看到它。
在集合的路径属性中,当我输入"Obstacles[0].Lines"时,它会为第一个障碍画线,但是当我取出索引并说"Obstacles.Lines"时不会画任何东西。我需要的是画出所有障碍物的所有线条的能力。
除此之外,我还需要为每组线条设置平移和旋转。
这是文件,你应该可以编译并且运行这个。
LineArt.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace POC_WPF_nestedDrawingObjects
{
public class LineArt : INotifyPropertyChanged
{
private Int32 _id = int.MinValue;
private double _ax = 0;
private double _ay = 0;
private double _bx = 0;
private double _by = 0;
private SolidColorBrush _lineColor = Brushes.Black;
private double _weight = 1;
private double _scaledWeight = 1;
public LineArt(
Int32 id,
double ax,
double ay,
double bx,
double by,
SolidColorBrush lineColor)
{
_id = id;
_ax = ax;
_ay = ay;
_bx = bx;
_by = by;
_lineColor = lineColor;
_weight = 1;
_scaledWeight = _weight;
}
public Int32 Id { get { return _id; } }
public double AX
{
get { return _ax; }
set
{
_ax = value;
SetPropertyChanged("AX");
}
}
public double AY
{
get { return _ay; }
set
{
_ay = value;
SetPropertyChanged("AY");
}
}
public double BX
{
get { return _bx; }
set
{
_bx = value;
SetPropertyChanged("BX");
}
}
public double BY
{
get { return _by; }
set
{
_by = value;
SetPropertyChanged("BY");
}
}
public SolidColorBrush LineColor
{
get { return _lineColor; }
set
{
_lineColor = value;
SetPropertyChanged("LineColor");
}
}
public double Weight
{
get { return _weight; }
set
{
_weight = value;
SetPropertyChanged("Weight");
}
}
public double ScaledWeight
{
get { return _scaledWeight; }
set
{
_scaledWeight = value;
SetPropertyChanged("ScaledWeight");
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void SetPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(
this,
new PropertyChangedEventArgs(prop));
}
}
#endregion INotifyPropertyChanged implementation
}
}
ObstacleArt.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace POC_WPF_nestedDrawingObjects
{
public class ObstacleArt : INotifyPropertyChanged
{
private Int32 _id = int.MinValue;
private ObservableCollection<LineArt> _lines
= new ObservableCollection<LineArt>();
private double _translateX = 0;
private double _translateY = 0;
private double _rotateAngle = 0;
public ObstacleArt(
Int32 id,
double translateX,
double translateY,
double rotateAngle)
{
_id = id;
_translateX = translateX;
_translateY = translateY;
_rotateAngle = rotateAngle;
}
public Int32 Id
{
get { return _id; }
}
public double TranslateX
{
get { return _translateX; }
set
{
_translateX = value;
SetPropertyChanged("TranslateX");
}
}
public double TranslateY
{
get { return _translateX; }
set
{
_translateX = value;
SetPropertyChanged("TranslateX");
}
}
public double RotateAngle
{
get { return _rotateAngle; }
set
{
_rotateAngle = value;
SetPropertyChanged("RotateAngle");
}
}
public ObservableCollection<LineArt> Lines
{
get { return _lines; }
}
public void NotifyLinesChanged()
{
SetPropertyChanged("Lines");
}
public void LinesAddPropertyChangedHandlers()
{
foreach (LineArt line in _lines)
{
line.PropertyChanged += line_PropertyChanged;
}
}
private void line_PropertyChanged(
object sender,
PropertyChangedEventArgs e)
{
SetPropertyChanged(e.PropertyName);
}
public void LinesDelPropertyChangedHandlers()
{
foreach (LineArt line in _lines)
{
line.PropertyChanged -= line_PropertyChanged;
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void SetPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(
this,
new PropertyChangedEventArgs(prop));
}
}
#endregion INotifyPropertyChanged implementation
}
}
MainWindowResource.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace POC_WPF_nestedDrawingObjects
{
public class MainWindowResource : INotifyPropertyChanged
{
private ObservableCollection<LineArt> _lines
= new ObservableCollection<LineArt>();
private ObservableCollection<ObstacleArt> _obstacles
= new ObservableCollection<ObstacleArt>();
public ObservableCollection<LineArt> Lines
{
get
{
return _lines;
}
}
public ObservableCollection<ObstacleArt> Obstacles
{
get
{
return _obstacles;
}
}
public void NotifyLinesChanged()
{
SetPropertyChanged("Lines");
}
public void NotifyObstaclesChanged()
{
SetPropertyChanged("Obstacles");
}
public void LinesAddPropertyChangedHandlers()
{
foreach (LineArt line in _lines)
{
line.PropertyChanged += line_PropertyChanged;
}
}
public void LinesDelPropertyChangedHandlers()
{
foreach (LineArt line in _lines)
{
line.PropertyChanged -= line_PropertyChanged;
}
}
private void line_PropertyChanged(
object sender,
PropertyChangedEventArgs e)
{
SetPropertyChanged(e.PropertyName);
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void SetPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(
this,
new PropertyChangedEventArgs(prop));
}
}
#endregion INotifyPropertyChanged implementation
}
}
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace POC_WPF_nestedDrawingObjects
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainWindowResource _mainWindowResource = null;
private SolidColorBrush _brush
= new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
public MainWindow()
{
LineArt line = null;
ObstacleArt obstacle = null;
InitializeComponent();
Application app = Application.Current;
_mainWindowResource
= (MainWindowResource)this.Resources["MainWindowResource"];
// initialize four lines, they will form a rectangle
_mainWindowResource.LinesDelPropertyChangedHandlers();
line = new LineArt(1, 10, 10, 30, 10, _brush);
_mainWindowResource.Lines.Add(line);
line = new LineArt(2, 30, 10, 30, 20, _brush);
_mainWindowResource.Lines.Add(line);
line = new LineArt(2, 30, 20, 10, 20, _brush);
_mainWindowResource.Lines.Add(line);
line = new LineArt(2, 10, 20, 10, 10, _brush);
_mainWindowResource.Lines.Add(line);
_mainWindowResource.LinesAddPropertyChangedHandlers();
_mainWindowResource.NotifyLinesChanged();
// initialize an obstacle made of four lines.
// the lines form a 20x20 square around 0,0.
// the obstacle should be trastlated to 50,50
// and not rotated
obstacle = new ObstacleArt(1, 50, 50, 0);
obstacle.LinesDelPropertyChangedHandlers();
line = new LineArt(1, -10, 10, 10, 10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(2, 10, 10, 10, -10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(3, 10, -10, -10, -10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(4, -10, -10, -10, 10, _brush);
obstacle.Lines.Add(line);
obstacle.LinesAddPropertyChangedHandlers();
_mainWindowResource.Obstacles.Add(obstacle);
// initialize an obstacle made of four lines.
// the lines form a 20x20 square around 0,0.
// the obstacle should be trastlated to 100,100
// and rotated to a 45 degree angle
obstacle = new ObstacleArt(1, 100, 100, 45);
obstacle.LinesDelPropertyChangedHandlers();
line = new LineArt(1, -10, 10, 10, 10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(2, 10, 10, 10, -10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(3, 10, -10, -10, -10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(4, -10, -10, -10, 10, _brush);
obstacle.Lines.Add(line);
obstacle.LinesAddPropertyChangedHandlers();
_mainWindowResource.Obstacles.Add(obstacle);
_mainWindowResource.NotifyObstaclesChanged();
_mainWindowResource.NotifyLinesChanged();
foreach(ObstacleArt obstacleArt in _mainWindowResource.Obstacles)
{
obstacleArt.NotifyLinesChanged();
}
}
}
}
MainWindow.xaml
<Window x:Class="POC_WPF_nestedDrawingObjects.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:POC_WPF_nestedDrawingObjects"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<c:MainWindowResource x:Key="MainWindowResource"/>
<DataTemplate DataType="{x:Type c:LineArt}">
<Line
X1="{Binding Path=AX}"
Y1="{Binding Path=AY}"
X2="{Binding Path=BX}"
Y2="{Binding Path=BY}"
Stroke="{Binding Path=LineColor}"
StrokeThickness="{Binding Path=ScaledWeight}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round">
</Line>
</DataTemplate>
<Style x:Key="ContentCanvasStyle" TargetType="Canvas">
<Setter Property="RenderTransformOrigin" Value="0,0"/>
</Style>
</Window.Resources>
<Grid>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="ContentCanvas"
Style="{StaticResource ContentCanvasStyle}">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Lines,
Mode=OneWay}"/>
<CollectionContainer>
<CollectionContainer.Collection>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Obstacles[0].Lines,
Mode=OneWay}"/>
</CompositeCollection>
</CollectionContainer.Collection>
</CollectionContainer>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</Grid>
</Window>
感谢您改进代码示例。由此看来,在我看来,您正在以完全错误的方式实现目标。
也就是说,您正试图让一个 ItemsControl
对象呈现您的所有线条。但这不是您的数据模型的组织方式。您的数据模型有两种完全不同的对象:LineArt
对象,以及包含 LineArt
个对象的 ObstacleArt
对象。
鉴于此,在我看来更合适的方法是简单地组合 MainWindowResource.Lines
和 MainWindowResource.Obstacles
集合,然后使用数据模板将这些集合适当地显示在一起。
这是您的 XAML 的新版本,符合我的意思:
<Window x:Class="POC_WPF_nestedDrawingObjects.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:POC_WPF_nestedDrawingObjects"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<c:MainWindowResource x:Key="MainWindowResource"/>
<Style x:Key="ContentCanvasStyle" TargetType="Canvas">
<Setter Property="RenderTransformOrigin" Value="0,0"/>
</Style>
<DataTemplate DataType="{x:Type c:LineArt}">
<Line
X1="{Binding Path=AX}"
Y1="{Binding Path=AY}"
X2="{Binding Path=BX}"
Y2="{Binding Path=BY}"
Stroke="{Binding Path=LineColor}"
StrokeThickness="{Binding Path=ScaledWeight}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round">
</Line>
</DataTemplate>
<DataTemplate DataType="{x:Type c:ObstacleArt}">
<ItemsControl ItemsSource="{Binding Lines, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="ContentCanvas"
Style="{StaticResource ContentCanvasStyle}">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding RotateAngle}"/>
<TranslateTransform X="{Binding TranslateX}" Y="{Binding TranslateY}"/>
</TransformGroup>
</ItemsControl.RenderTransform>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="ContentCanvas"
Style="{StaticResource ContentCanvasStyle}">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Lines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Obstacles,
Mode=OneWay}"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</Grid>
</Window>
此处的键是第二个 DataTemplate
,目标类型为 ObstacleArt
。这允许主 ItemsControl
显示复合集合中的单个 ObstacleArt
元素。通过第二个数据模板,它通过为每个 ObstacleArt
对象嵌套一个全新的 ItemsControl
来实现,其中 ItemsControl
处理 ObstacleArt
对象的所有渲染。请注意,由于嵌套 ItemsControl
对象中的实际 项 本身就是 LineArt
项,因此最终会引用 DataTemplate
以获取 LineArt
类型。
以上内容无需对您的代码隐藏进行任何更改即可运行。也就是说,我认为您最好让 类 继承 DependencyObject
,然后使可绑定属性成为依赖属性。 WPF 当然支持 INotifyPropertyChanged
,但是你有很多明确的 属性 通知代码,如果你使用 dependency-属性 范例,这些代码就会消失。
我有一个 WPF 项目,它在一个面板中绘制了几个东西。对于下一个版本,除了现有的东西之外,我还需要添加另一种类型的东西来绘制。目前我有一个包含 ItemsControl 的网格,其中包含一个 ItemsPanel 和一个 ItemsSource。现有的 ItemsSource 看起来像这样:
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=DottedLines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=BarrierLines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=ProjectedLines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=ProjectedCranes,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=CraneConfigs,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Sightlines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=CraneCenters,
Mode=OneWay}"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
大部分集合都是直线或多边形。我定义了 DataTemplates 以将绘图对象的属性绑定到支持对象。 BarrierLine 对象的示例如下所示:
<DataTemplate DataType="{x:Type c:BarrierLineArt}">
<Line
X1="{Binding Path=AX}"
Y1="{Binding Path=AY}"
X2="{Binding Path=BX}"
Y2="{Binding Path=BY}"
Stroke="{Binding Path=LineColor}"
StrokeThickness="{Binding Path=ScaledWeight}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round">
</Line>
</DataTemplate>
一切正常。现在,除了现有的东西之外,我还需要添加要绘制的东西的集合。这个新东西有线的集合和平移旋转值。不幸的是,我需要收集这些新事物。每个实例都有自己的平移和旋转以及线条集合。实际上,我现在有一组行的集合。有什么方法可以嵌套 CollectionContainer 吗?我应该尝试向 DataTemplate 添加一个集合吗?我不知道该往哪个方向走。
编辑:
好的,我创建了一个概念验证程序,希望能满足 Peter 的要求。我将在下面提供代码。它由五个文件组成: LineArt.cs ObstacleArt.cs MainWindowResource.cs MainWindow.xaml.cs MainWindow.xaml
LineArt 对象表示绘制单条线所需的数据。 ObstacleArt 对象表示线条的集合以及这些线条的平移和旋转。我希望能够绘制一组线(没有平移或旋转)加上一组障碍,每个障碍都有一组线。
我尝试使用 Peter 的建议将 CompositeCollection 放入 CompositeCollection 中的 CollectionContainer 的 Collection 属性 中。您可以在靠近底部的 xaml 文件中看到它。
在集合的路径属性中,当我输入"Obstacles[0].Lines"时,它会为第一个障碍画线,但是当我取出索引并说"Obstacles.Lines"时不会画任何东西。我需要的是画出所有障碍物的所有线条的能力。
除此之外,我还需要为每组线条设置平移和旋转。
这是文件,你应该可以编译并且运行这个。
LineArt.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace POC_WPF_nestedDrawingObjects
{
public class LineArt : INotifyPropertyChanged
{
private Int32 _id = int.MinValue;
private double _ax = 0;
private double _ay = 0;
private double _bx = 0;
private double _by = 0;
private SolidColorBrush _lineColor = Brushes.Black;
private double _weight = 1;
private double _scaledWeight = 1;
public LineArt(
Int32 id,
double ax,
double ay,
double bx,
double by,
SolidColorBrush lineColor)
{
_id = id;
_ax = ax;
_ay = ay;
_bx = bx;
_by = by;
_lineColor = lineColor;
_weight = 1;
_scaledWeight = _weight;
}
public Int32 Id { get { return _id; } }
public double AX
{
get { return _ax; }
set
{
_ax = value;
SetPropertyChanged("AX");
}
}
public double AY
{
get { return _ay; }
set
{
_ay = value;
SetPropertyChanged("AY");
}
}
public double BX
{
get { return _bx; }
set
{
_bx = value;
SetPropertyChanged("BX");
}
}
public double BY
{
get { return _by; }
set
{
_by = value;
SetPropertyChanged("BY");
}
}
public SolidColorBrush LineColor
{
get { return _lineColor; }
set
{
_lineColor = value;
SetPropertyChanged("LineColor");
}
}
public double Weight
{
get { return _weight; }
set
{
_weight = value;
SetPropertyChanged("Weight");
}
}
public double ScaledWeight
{
get { return _scaledWeight; }
set
{
_scaledWeight = value;
SetPropertyChanged("ScaledWeight");
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void SetPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(
this,
new PropertyChangedEventArgs(prop));
}
}
#endregion INotifyPropertyChanged implementation
}
}
ObstacleArt.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace POC_WPF_nestedDrawingObjects
{
public class ObstacleArt : INotifyPropertyChanged
{
private Int32 _id = int.MinValue;
private ObservableCollection<LineArt> _lines
= new ObservableCollection<LineArt>();
private double _translateX = 0;
private double _translateY = 0;
private double _rotateAngle = 0;
public ObstacleArt(
Int32 id,
double translateX,
double translateY,
double rotateAngle)
{
_id = id;
_translateX = translateX;
_translateY = translateY;
_rotateAngle = rotateAngle;
}
public Int32 Id
{
get { return _id; }
}
public double TranslateX
{
get { return _translateX; }
set
{
_translateX = value;
SetPropertyChanged("TranslateX");
}
}
public double TranslateY
{
get { return _translateX; }
set
{
_translateX = value;
SetPropertyChanged("TranslateX");
}
}
public double RotateAngle
{
get { return _rotateAngle; }
set
{
_rotateAngle = value;
SetPropertyChanged("RotateAngle");
}
}
public ObservableCollection<LineArt> Lines
{
get { return _lines; }
}
public void NotifyLinesChanged()
{
SetPropertyChanged("Lines");
}
public void LinesAddPropertyChangedHandlers()
{
foreach (LineArt line in _lines)
{
line.PropertyChanged += line_PropertyChanged;
}
}
private void line_PropertyChanged(
object sender,
PropertyChangedEventArgs e)
{
SetPropertyChanged(e.PropertyName);
}
public void LinesDelPropertyChangedHandlers()
{
foreach (LineArt line in _lines)
{
line.PropertyChanged -= line_PropertyChanged;
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void SetPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(
this,
new PropertyChangedEventArgs(prop));
}
}
#endregion INotifyPropertyChanged implementation
}
}
MainWindowResource.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace POC_WPF_nestedDrawingObjects
{
public class MainWindowResource : INotifyPropertyChanged
{
private ObservableCollection<LineArt> _lines
= new ObservableCollection<LineArt>();
private ObservableCollection<ObstacleArt> _obstacles
= new ObservableCollection<ObstacleArt>();
public ObservableCollection<LineArt> Lines
{
get
{
return _lines;
}
}
public ObservableCollection<ObstacleArt> Obstacles
{
get
{
return _obstacles;
}
}
public void NotifyLinesChanged()
{
SetPropertyChanged("Lines");
}
public void NotifyObstaclesChanged()
{
SetPropertyChanged("Obstacles");
}
public void LinesAddPropertyChangedHandlers()
{
foreach (LineArt line in _lines)
{
line.PropertyChanged += line_PropertyChanged;
}
}
public void LinesDelPropertyChangedHandlers()
{
foreach (LineArt line in _lines)
{
line.PropertyChanged -= line_PropertyChanged;
}
}
private void line_PropertyChanged(
object sender,
PropertyChangedEventArgs e)
{
SetPropertyChanged(e.PropertyName);
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void SetPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(
this,
new PropertyChangedEventArgs(prop));
}
}
#endregion INotifyPropertyChanged implementation
}
}
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace POC_WPF_nestedDrawingObjects
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainWindowResource _mainWindowResource = null;
private SolidColorBrush _brush
= new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
public MainWindow()
{
LineArt line = null;
ObstacleArt obstacle = null;
InitializeComponent();
Application app = Application.Current;
_mainWindowResource
= (MainWindowResource)this.Resources["MainWindowResource"];
// initialize four lines, they will form a rectangle
_mainWindowResource.LinesDelPropertyChangedHandlers();
line = new LineArt(1, 10, 10, 30, 10, _brush);
_mainWindowResource.Lines.Add(line);
line = new LineArt(2, 30, 10, 30, 20, _brush);
_mainWindowResource.Lines.Add(line);
line = new LineArt(2, 30, 20, 10, 20, _brush);
_mainWindowResource.Lines.Add(line);
line = new LineArt(2, 10, 20, 10, 10, _brush);
_mainWindowResource.Lines.Add(line);
_mainWindowResource.LinesAddPropertyChangedHandlers();
_mainWindowResource.NotifyLinesChanged();
// initialize an obstacle made of four lines.
// the lines form a 20x20 square around 0,0.
// the obstacle should be trastlated to 50,50
// and not rotated
obstacle = new ObstacleArt(1, 50, 50, 0);
obstacle.LinesDelPropertyChangedHandlers();
line = new LineArt(1, -10, 10, 10, 10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(2, 10, 10, 10, -10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(3, 10, -10, -10, -10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(4, -10, -10, -10, 10, _brush);
obstacle.Lines.Add(line);
obstacle.LinesAddPropertyChangedHandlers();
_mainWindowResource.Obstacles.Add(obstacle);
// initialize an obstacle made of four lines.
// the lines form a 20x20 square around 0,0.
// the obstacle should be trastlated to 100,100
// and rotated to a 45 degree angle
obstacle = new ObstacleArt(1, 100, 100, 45);
obstacle.LinesDelPropertyChangedHandlers();
line = new LineArt(1, -10, 10, 10, 10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(2, 10, 10, 10, -10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(3, 10, -10, -10, -10, _brush);
obstacle.Lines.Add(line);
line = new LineArt(4, -10, -10, -10, 10, _brush);
obstacle.Lines.Add(line);
obstacle.LinesAddPropertyChangedHandlers();
_mainWindowResource.Obstacles.Add(obstacle);
_mainWindowResource.NotifyObstaclesChanged();
_mainWindowResource.NotifyLinesChanged();
foreach(ObstacleArt obstacleArt in _mainWindowResource.Obstacles)
{
obstacleArt.NotifyLinesChanged();
}
}
}
}
MainWindow.xaml
<Window x:Class="POC_WPF_nestedDrawingObjects.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:POC_WPF_nestedDrawingObjects"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<c:MainWindowResource x:Key="MainWindowResource"/>
<DataTemplate DataType="{x:Type c:LineArt}">
<Line
X1="{Binding Path=AX}"
Y1="{Binding Path=AY}"
X2="{Binding Path=BX}"
Y2="{Binding Path=BY}"
Stroke="{Binding Path=LineColor}"
StrokeThickness="{Binding Path=ScaledWeight}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round">
</Line>
</DataTemplate>
<Style x:Key="ContentCanvasStyle" TargetType="Canvas">
<Setter Property="RenderTransformOrigin" Value="0,0"/>
</Style>
</Window.Resources>
<Grid>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="ContentCanvas"
Style="{StaticResource ContentCanvasStyle}">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Lines,
Mode=OneWay}"/>
<CollectionContainer>
<CollectionContainer.Collection>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Obstacles[0].Lines,
Mode=OneWay}"/>
</CompositeCollection>
</CollectionContainer.Collection>
</CollectionContainer>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</Grid>
</Window>
感谢您改进代码示例。由此看来,在我看来,您正在以完全错误的方式实现目标。
也就是说,您正试图让一个 ItemsControl
对象呈现您的所有线条。但这不是您的数据模型的组织方式。您的数据模型有两种完全不同的对象:LineArt
对象,以及包含 LineArt
个对象的 ObstacleArt
对象。
鉴于此,在我看来更合适的方法是简单地组合 MainWindowResource.Lines
和 MainWindowResource.Obstacles
集合,然后使用数据模板将这些集合适当地显示在一起。
这是您的 XAML 的新版本,符合我的意思:
<Window x:Class="POC_WPF_nestedDrawingObjects.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:POC_WPF_nestedDrawingObjects"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<c:MainWindowResource x:Key="MainWindowResource"/>
<Style x:Key="ContentCanvasStyle" TargetType="Canvas">
<Setter Property="RenderTransformOrigin" Value="0,0"/>
</Style>
<DataTemplate DataType="{x:Type c:LineArt}">
<Line
X1="{Binding Path=AX}"
Y1="{Binding Path=AY}"
X2="{Binding Path=BX}"
Y2="{Binding Path=BY}"
Stroke="{Binding Path=LineColor}"
StrokeThickness="{Binding Path=ScaledWeight}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round">
</Line>
</DataTemplate>
<DataTemplate DataType="{x:Type c:ObstacleArt}">
<ItemsControl ItemsSource="{Binding Lines, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="ContentCanvas"
Style="{StaticResource ContentCanvasStyle}">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding RotateAngle}"/>
<TranslateTransform X="{Binding TranslateX}" Y="{Binding TranslateY}"/>
</TransformGroup>
</ItemsControl.RenderTransform>
</ItemsControl>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="ContentCanvas"
Style="{StaticResource ContentCanvasStyle}">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Lines,
Mode=OneWay}"/>
<CollectionContainer
Collection="{Binding
Source={StaticResource MainWindowResource},
Path=Obstacles,
Mode=OneWay}"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</Grid>
</Window>
此处的键是第二个 DataTemplate
,目标类型为 ObstacleArt
。这允许主 ItemsControl
显示复合集合中的单个 ObstacleArt
元素。通过第二个数据模板,它通过为每个 ObstacleArt
对象嵌套一个全新的 ItemsControl
来实现,其中 ItemsControl
处理 ObstacleArt
对象的所有渲染。请注意,由于嵌套 ItemsControl
对象中的实际 项 本身就是 LineArt
项,因此最终会引用 DataTemplate
以获取 LineArt
类型。
以上内容无需对您的代码隐藏进行任何更改即可运行。也就是说,我认为您最好让 类 继承 DependencyObject
,然后使可绑定属性成为依赖属性。 WPF 当然支持 INotifyPropertyChanged
,但是你有很多明确的 属性 通知代码,如果你使用 dependency-属性 范例,这些代码就会消失。