Pregunta Uso de ItemsSource para poblar WPF ListBox - ¿Buena idea?


Soy un codificador (relativamente) experimentado de Cocoa / Objective-C, y me estoy enseñando C # y el marco WPF.

En cacao, al poblar una NSTableView, es relativamente simple asignar un delegado y una fuente de datos a la vista. Esos métodos de fuente de datos / delegado se utilizan para rellenar la tabla y determinar su comportamiento.

Estoy armando una aplicación simple que tiene una lista de objetos, llamémoslos Dog objetos, que cada uno tiene una public string name. Este es el valor de retorno de Dog.ToString().

Los objetos se mostrarán en un ListBox, y me gustaría llenar esta vista usando un patrón similar al de Cocoa NSTableViewDataSource. Actualmente parece estar funcionando usando:

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

Pero me pregunto como correcto esto es. Literalmente, tengo instalado Visual Studio durante menos de una hora, así que es seguro decir que no tengo idea de lo que estoy haciendo.

  1. ¿Es este el patrón adecuado?
  2. Añadiendo el segundo elemento a la lista (spot) parece actualizar el ListBox correctamente, pero me pregunto qué desencadena las actualizaciones?
  3. ¿Qué pasa si actualizo el Pound en un hilo de fondo?
  4. ¿Cómo puedo pedir manualmente el ListBox para actualizarse? (¿Incluso necesito?)

Un cambio que sé que necesito hacer es refactorizar el IEnumerable<Dog> implementación en su propia clase, como DogListItemsSource, pero quiero asegurarme de tener un enfoque sólido antes de pulirlo.

Sentirse libre para señalar, en los comentarios, cualquier otro punto que deba abordar o tener en cuenta, grande o pequeño. Me gustaría aprender esto de la manera correcta, la primera vez.


5
2017-11-29 22:23


origen


Respuestas:


Mi sugerencia sería crear una clase además de su ventana que sería responsable de proporcionar los datos a su ListBox. Un enfoque común es que se llama WPF. MVVM, que como cualquier patrón tiene muchas implementaciones.

Los conceptos básicos son cada modelo (por ejemplo, Pound y Dog) tendría un modelo de visualización responsable de presentar el modelo de una manera que sea fácil de interactuar desde la interfaz de usuario.

Para empezar, WPF proporciona una excelente clase, ObservableCollection<T>, que es una colección que dispara un evento "Hey I Changed" cada vez que alguien se agrega, mueve o elimina.

A continuación se muestra un ejemplo que no pretende enseñarle MVVM, ni tampoco utiliza ningún marco para MVVM. Sin embargo, si configura algunos puntos de interrupción y juega con ellos, aprenderá sobre enlaces, comandos, INotifyPropertyChanged y ObservableCollection; todo lo cual juega un papel importante en el desarrollo de aplicaciones WPF.

Comenzando en el MainWindow, puedes configurar tu DataContext a un modelo de vista:

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

Donde el PoundViewModel maneja una colección de DogViewModel objetos:

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

Y en tu XAML (asegúrate de mapear xmlns:local permitir que XAML use tus modelos de vista)

<!-- <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>

Por supuesto, necesitarías 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));
    }
}

Finalmente necesitarás una implementación de 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);
    }
}

Esta respuesta de ninguna manera te ayudará a crear interfaces de usuario de WPF envolventes y totalmente vinculadas, ¡pero con suerte te dará una idea de cómo la interfaz de usuario puede interactuar con tu código!


13
2017-11-29 23:15



  1. En WPF usualmente solo tienes una colección como ItemsSource y plantillas de datos para mostrar el artículo.

  2. Normalmente esos controles solo se actualizan si la instancia ItemsSource implementa INotifyCollectionChanged, tal vez hayas agregado el artículo antes del ListBox lo recuperó

  3. ¿Qué es Pound? A menos que Libra tenga alguna afinidad de hilos como, por ejemplo, ObservableCollection sí, eso no es problema, si lo hace necesitas usar despachando.

  4. ListBox.Items.Refresh() podría hacer eso, pero generalmente solo usa una colección con notificaciones.

WPF utiliza en gran medida el enlace de datos, por lo que si desea aprender el marco la descripción respectiva (junto con todos los otros) podría ser de interés.


1
2017-11-29 22:36