我正在尝试创建一个 WPF 应用程序,其中在 DataGrid 中有一个项目列表(在本例中为墙),并且我想要可视化项目(墙)的几何形状。我在下面附上了一张图片。
基本上,我的物品是有高度和长度的墙。墙壁应该堆叠在一起,如下图所示。 (在初始化时,它们整齐地堆叠在一起,只是因为这就是我手动初始化列表的方式。)
您在上面看到的主窗口由 3 列组成:
0 - 左列有一个包含 DataGrid 的堆栈面板。
1 - 中间列有一个 ItemsControl,其中 ItemsPanelTemplate 设置为画布。大红色方块显示了画布的轮廓,作为临时指南。
2 - 右栏目前不相关/未使用。
下面的代码显示了如何设置 ItemsControl/Canvas。基本上,我将墙可视化为边界,根据输入具有特定的尺寸和位置。我也可以使用矩形,但没有偶然发现任何显着的优点/缺点。
<ItemsControl Grid.Column="1"
ItemsSource="{Binding Walls, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="IndianRed"
Margin="20"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="{Binding XLength}"
Height="{Binding YHeight}"
Background="#CCCCCC"
BorderBrush="Black"
BorderThickness="1">
<TextBlock Text="{Binding Floor}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding XPos}"/>
<Setter Property="Canvas.Top" Value="{Binding YPos}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
这是我的解决方案资源管理器的图片,可以快速概览:
墙由此类定义:
基本上,高度和长度是墙的实际尺寸。我使用比例 50 来获取 XLength 和 YHeight,这是代表墙的边框的大小。同样,每面墙都有一个 XPos 和 YPos,并应用了相同的比例,这定义了它在画布上的位置。
public class Wall : INotifyPropertyChanged
{
private static int _scale = 50;
private string _floor; //Just a string of text displayed on each Border
private double _height; //Height of wall IRL
private double _length; //Lenght of wall IRL
private double _xPos; //Horizonal position on canvas (Canvas.Left)
private double _yPos; //Vertical position on canvas (Canvas.Top)
private double _xLength; //Width of Border on canvas (IRL Length * Scale)
private double _yHeight; //Height of Border on canvas (IRL Height * Scale)
public string Floor
{
get { return _floor; }
set { _floor = value; }
}
public double Height
{
get { return _height; }
set
{
_height = value;
_yHeight = value*_scale;
OnPropertyChanged("YHeight");
}
}
public double Length
{
get { return _length; }
set
{
_length = value;
_xLength = value*_scale;
OnPropertyChanged("XLength");
}
}
public double XPos
{
get { return _xPos; }
set { _xPos = value; }
}
public double YPos
{
get { return _yPos; }
set { _yPos = value; }
}
public double XLength
{
get { return _xLength; }
set { _xLength = Length * _scale; }
}
public double YHeight
{
get { return _yHeight; }
set { _yHeight = Height * _scale; }
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Wall的变量如下图描述:
在 XLength 和 YHeight 的属性中,我成功地应用了一些简单的公式来使长度和高度遵循。例如,当我更改 DataGrid 中的一堆值时,我可以得到如下内容:
但是,我希望更改 YPos 属性,以便墙壁始终堆叠在彼此的顶部。因此,如果我更改 4 楼的高度,则其下方的所有墙壁都需要更改其 YPos 值,依此类推。我没有成功,请求您的帮助!
我尝试创建一种方法来执行以下操作:
public void OrderWalls()
{
for (int i = 0; i < _walls.Count; i++)
{
Walls[i].YPos = Walls[i-1].YPos + Walls[i].YHeight;
}
}
但是我无法完全理解如何在 YPos 属性中应用该方法。还是我的做法全错了?如有任何帮助,我们将不胜感激!
这是我的 ViewModel,以及主窗口的代码隐藏:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ObservableCollection<Wall> _walls;
public ObservableCollection<Wall> Walls
{
get { return _walls; }
set
{
_walls = value;
OnPropertyChanged();
}
}
public void OrderWalls()
{
for (int i = 0; i < _walls.Count; i++)
{
Walls[i].YPos = Walls[i-1].YPos + Walls[i].YHeight;
}
}
public MainWindowViewModel()
{
Walls = new ObservableCollection<Wall>();
Walls.Add(new Wall { Floor = "Floor 4", Height = 3, Length = 6, YPos = 50, XPos = 50 });
Walls.Add(new Wall { Floor = "Floor 3", Height = 3, Length = 6, YPos = 200, XPos = 50 });
Walls.Add(new Wall { Floor = "Floor 2", Height = 3, Length = 6, YPos = 350, XPos = 50 });
Walls.Add(new Wall { Floor = "Floor 1", Height = 3, Length = 6, YPos = 500, XPos = 50 });
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
MainWindowViewModel viewModel = new MainWindowViewModel();
this.DataContext = viewModel;
InitializeComponent();
}
}
您的墙对象需要实现 INotifyPropertyChanged,并且您需要为每个对象订阅 PropertyChangedEvent。这里有一些东西可以为您指明正确的方向。
public class MainWindowViewModel : INotifyPropertyChanged
{
private ObservableCollection<Wall> _walls;
public ObservableCollection<Wall> Walls
{
get { return _walls; }
set
{
_walls = value;
OnPropertyChanged();
}
}
public void OrderWalls()
{
for (int i = 0; i < _walls.Count; i++)
{
Walls[i].YPos = Walls[i-1].YPos + Walls[i].YHeight;
}
}
public MainWindowViewModel()
{
Walls = new ObservableCollection<Wall>();
Walls.Add(new Wall { Floor = "Floor 4", Height = 3, Length = 6, YPos = 50, XPos = 50 });
Walls.Add(new Wall { Floor = "Floor 3", Height = 3, Length = 6, YPos = 200, XPos = 50 });
Walls.Add(new Wall { Floor = "Floor 2", Height = 3, Length = 6, YPos = 350, XPos = 50 });
Walls.Add(new Wall { Floor = "Floor 1", Height = 3, Length = 6, YPos = 500, XPos = 50 });
foreach(var wall in Walls)
{
wall.PropertyChanged += WallChanged; // subscribe to all changes.
}
}
private void WallChanged(object sender, PropertyChangedEventArgs e)
{
// May want to be selective on what changes you are worried
// about. Only run when the height changes for instance.
OrderWalls(); // Something changed update the y's.
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}