Как правильно привязать ObservableCollection внутри ObservableCollection к ItemsControl (WPF) ⇐ C#
Как правильно привязать ObservableCollection внутри ObservableCollection к ItemsControl (WPF)
Прежде чем я начну с объяснения проблемы, я новичок в WPF и еще не уловил всех концепций шаблона MVVM.
У меня есть ObservableCollection, где Vertex — это класс, представляющий вершину в графе. Он содержит свойства, представляющие его положение: X,Y и Point Position. Он также содержит ObservableCollection, где Edge — это простая модель, содержащая 2 вершины (от и до) и ее вес в виде целого числа.
Мое намерение состоит в том, чтобы отобразить как совокупность вершин (в виде эллипсов), так и ребра в виде линий. Представление, которое я пытаюсь создать, должно позволять добавлять эти вершины и ребра, что я и делаю с помощью команд, поскольку пытаюсь следовать архитектуре MVVM.
Вершины можно добавлять без проблем, однако проблемы начинаются, когда я пытаюсь добавить ребро из одной вершины в другую. Первая выбранная вершина ребра перемещается в совершенно другое место на виде (+ по осям x и y), а линия, представляющая ребро, имеет правильный наклон, но также перемещается в другое место.
Вот краткая демонстрация того, что я имею в виду: Ситуация перед добавлением ребра Ситуация после добавления ребра
Вот что у меня сейчас есть в файле xaml для представления с холстом (только важная часть с ItemsControl):
И вот что у меня есть в файле ViewModel:
публичный класс SceneEditorViewModel: BaseViewModel { режим частного перечисления { Режим добавления вершин, EdgeAdditionMode, Режим просмотра, Режим удаления вершин } частный только для чтения SceneEntry _sceneEntry; частный режим _mode = Mode.ViewMode; частный Вертекс? _firstClickedVertex; общедоступный график IGraph => _sceneEntry.Graph; /// /// Команды для кнопок /// общественный ICommand LoadFileCommand {получить; } общественный ICommand SetNameCommand {получить; } общественный ICommand BackCommand {получить; } общественный ICommand AddVertexCommand {get; } общественный ICommand AddEdgeCommand {получить; } общественный ICommand ClearCommand {получить; } общественный ICommand DeleteVertexCommand {get; } /// /// Команда вызывается после щелчка пользователя по области холста /// общественный ICommand CanvasClickCommand {получить; } общедоступная строка NameTextBox { получить => _sceneEntry.Name; набор { _sceneEntry.Name = значение; ПриИзмененииСвойства(); } } public SceneEditorViewModel (NavigationManager NavigationManager, SceneEntry? запись = null) { LoadFileCommand = новый RelayCommand (LoadFile, o => true); SetNameCommand = новая RelayCommand (SetName, o => true); BackCommand = new NavigationCommand(новый NavigationService(navigationManager, () => новая SceneMenuViewModel(navigationManager))); ДобавитьВертексКоманда = новая команда реле( o => _mode = _mode == Mode.VertexAdditionMode ? Mode.ViewMode : Mode.VertexAdditionMode, о => правда); AddEdgeCommand = новая команда реле( o => _mode = _mode == Mode.EdgeAdditionMode ? Mode.ViewMode : Mode.EdgeAdditionMode, о => правда); CanvasClickCommand = новый EventRelayCommand(OnMouseDown, o => true); DeleteVertexCommand = новый RelayCommand (DeleteVertex, o => true); _sceneEntry = запись ?? новый SceneEntry("", SceneMenuViewModel.SceneIdCounter++, новый Graph()); ClearCommand = new RelayCommand(o => _sceneEntry.Graph.Vertices.Clear(), o => true); если (запись равна нулю) { App.Scenes.Add(new SceneEntryViewModel(navigationManager, _sceneEntry)); } } частная пустота LoadFile (объект obj) { var openFileDialog = новый OpenFileDialog { Filter = "Файлы графиков (*.graph)|*.graph" }; если (openFileDialog.ShowDialog() == true) { вар файл = openFileDialog.FileName; вар сериализатор = новый GraphSerializer(); вар график = сериализатор.Десериализовать(файл); _sceneEntry.Graph = график; } //var сериализатор = новый сериализатор(); //var Graph = сериализатор.Deserialize(OpenFileDialog.FileName); //_graphViewModel.Graph = график; } частная пустота SetName (объект obj) { если (NameTextBox != "") { _sceneEntry.Name = NameTextBox; } } частная пустота DeleteVertex (объект obj) { _mode = _mode == Mode.VertexDeletionMode ? Mode.ViewMode : Mode.VertexDeletionMode; } частная пустота OnMouseDown (MouseButtonEventArgs e) { вар отправитель = e.Source как IInputElement; вар позиция = e.GetPosition(отправитель); переключатель (_mode) { Case Mode.VertexAdditionMode: var vertex = new Vertex("", Position.X, Position.Y, new ObservableCollection()); _sceneEntry.Graph.AddVertex(вершина); перерыв; Case Mode.EdgeAdditionMode: var first = _sceneEntry.Graph.Vertices.FirstOrDefault(v => v.X - 10 < позиция.X && позиция.X < v.X + 10 && v.Y - 10 < позиция.Y && позиция.Y < v.Y + 10); если (первое значение равно нулю) перерыв; если (_firstClickedVertex имеет значение null ) { first.IsSelected = true; _firstClickedVertex = первый; } еще { если (первый != _firstClickedVertex) { вар край = новый край() { FromVertex = новая вершина (_firstClickedVertex), ToVertex = новая вершина (первая), IsDirected = ложь, Вес = 1 }; _sceneEntry.Graph.Vertices.First(v => v == _firstClickedVertex).Edges.Add(edge); } _firstClickedVertex.IsSelected = ложь; _firstClickedVertex = ноль; } перерыв; Case Mode.VertexDeletionMode: var selected = _sceneEntry.Graph.Vertices.FirstOrDefault(v => v.X - 10 < позиция.X && позиция.X < v.X + 10 && v.Y - 10 < позиция.Y && позиция.Y < в.Й+10); если (выбрано значение null) перерыв; _sceneEntry.Graph.Vertices.Remove(выделено); перерыв; по умолчанию: перерыв; } } } Код действительно беспорядочный (в основном из-за попыток выяснить, в чем именно заключается проблема), но намерение должно быть несколько видно.
Класс Graph — это, по сути, просто класс, инкапсулирующий ObservableCollection, и все модели реализуют интерфейс INotifyPropertyChaned.
На правильном ли я пути, используя этот подход внутри файла xaml, или есть гораздо лучший способ сделать то, что я хочу?
Прежде чем я начну с объяснения проблемы, я новичок в WPF и еще не уловил всех концепций шаблона MVVM.
У меня есть ObservableCollection, где Vertex — это класс, представляющий вершину в графе. Он содержит свойства, представляющие его положение: X,Y и Point Position. Он также содержит ObservableCollection, где Edge — это простая модель, содержащая 2 вершины (от и до) и ее вес в виде целого числа.
Мое намерение состоит в том, чтобы отобразить как совокупность вершин (в виде эллипсов), так и ребра в виде линий. Представление, которое я пытаюсь создать, должно позволять добавлять эти вершины и ребра, что я и делаю с помощью команд, поскольку пытаюсь следовать архитектуре MVVM.
Вершины можно добавлять без проблем, однако проблемы начинаются, когда я пытаюсь добавить ребро из одной вершины в другую. Первая выбранная вершина ребра перемещается в совершенно другое место на виде (+ по осям x и y), а линия, представляющая ребро, имеет правильный наклон, но также перемещается в другое место.
Вот краткая демонстрация того, что я имею в виду: Ситуация перед добавлением ребра Ситуация после добавления ребра
Вот что у меня сейчас есть в файле xaml для представления с холстом (только важная часть с ItemsControl):
И вот что у меня есть в файле ViewModel:
публичный класс SceneEditorViewModel: BaseViewModel { режим частного перечисления { Режим добавления вершин, EdgeAdditionMode, Режим просмотра, Режим удаления вершин } частный только для чтения SceneEntry _sceneEntry; частный режим _mode = Mode.ViewMode; частный Вертекс? _firstClickedVertex; общедоступный график IGraph => _sceneEntry.Graph; /// /// Команды для кнопок /// общественный ICommand LoadFileCommand {получить; } общественный ICommand SetNameCommand {получить; } общественный ICommand BackCommand {получить; } общественный ICommand AddVertexCommand {get; } общественный ICommand AddEdgeCommand {получить; } общественный ICommand ClearCommand {получить; } общественный ICommand DeleteVertexCommand {get; } /// /// Команда вызывается после щелчка пользователя по области холста /// общественный ICommand CanvasClickCommand {получить; } общедоступная строка NameTextBox { получить => _sceneEntry.Name; набор { _sceneEntry.Name = значение; ПриИзмененииСвойства(); } } public SceneEditorViewModel (NavigationManager NavigationManager, SceneEntry? запись = null) { LoadFileCommand = новый RelayCommand (LoadFile, o => true); SetNameCommand = новая RelayCommand (SetName, o => true); BackCommand = new NavigationCommand(новый NavigationService(navigationManager, () => новая SceneMenuViewModel(navigationManager))); ДобавитьВертексКоманда = новая команда реле( o => _mode = _mode == Mode.VertexAdditionMode ? Mode.ViewMode : Mode.VertexAdditionMode, о => правда); AddEdgeCommand = новая команда реле( o => _mode = _mode == Mode.EdgeAdditionMode ? Mode.ViewMode : Mode.EdgeAdditionMode, о => правда); CanvasClickCommand = новый EventRelayCommand(OnMouseDown, o => true); DeleteVertexCommand = новый RelayCommand (DeleteVertex, o => true); _sceneEntry = запись ?? новый SceneEntry("", SceneMenuViewModel.SceneIdCounter++, новый Graph()); ClearCommand = new RelayCommand(o => _sceneEntry.Graph.Vertices.Clear(), o => true); если (запись равна нулю) { App.Scenes.Add(new SceneEntryViewModel(navigationManager, _sceneEntry)); } } частная пустота LoadFile (объект obj) { var openFileDialog = новый OpenFileDialog { Filter = "Файлы графиков (*.graph)|*.graph" }; если (openFileDialog.ShowDialog() == true) { вар файл = openFileDialog.FileName; вар сериализатор = новый GraphSerializer(); вар график = сериализатор.Десериализовать(файл); _sceneEntry.Graph = график; } //var сериализатор = новый сериализатор(); //var Graph = сериализатор.Deserialize(OpenFileDialog.FileName); //_graphViewModel.Graph = график; } частная пустота SetName (объект obj) { если (NameTextBox != "") { _sceneEntry.Name = NameTextBox; } } частная пустота DeleteVertex (объект obj) { _mode = _mode == Mode.VertexDeletionMode ? Mode.ViewMode : Mode.VertexDeletionMode; } частная пустота OnMouseDown (MouseButtonEventArgs e) { вар отправитель = e.Source как IInputElement; вар позиция = e.GetPosition(отправитель); переключатель (_mode) { Case Mode.VertexAdditionMode: var vertex = new Vertex("", Position.X, Position.Y, new ObservableCollection()); _sceneEntry.Graph.AddVertex(вершина); перерыв; Case Mode.EdgeAdditionMode: var first = _sceneEntry.Graph.Vertices.FirstOrDefault(v => v.X - 10 < позиция.X && позиция.X < v.X + 10 && v.Y - 10 < позиция.Y && позиция.Y < v.Y + 10); если (первое значение равно нулю) перерыв; если (_firstClickedVertex имеет значение null ) { first.IsSelected = true; _firstClickedVertex = первый; } еще { если (первый != _firstClickedVertex) { вар край = новый край() { FromVertex = новая вершина (_firstClickedVertex), ToVertex = новая вершина (первая), IsDirected = ложь, Вес = 1 }; _sceneEntry.Graph.Vertices.First(v => v == _firstClickedVertex).Edges.Add(edge); } _firstClickedVertex.IsSelected = ложь; _firstClickedVertex = ноль; } перерыв; Case Mode.VertexDeletionMode: var selected = _sceneEntry.Graph.Vertices.FirstOrDefault(v => v.X - 10 < позиция.X && позиция.X < v.X + 10 && v.Y - 10 < позиция.Y && позиция.Y < в.Й+10); если (выбрано значение null) перерыв; _sceneEntry.Graph.Vertices.Remove(выделено); перерыв; по умолчанию: перерыв; } } } Код действительно беспорядочный (в основном из-за попыток выяснить, в чем именно заключается проблема), но намерение должно быть несколько видно.
Класс Graph — это, по сути, просто класс, инкапсулирующий ObservableCollection, и все модели реализуют интерфейс INotifyPropertyChaned.
На правильном ли я пути, используя этот подход внутри файла xaml, или есть гораздо лучший способ сделать то, что я хочу?
-
- Похожие темы
- Ответы
- Просмотры
- Последнее сообщение
-
-
Как установить фокус на текстовое поле, которое находится под ItemsControl и Datatemplate Grid
Anonymous » » в форуме C# - 0 Ответы
- 25 Просмотры
-
Последнее сообщение Anonymous
-
-
-
Обновление до .NET 4.5: ItemsControl не соответствует источнику элементов.
Anonymous » » в форуме C# - 0 Ответы
- 16 Просмотры
-
Последнее сообщение Anonymous
-