C# WPF – Как обновить все элементы в списке на основе изменения одного из них и заставить привязки работать?C#

Место общения программистов C#
Anonymous
C# WPF – Как обновить все элементы в списке на основе изменения одного из них и заставить привязки работать?

Сообщение Anonymous »

Я пытаюсь создать приложение WPF, в котором у меня есть список элементов (в данном случае стен) в DataGrid, и я хочу визуализировать геометрию элементов (стен). Я прикрепил изображение ниже.
По сути, мои элементы — это стены, имеющие высоту и длину. Стены должны быть наложены друг на друга, как на изображении ниже. (При инициализации они аккуратно сложены друг на друга только потому, что я вручную инициализировал свой список именно так.)
MainWindow
Главное окно, которое вы видите выше состоит из трех столбцов:
0 — в левом столбце есть панель стека, содержащая DataGrid.
1 — в среднем столбце есть ItemsControl. , при этом для ItemsPanelTemplate установлено значение холста. Большой красный квадрат показывает контур холста в качестве временного ориентира.
2 – Правый столбец пока неактуален/не используется.
В приведенном ниже коде показано, как настраивается ItemsControl/Canvas. По сути, я визуализирую стены как границы с определенным размером и положением в зависимости от введенных данных. Я мог бы использовать и прямоугольники, но не нашел существенных плюсов/минусов.





















Вот изображение моего обозревателя решений для краткого обзора:
Обозреватель решений
Стены определяются этим классом:
По сути, высота и длина — это размеры стены в реальной жизни. Я использую шкалу 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));
}
}

Переменные стены описаны на этом рисунке:
Описание XLength, YHeight, XPos, YPos
В свойствах XLength и YHeight я успешно применил несколько простых формул, чтобы длина и высота соответствовали друг другу. Например, когда я меняю несколько значений в DataGrid, я могу получить что-то вроде этого:
MainWindow, когда значения в DataGrid изменились
Однако , я хотел бы изменить свойство YPos, чтобы стены всегда располагались друг над другом. Итак, если я изменю высоту четвертого этажа, все стены ниже него должны будут изменить свое значение YPos и ​​так далее. Мне это не удалось, и я прошу вашей помощи!
Я пытался создать метод, который делает что-то вроде этого:
public void OrderWalls()
{
for (int i = 0; i < _walls.Count; i++)
{
Walls.YPos = Walls[i-1].YPos + Walls.YHeight;
}
}

Но я не могу понять, как применить этот метод в свойстве YPos. Или я все делаю неправильно? Любая помощь приветствуется!
Вот моя ViewModel и код моего главного окна:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ObservableCollection _walls;

public ObservableCollection Walls
{
get { return _walls; }
set
{
_walls = value;
OnPropertyChanged();
}
}

public void OrderWalls()
{
for (int i = 0; i < _walls.Count; i++)
{
Walls.YPos = Walls[i-1].YPos + Walls.YHeight;
}
}

public MainWindowViewModel()
{
Walls = new ObservableCollection();

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();
}
}


Изменить комментарий, сделанный в @BionicCode 11 июля:Итак, я потратил немало времени, пытаясь реализовать ваш ответ. Вы используете множество функций, о которых я раньше не знал, поэтому мне пришлось провести небольшое исследование. Кажется, я уже многое понял, но я еще точно не там.
Я пытался внедрить ваши предложения в свою программу, но без свойства "ориентация", так как я просто хочу пока оставить его вертикальным (стараясь не перегружать себя), а также без «IsArrangeItemEnabled», так как я хочу, чтобы он всегда располагался. Так что я не совсем скопировал ваш код. Надеюсь, я не нарушил при этом ни одну запланированную функциональность.
После инициализации программы все YPos рассчитываются правильно. Однако когда я меняю значения в DataGrid, свойства YPos не обновляются, и я не могу понять, почему.
Я пытаюсь понять систему, с помощью которой все функции вызывают друг друга, и я не могу понять, что должно вызывать обновление свойств YPos. Я заметил, что в WallCollection.cs есть метод OnSizeChanged, который нигде не используется. Может ли это быть причиной?
Думаю, мне бы очень помогло, если бы вы немного подробнее рассказали о функциях, используемых в WallCollection.cs, и о том, как они связаны друг с другом. Меня особенно смущают StartOberveSizeChanges и StopObserveSizeChanges, а также Dictionary/indexmap. Если у вас нет времени, я вас полностью пойму :)
WallCollection.cs
public class WallCollection : ObservableCollection
{
public WallCollection() { }
private readonly Dictionary indexMap = new();

protected override void SetItem(int index, Wall item)
{
base.SetItem(index, item);
AddItemInternal(index, item);
}

protected override void InsertItem(int index, Wall item)
{
base.InsertItem(index, item);
AddItemInternal(index, item);
}

private void AddItemInternal(int index, Wall item)
{
if (!this.indexMap.ContainsKey(item))
{
StartObservingSizeChanges(item);
}
InitializeWallPosition(index, item);
}

private void InitializeWallPosition(int index, Wall item)
{
if (index > 0 && item is not null && double.IsInfinity(item.YPos))
{
Wall previousWall = this.Items[index - 1];
item.YPos = previousWall.YPos + previousWall.YHeight;
}
}

private void StartObservingSizeChanges(Wall item)
{
WeakEventManager.AddHandler(item, nameof(Wall.SizeChanged), OnWallSizeChanged);
}

private void OnWallSizeChanged(object? sender, WallSizeChangedEventArgs e)
{
var changedWall = (Wall)sender;
ArrangeWalls(changedWall, e.HasHeightChanged, e.HeightChange);
}

private void ArrangeWalls(Wall? changedWall, bool hasHeightChanged, double heightChange)
{
if (hasHeightChanged)
{
ArrangeItemsVertically(changedWall, heightChange);
}
}

private void ArrangeItemsVertically(Wall changedWall, double change)
{
if (!this.indexMap.TryGetValue(changedWall, out int indexOfChangedWall))
{
return;
}

for (int index = indexOfChangedWall + 1; index this.OldSize.Height != this.NewSize.Height;
public double HeightChange => this.NewSize.Height - this.OldSize.Height;
}

Wall.cs
public class Wall : INotifyPropertyChanged
{

public Wall()
{
this.YPos = double.PositiveInfinity;
}

private const int _scale = 50;
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)

public string Floor { get; set; }

public double Height
{
get { return _height; }
set
{
_height = value;
OnPropertyChanged(nameof(this.YHeight));
}
}

public double Length
{
get { return _length; }
set
{
_length = value;
OnPropertyChanged(nameof(this.XLength));
}
}

public double XPos
{
get { return _xPos; }
set
{
_xPos = value;
OnPropertyChanged();
}
}

public double YPos
{
get { return _yPos; }
set
{
_yPos = value;
OnPropertyChanged();
}
}

public double XLength => this.Length * _scale;

public double YHeight => this.Height * _scale;

public event PropertyChangedEventHandler? PropertyChanged; //Unchanged
public event EventHandler? SizeChanged; //New
protected void OnPropertyChanged([CallerMemberName] string name = null) //Unchanged
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

protected virtual void OnSizeChanged(Size oldSize, Size newSize)
=> this.SizeChanged?.Invoke(this, new WallSizeChangedEventArgs(oldSize, newSize));
}

MainWindowViewModel.cs
public class MainWindowViewModel : INotifyPropertyChanged
{
public ObservableCollection Walls { get; }
public MainWindowViewModel()
{
Walls = new WallCollection()
{
new Wall { Floor = "Floor 4", Height = 3, Length = 6, YPos = 50, XPos = 50 },
new Wall { Floor = "Floor 3", Height = 3, Length = 6, XPos = 50 },
new Wall { Floor = "Floor 2", Height = 3, Length = 6, XPos = 50 },
new Wall { Floor = "Floor 1", Height = 3, Length = 6, XPos = 50 }
};
}

public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}


Подробнее здесь: https://stackoverflow.com/questions/787 ... changing-a

Вернуться в «C#»