C# WPF - 如何根据其中一项的更改来更新列表中的所有项目,并使绑定起作用?

问题描述 投票:0回答:1

我正在尝试创建一个 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、XPos、YPos的说明

在 XLength 和 YHeight 的属性中,我成功地应用了一些简单的公式来使长度和高度遵循。例如,当我更改 DataGrid 中的一堆值时,我可以得到如下内容:

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();
    }
}
c# wpf mvvm
1个回答
0
投票

您的墙对象需要实现 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));
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.