Использование ItemsSource для заполнения списка WPF - Хорошая идея?
-
25-10-2019 - |
Вопрос
Я (относительно) опытный программист Cocoa / Objective-C и сам изучаю C # и фреймворк WPF.
В Какао, при заполнении NSTableView
, относительно просто назначить делегат и источник данных для представления.Эти методы делегирования / источника данных затем используются для заполнения таблицы и определения ее поведения.
Я собираю простое приложение, у которого есть список объектов, давайте назовем их Dog
объекты, каждый из которых имеет public string name
.Это возвращаемое значение Dog.ToString()
.
Объекты будут отображаться в виде ListBox
, и я хотел бы заполнить это представление, используя шаблон, аналогичный шаблону Cocoa NSTableViewDataSource
.В настоящее время, похоже, он работает с использованием:
public partial class MainWindow : Window, IEnumerable<Dog>
{
public Pound pound = new Pound();
public MainWindow()
{
InitializeComponent();
Dog fido = new Dog();
fido.name = "Fido";
pound.AddDog(fido);
listBox1.ItemsSource = this;
Dog spot = new Dog();
spot.name = "Spot";
pound.AddDog(spot);
}
public IEnumerator<Dog> GetEnumerator()
{
return currentContext.subjects.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Но мне интересно, как правильный это так.Я установил Visual Studio буквально меньше часа назад, так что можно с уверенностью сказать, что я понятия не имею, что делаю.
- Это правильный шаблон?
- Добавление второго элемента в список (
spot
), похоже, обновляетListBox
правильно, но мне интересно, что запускает обновления? - Что произойдет, если я обновлю
Pound
в фоновом потоке? - Как я могу вручную задать
ListBox
чтобы обновить себя?(Нужно ли мне это вообще?)
Одно изменение что, я знаю, мне нужно сделать, так это рефакторинг IEnumerable<Dog>
реализация в свой собственный класс, например DogListItemsSource
, но я хочу убедиться, что у меня есть надежный подход, прежде чем полировать его.
Чувствуй себя свободно указать в комментариях на любые другие моменты, которые я должен затронуть или иметь в виду, большие или маленькие.Я бы хотел научиться этому правильно, с первого раза.
Решение
Мое предложение состояло бы в том, чтобы создать класс помимо вашего Window, который отвечал бы за предоставление данных вашему ListBox
.Распространенный подход заключается в том, что WPF называется MVVM, который, как и любой шаблон, имеет множество реализаций.
Основой является каждая Модель (например, Pound
и Dog
) будет иметь модель представления, ответственную за представление модели таким образом, чтобы с ней было легко взаимодействовать из пользовательского интерфейса.
Чтобы вы могли начать, WPF предоставляет отличный класс, ObservableCollection<T>
, которая представляет собой коллекцию, которая запускает событие "Эй, я изменился" всякий раз, когда кто-либо добавляется, перемещается или удаляется.
Ниже приведен пример, который не предназначен для обучения вас MVVM и не использует какой-либо фреймворк для MVVM.Однако, если вы установите некоторые точки останова и поиграете с ними, вы узнаете о привязках, командах, INotifyPropertyChanged и ObservableCollection;все это играет большую роль в разработке приложений WPF.
Начиная с MainWindow
, вы можете установить свой DataContext
к Модели представления:
public class MainWindow : Window
{
// ...
public MainWindow()
{
// Assigning to the DataContext is important
// as all of the UIElement bindings inside the UI
// will be a part of this hierarchy
this.DataContext = new PoundViewModel();
this.InitializeComponent();
}
}
Где находится PoundViewModel
управляет коллекцией DogViewModel
Объекты:
public class PoundViewModel
{
// No WPF application is complete without at least 1 ObservableCollection
public ObservableCollection<DogViewModel> Dogs
{
get;
private set;
}
// Commands play a large role in WPF as a means of
// transmitting "actions" from UI elements
public ICommand AddDogCommand
{
get;
private set;
}
public PoundViewModel()
{
this.Dogs = new ObservableCollection<DogViewModel>();
// The Command takes a string parameter which will be provided
// by the UI. The first method is what happens when the command
// is executed. The second method is what is queried to find out
// if the command should be executed
this.AddDogCommand = new DelegateCommand<string>(
name => this.Dogs.Add(new DogViewModel { Name = name }),
name => !String.IsNullOrWhitespace(name)
);
}
}
И в вашем XAML (обязательно нанесите на карту xmlns:local
чтобы разрешить XAML использовать ваши модели просмотра):
<!-- <Window ...
xmlns:local="clr-namespace:YourNameSpace" -->
<!-- Binding the ItemsSource to Dogs, will use the Dogs property
-- On your DataContext, which is currently a PoundViewModel
-->
<ListBox x:Name="listBox1"
ItemsSource="{Binding Dogs}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:DogViewModel}">
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="5">
<TextBox Text="{Binding Name}" />
</Border>
</DataTemplate>
</ListBox.Resources>
</ListBox>
<GroupBox Header="New Dog">
<StackPanel>
<Label>Name:</Label>
<TextBox x:Name="NewDog" />
<!-- Commands are another big part of WPF -->
<Button Content="Add"
Command="{Binding AddDogCommand}"
CommandParameter="{Binding Text, ElementName=NewDog}" />
</StackPanel>
</GroupBox>
Конечно, вам понадобится DogViewModel
:
public class DogViewModel : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
this.name = value;
// Needed to alert WPF to a change in the data
// which will then update the UI
this.RaisePropertyChanged("Name");
}
}
public event PropertyChangedHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Наконец, вам понадобится реализация DelegateCommand<T>
:
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> execute;
private readonly Func<T, bool> canExecute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
{
if (execute == null) throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(T parameter)
{
return this.canExecute != null && this.canExecute(parameter);
}
bool ICommand.CanExecute(object parameter)
{
return this.CanExecute((T)parameter);
}
public void Execute(T parameter)
{
this.execute(parameter);
}
bool ICommand.Execute(object parameter)
{
return this.Execute((T)parameter);
}
}
Этот ответ ни в коем случае не заставит вас создавать захватывающий, полностью связанный пользовательский интерфейс WPF, но, надеюсь, он даст вам представление о том, как пользовательский интерфейс может взаимодействовать с вашим кодом!
Другие советы
В WPF у вас обычно просто есть некоторая коллекция в качестве ItemsSource и шаблоны данных чтобы отобразить элемент.
Обычно эти элементы управления обновляются только в том случае, если экземпляр ItemsSource реализует
INotifyCollectionChanged
, возможно, вы добавили элемент передListBox
извлек его.Что такое фунт?Если только Pound не имеет некоторого сродства к потоку, как, например,
ObservableCollection
делает, это не проблема, если вам нужно использовать отправка.ListBox.Items.Refresh()
можно было бы это сделать, но обычно вы просто используете коллекцию с уведомлениями.
WPF в значительной степени использует привязку данных, поэтому, если вы хотите изучить фреймворк соответствующий обзор (вместе с все остальные) может представлять интерес.