Utilizzando ItemsSource per popolare WPF ListBox - buona idea?
-
25-10-2019 - |
Domanda
Sono un (relativamente) sperimentato Cocoa / Objective-C coder, e insegno io C # e il quadro WPF.
In Cocoa, quando popola una NSTableView
, è relativamente semplice per assegnare un delegato e datasource alla vista. Quei delegati / metodi di origine dati vengono poi utilizzati per popolare la tabella, e per determinare il suo comportamento.
Sto mettendo insieme una semplice applicazione che ha una lista di oggetti, consente di chiamare loro Dog
oggetti, che hanno ciascuno una public string name
. Questo è il valore di ritorno di Dog.ToString()
.
Gli oggetti sarà visualizzato in un ListBox
, e vorrei per popolare questo punto di vista utilizzando un modello simile a NSTableViewDataSource
di cacao. Attualmente sembra funzionare utilizzando:
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();
}
}
Ma mi chiedo come corretta ??em> questo è. Ho letteralmente dovuto Visual Studio installato per meno di un'ora, quindi è sicuro di dire non ho idea di quello che sto facendo.
- E 'questo il modello corretto?
- aggiunta del secondo elemento alla lista (
spot
) sembra per aggiornare ilListBox
correttamente, ma mi chiedo che cosa fa scattare gli aggiornamenti? - Che cosa accade se aggiorno il
Pound
su un thread in background? - Come posso chiedere manualmente il
ListBox
di aggiornarsi? (Do Ho anche bisogno di?)
Un cambiamento che so che ho bisogno di fare è refactoring l'attuazione IEnumerable<Dog>
nella propria classe, come DogListItemsSource
, ma voglio essere sicuro di avere un approccio solido prima lucidatura esso.
Sentitevi liberi a sottolineare, nei commenti, eventuali altri punti che devono affrontare o tenere a mente, grande o piccolo. Mi piacerebbe imparare questo il modo giusto, la prima volta.
Soluzione
Il mio suggerimento sarebbe quello di creare una classe oltre al tuo Window, che sarebbe responsabile di fornire i dati al vostro ListBox
. Un approccio comune è WPF è chiamato MVVM , che come qualsiasi modello ha molte implementazioni.
Le basi sono ogni modello (ad esempio Pound
e Dog
) avrebbe una vista del modello responsabile per la presentazione del modello in un modo che è facile interagire con dall'interfaccia utente.
Per iniziare, WPF fornisce una classe eccellente, ObservableCollection<T>
, che è una collezione che spara un "Hey ho cambiato" evento ogni volta che viene aggiunto a nessuno, spostato o rimosso.
Di seguito è riportato un esempio che non intende insegnare MVVM, né utilizza alcun quadro di riferimento per MVVM. Tuttavia, se si imposta alcuni punti di interruzione e giocare con lui, imparerete a conoscere attacchi, comandi, INotifyPropertyChanged, e ObservableCollection; ognuno dei quali gioca un ruolo importante nello sviluppo di applicazioni WPF.
A partire dal MainWindow
, è possibile impostare il DataContext
ad un Model View:
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();
}
}
Quando la PoundViewModel
gestisce una collezione di oggetti 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)
);
}
}
E nel tuo XAML ( essere sicuri di mappare xmlns:local
per consentire di utilizzare il XAML Visualizza i modelli ):
<!-- <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>
Naturalmente, avresti bisogno di un 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));
}
}
Infine avrete bisogno di un'implementazione di 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);
}
}
Questa risposta in nessun modo vi farà fustigazione coinvolgente, pienamente legata WPF UI, ma si spera che sarà darvi un'idea di come l'interfaccia utente può interagire con il vostro codice!
Altri suggerimenti
-
In WPF di solito basta avere un po 'di raccolta come ItemsSource e modelli di dati per visualizzare l'elemento.
-
Normalmente tali controlli aggiornano solo se gli attrezzi istanza ItemsSource
INotifyCollectionChanged
, forse si è aggiunto la voce prima che ilListBox
recuperò. -
Che cos'è Pound? A meno che Pound abbia qualche filo affinità come ad esempio
ObservableCollection
fa, che non è un problema, se lo fa è necessario utilizzare invio . -
ListBox.Items.Refresh()
poteva farlo, ma di solito è sufficiente utilizzare una collezione con le notifiche.
WPF utilizza pesantemente l'associazione di dati, quindi se volete imparare il quadro rispettivo panoramica (insieme a tutti gli altri ) potrebbe essere di interessi.