I am trying to make a WPF application where I have a list of items (in this case Walls) in a DataGrid, and I want to visualize the geometry of the items (Walls). I have attached an image below.
Basically, my items are Walls which have a height and a length. The walls are supposed to be stacked on top of each other, like in the image below. (At initialization they are neatly stacked, only because that is how I have manually initialized my list.)
MainWindow
The Main Window that you see above is made of 3 columns:
0 – The left column has a stackpanel which contains the DataGrid.
1 – The middle column has an ItemsControl, with the ItemsPanelTemplate set to a canvas. The big red square shows the outline of the canvas, as a temporary guide.
2 – The right column is irrelevant/unused for now.
The code below shows how the ItemsControl/Canvas is set up. Basically I visualize the Walls as borders, with a specific size and position based on the input. I could use rectangles as well, but have not stumbled upon any significant pros/cons.
<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>
Here is a picture of my Solution Explorer for a quick overview:
Solution Explorer
The Walls are defined by this class:
Basically, Height and Length are the IRL dimensions of the wall. I use a scale of 50 to get XLength and YHeight, which is the size of the border that represents weach wall. Similarly, each wall has a XPos and YPos with the same scale applied to it, which defines its position on the canvas.
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));
}
}
The variables of the Wall are described in this picture:
Description of XLength, YHeight, XPos, YPos
In the properties for XLength and YHeight I have successfully applied some simple formulas to make the length and height follow along. For instance, when I change a bunch of values in the DataGrid, I can get something like this:
MainWindow when values in DataGrid changed
However, I would like the YPos property to change, so that the walls are always stacked on top of each other. So if I change the height of floor 4, all the walls below it need to change their YPos value, and so on. I have not succeeded in this, and am asking for your help!
I tried to make a method which does something along the lines of this:
public void OrderWalls()
{
for (int i = 0; i < _walls.Count; i++)
{
Walls[i].YPos = Walls[i-1].YPos + Walls[i].YHeight;
}
}
But I can’t quite wrap my head around how I would apply the method within the property of YPos. Or am I going about it all wrong? Any help is appreciated!
Here is my ViewModel, and the codebehind of my Main Window:
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();
}
}
mmdn is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.