【问题标题】:Using ItemsSource to populate WPF ListBox - Good Idea?使用 ItemsSource 填充 WPF ListBox - 好主意?
【发布时间】:2012-01-09 06:07:53
【问题描述】:

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

在 Cocoa 中,当填充 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 不到一个小时,所以可以肯定地说我不知道​​自己在做什么。

  1. 这是正确的模式吗?
  2. 将第二项添加到列表 (spot) 似乎可以正确更新 ListBox,但我想知道是什么触发了更新?
  3. 如果我在后台线程上更新 Pound 会发生什么?
  4. 如何手动要求ListBox 自行更新? (我什至需要吗?)

我知道我需要做出的一个改变是将 IEnumerable&lt;Dog&gt; 实现重构为它自己的类,例如 DogListItemsSource,但我想确保在完善它之前我有一个可靠的方法.

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

【问题讨论】:

    标签: c# wpf listbox ienumerator


    【解决方案1】:

    我的建议是在您的 Window 之外创建一个类,该类负责将数据提供给您的 ListBox。一种常见的方法是 WPF 被称为MVVM,它像任何模式一样有许多实现。

    基础是每个模型(例如PoundDog)都有一个视图模型,负责以易于从 UI 交互的方式呈现模型。

    为了帮助您入门,WPF 提供了一个出色的类 ObservableCollection&lt;T&gt;,它是一个在添加、移动或删除任何人时触发“Hey I Changed”事件的集合。

    下面是一个不打算教你 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 (be sure to map xmlns:local to allow XAML to use your 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>
    

    当然,您需要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&lt;T&gt;的实现:

    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 UI,但希望它能让您了解 UI 如何与您的代码交互!

    【讨论】:

    • 哎呀,这东西复杂!与 KVO 和 MVC 以及 IBOutlets 的简单性相去甚远,但也许我仍然需要热身。 :) 非常感谢你的解释和量身定制的例子,我会在接下来的几天里经常提到它。
    • @craig:是的,它与 Obj-C 不同,这是肯定的。诚然,我很难从 WPF 转到 Obj-C/Cocoa ;) 到 MVVM 框架的有用链接:WPF Application FrameworkPrism 4.0Prism's Chapter 5: Implementing the MVVM Pattern 都是不错的起点。
    【解决方案2】:
    1. 在 WPF 中,您通常只有一些集合作为 ItemsSource 和 data templates 来显示项目。

    2. 通常,这些控件仅在 ItemsSource 实例实现 INotifyCollectionChanged 时才会更新,也许您在 ListBox 检索到它之前添加了该项目。

    3. 什么是英镑?除非 Pound 具有一些线程亲和力,例如ObservableCollection 可以,那没问题,如果是你需要使用dispatching

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

    WPF 大量使用数据绑定,所以如果你想学习框架the respective overview(以及all the others)可能会感兴趣。

    【讨论】:

      猜你喜欢
      • 2013-06-11
      • 2014-08-16
      • 2010-11-03
      • 2011-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-29
      相关资源
      最近更新 更多