使用ItemsSource填充WPF ListBox - 好主意?

时间:2022-10-05 20:24:22

I'm a (relatively) experienced Cocoa/Objective-C coder, and am teaching myself C# and the WPF framework.

我是(相对)经验丰富的Cocoa / Objective-C编码器,我正在自学C#和WPF框架。

In Cocoa, when populating an NSTableView, it's relatively simply to assign a delegate and datasource to the view. Those delegate/datasource methods are then used to populate the table, and to determine its behavior.

在Cocoa中,当填充NSTableView时,相对简单地将一个委托和数据源分配给视图。然后使用这些委托/数据源方法填充表,并确定其行为。

I'm putting together a simple application that has a list of objects, lets call them Dog objects, that each have a public string name. This is the return value of Dog.ToString().

我正在整理一个包含对象列表的简单应用程序,让我们称之为Dog对象,每个对象都有一个公共字符串名称。这是Dog.ToString()的返回值。

The objects will be displayed in a ListBox, and I would like to populate this view using a similar pattern to Cocoa's NSTableViewDataSource. It currently seems to be working using:

对象将显示在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();
        }
    }

But I'm wondering how correct this is. I've literally had Visual Studio installed for less than an hour, so it's safe to say I have no idea what I'm doing.

但我想知道这是多么正确。我确实安装了Visual Studio不到一个小时,所以可以肯定地说我不知道​​我在做什么。

  1. Is this the proper pattern?
  2. 这是正确的模式吗?

  3. Adding the second item to the list (spot) seems to update the ListBox properly, but I'm wondering what triggers the updates?
  4. 将第二项添加到列表(spot)似乎正确地更新了ListBox,但我想知道是什么触发了更新?

  5. What happens if I update the Pound on a background thread?
  6. 如果我在后台线程上更新磅会发生什么?

  7. How can I manually ask the ListBox to update itself? (Do I even need to?)
  8. 如何手动要求ListBox自行更新? (我甚至需要吗?)

One change that I know I need to make is refactoring the IEnumerable<Dog> implementation into its own class, like DogListItemsSource, but I want to make sure I have a solid approach before polishing it.

我知道我需要做的一个改变是将IEnumerable 实现重构到它自己的类中,比如DogListItemsSource,但我想确保在抛光之前我有一个可靠的方法。

Feel free to point out, in comments, any other points I should address or keep in mind, big or small. I'd like to learn this the right way, the first time.

请在评论中指出我应该解决或记住的任何其他要点,无论大小。我想第一次以正确的方式学习这个。

2 个解决方案

#1


13  

My suggestion would be to create a class besides your Window which would be responsible for providing the data to your ListBox. A common approach is WPF is called MVVM, which like any pattern has many implementations.

我的建议是创建一个除了Window之外的类,它将负责向ListBox提供数据。一种常见的方法是WPF称为MVVM,它与任何模式都有很多实现。

The basics are each Model (e.g. Pound and Dog) would have a View Model responsible for presenting the model in a manner which is easy to interact with from the UI.

基本是每个模型(例如镑和狗)将具有负责的方式这是很容易与来自UI交互呈现模型的图模型。

To get you started, WPF provides an excellent class, ObservableCollection<T>, which is a collection that fires off a "Hey I Changed" event whenever anybody is added, moved, or removed.

为了帮助您入门,WPF提供了一个优秀的类ObservableCollection ,这是一个集合,无论何时添加,移动或删除任何人,都会触发“Hey I Changed”事件。

Below is an example that doesn't intend to teach you MVVM, nor does it use any framework for MVVM. However, if you set some breakpoints and play with it, you'll learn about bindings, commands, INotifyPropertyChanged, and ObservableCollection; all of which play a large role in WPF application development.

下面是一个不打算教你MVVM的例子,它也没有使用MVVM的任何框架。但是,如果你设置一些断点并使用它,你将学习绑定,命令,INotifyPropertyChanged和ObservableCollection;所有这些都在WPF应用程序开发中发挥了重要作用。

Starting in the MainWindow, you can set your DataContext to a View Model:

从MainWindow开始,您可以将DataContext设置为View Model:

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

Where the PoundViewModel manages a collection of DogViewModel objects:

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

And in your XAML (be sure to map xmlns:local to allow XAML to use your View Models):

在您的XAML中(确保映射xmlns:local以允许XAML使用您的View Models):

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

Of course, you'd need a DogViewModel:

当然,你需要一个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));
    }
}

Finally you'll need an implementation of DelegateCommand<T>:

最后,您需要DelegateCommand 的实现:

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

This answer by no means will have you whipping up immersive, fully bound WPF UI's, but hopefully it'll give you a feel for how the UI can interact with your code!

这个答案绝不会让你掀起沉浸式,完全绑定的WPF UI,但希望它能让你感受到UI如何与你的代码进行交互!

#2


1  

  1. In WPF you usually just have some collection as ItemsSource and data templates to display the item.

    在WPF中,您通常只有一些集合作为ItemsSource和数据模板来显示项目。

  2. Normally those controls only update if the ItemsSource instance implements INotifyCollectionChanged, maybe you added the item before the ListBox retrieved it.

    通常,如果ItemsSource实例实现INotifyCollectionChanged,那些控件只会更新,也许您在ListBox检索它之前添加了该项。

  3. What is Pound? Unless Pound has some thread-affinity as e.g. ObservableCollection does, that is no problem, if it does you need to use dispatching.

    什么是庞德?除非Pound具有一些线程亲和力,例如ObservableCollection确实没问题,如果确实需要使用调度。

  4. ListBox.Items.Refresh() could do that, but usually you just use a collection with notifications.

    ListBox.Items.Refresh()可以做到这一点,但通常只使用带通知的集合。

WPF heavily uses data binding, so if you want to learn the framework the respective overview (along with all the others) might be of interest.

WPF大量使用数据绑定,因此如果您想学习框架,可能会对相应的概述(以及所有其他概述)感兴趣。

#1


13  

My suggestion would be to create a class besides your Window which would be responsible for providing the data to your ListBox. A common approach is WPF is called MVVM, which like any pattern has many implementations.

我的建议是创建一个除了Window之外的类,它将负责向ListBox提供数据。一种常见的方法是WPF称为MVVM,它与任何模式都有很多实现。

The basics are each Model (e.g. Pound and Dog) would have a View Model responsible for presenting the model in a manner which is easy to interact with from the UI.

基本是每个模型(例如镑和狗)将具有负责的方式这是很容易与来自UI交互呈现模型的图模型。

To get you started, WPF provides an excellent class, ObservableCollection<T>, which is a collection that fires off a "Hey I Changed" event whenever anybody is added, moved, or removed.

为了帮助您入门,WPF提供了一个优秀的类ObservableCollection ,这是一个集合,无论何时添加,移动或删除任何人,都会触发“Hey I Changed”事件。

Below is an example that doesn't intend to teach you MVVM, nor does it use any framework for MVVM. However, if you set some breakpoints and play with it, you'll learn about bindings, commands, INotifyPropertyChanged, and ObservableCollection; all of which play a large role in WPF application development.

下面是一个不打算教你MVVM的例子,它也没有使用MVVM的任何框架。但是,如果你设置一些断点并使用它,你将学习绑定,命令,INotifyPropertyChanged和ObservableCollection;所有这些都在WPF应用程序开发中发挥了重要作用。

Starting in the MainWindow, you can set your DataContext to a View Model:

从MainWindow开始,您可以将DataContext设置为View Model:

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

Where the PoundViewModel manages a collection of DogViewModel objects:

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

And in your XAML (be sure to map xmlns:local to allow XAML to use your View Models):

在您的XAML中(确保映射xmlns:local以允许XAML使用您的View Models):

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

Of course, you'd need a DogViewModel:

当然,你需要一个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));
    }
}

Finally you'll need an implementation of DelegateCommand<T>:

最后,您需要DelegateCommand 的实现:

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

This answer by no means will have you whipping up immersive, fully bound WPF UI's, but hopefully it'll give you a feel for how the UI can interact with your code!

这个答案绝不会让你掀起沉浸式,完全绑定的WPF UI,但希望它能让你感受到UI如何与你的代码进行交互!

#2


1  

  1. In WPF you usually just have some collection as ItemsSource and data templates to display the item.

    在WPF中,您通常只有一些集合作为ItemsSource和数据模板来显示项目。

  2. Normally those controls only update if the ItemsSource instance implements INotifyCollectionChanged, maybe you added the item before the ListBox retrieved it.

    通常,如果ItemsSource实例实现INotifyCollectionChanged,那些控件只会更新,也许您在ListBox检索它之前添加了该项。

  3. What is Pound? Unless Pound has some thread-affinity as e.g. ObservableCollection does, that is no problem, if it does you need to use dispatching.

    什么是庞德?除非Pound具有一些线程亲和力,例如ObservableCollection确实没问题,如果确实需要使用调度。

  4. ListBox.Items.Refresh() could do that, but usually you just use a collection with notifications.

    ListBox.Items.Refresh()可以做到这一点,但通常只使用带通知的集合。

WPF heavily uses data binding, so if you want to learn the framework the respective overview (along with all the others) might be of interest.

WPF大量使用数据绑定,因此如果您想学习框架,可能会对相应的概述(以及所有其他概述)感兴趣。