WPF 列表框未填充来自 ObservableCollection 的项目

WPF Listbox not populated with items from ObservableCollection

main-window 正在侦听插入 in/out USB 设备。如果它是 usb-key/disk,它会从该设备收集文件列表并在一秒钟内显示该列表 window。

调试时我可以看到 NewUsbFiles observablecollection 填充了 117 个项目。我看到 属性 UsbFile(在调用 showdialog 之前)有 117 个项目,但是列表框是空的。

有什么想法吗?

填充/创建第二个的方法window:

NewUsbFiles = new ObservableCollection<UsbFile>();
UpdateNewUsbFiles(driveName);

Application.Current.Dispatcher.Invoke(delegate
{
   var usbFileSelector = new UsbFileSelector()
   {
       Owner = this,
       UsbFiles = NewUsbFiles
    };
    usbFileSelector.ShowDialog();
});

UsbFile-class:

 public class UsbFile 
    {
        public string UsbFileName { get; set; }
        public string OnTableFileName { get; set; }
        public bool Ignored { get; set; } = false;

        public UsbFile(string fileName)
        {
            var fileInfo = new FileInfo(fileName);
            UsbFileName = fileInfo.FullName;
            OnTableFileName = $"{fileInfo.CreationTime:yyMMddHHmmsss}_{fileInfo.Name}";
        }
    }

第二个window的XAML:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:MainWindow="clr-namespace:PartyPictures.WPF.MainWindow" x:Name="wUsbFileSelector"
    x:Class="PartyPictures.WPF.UsbFileSelector"
        mc:Ignorable="d"
        Title="USB" HorizontalAlignment="Center" VerticalAlignment="Center" WindowStyle="ToolWindow" ScrollViewer.VerticalScrollBarVisibility="Auto" SizeToContent="WidthAndHeight">
    <StackPanel x:Name="spUsbFileList">
        <ListBox  x:Name="ImageListbox"
                  DataContext="{Binding ElementName=wUsbFileSelector}"
                 ItemsSource="{Binding UsbFiles}" 
                 Background="AliceBlue" ScrollViewer.HorizontalScrollBarVisibility="Disabled" MinWidth="200" MinHeight="200">
        </ListBox>
    </StackPanel>
</Window>

第二个window的代码隐藏:

public partial class UsbFileSelector : Window
    {
        public ObservableCollection<UsbFile> UsbFiles { get; set; } = new ObservableCollection<UsbFile>();

        public UsbFileSelector()
        {
            InitializeComponent();
        }
    }

var usbFileSelector = new UsbFileSelector()
{
   Owner = this,
   UsbFiles = NewUsbFiles
};

您正在为 UsbFiles 属性 分配一个新值,而没有为该 属性 触发 属性 更改通知。

您可以实施 INotifyPropertyChanged 并触发 PropertyChanged 事件,或者使 UsbFiles 成为依赖项 属性。

或者您将 NewUsbFiles 作为构造函数参数传递并在调用 InitializeComponent

之前分配它
public UsbFileSelector(ObservableCollection<UsbFile> usbFiles)
{
    UsbFiles = usbFiles;
    InitializeComponent();
}

并这样称呼它:

var usbFileSelector = new UsbFileSelector(NewUsbFiles)
{
   Owner = this
};

请注意,如果您总是传递新集合,则实际上没有必要使用 ObservableCollection。您永远不会添加或删除元素 to/from 集合,因此不需要更改通知。

有人发布(并删除了评论)我应该添加 DataContext = 这个;

public UsbFileSelector()
{
   InitializeComponent();
   DataContext = this;
}

其他人提到(该评论也被删除了)因为

这不是必需的
DataContext="{Binding ElementName=wUsbFileSelector}"

在 XAML.

但事实证明,从 XAML 中删除 DataContext 行并在代码中将其设置为此是解决方案。不知道为什么,但就是这样。


编辑只是为了表明这不是一个好的解决方案并且只是偶然起作用,请尝试以下操作:

// this works
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();

// this does not
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
await Task.Delay(10);
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();

在 window 中您可以看到 InitializeComponent 方法。它创建 XAML 中定义的所有内容并应用所有绑定。在将绑定应用到您的空集合(您使用默认 属性 值创建)后,绑定将不知道 属性 的任何更改,这是正确的答案。

但是实现 INotify属性Changed 更多的是关于视图模型实例,而不是视觉。

我真的建议你使用 Dependency 属性 for windows 和 controls 如果你想绑定。有一些原因:

  1. 依赖项属性 setter 具有内置的通知机制。
  2. 如果将一个 DP 绑定到另一个 DP,则值在两者之间共享。
  3. 毕竟是WPF的做法=)

这是您的 window 更改后的样子

    public partial class UsbFileSelector : Window
    {
        public static readonly DependencyProperty UsbFilesProperty = 
            DependencyProperty.Register("UsbFiles", typeof(ObservableCollection<UsbFile>), typeof(UsbFileSelector));

        public ObservableCollection<UsbFile> UsbFiles 
        {
            get { return (ObservableCollection<UsbFile>) GetValue(UsbFilesProperty); }
            set { SetValue(UsbFilesProperty, value); }
        }

        public UsbFileSelector()
        {
            InitializeComponent();
        }
    }

另外,我强烈建议您在为 WPF 开发时使用一些 WPF 检查器工具,例如 snoop。您可以在应用程序处于 运行 时浏览控件和属性,并且可以从代码或 Whosebug =)

中快速找到问题

所有已经给出的答案都是正确的,你问题的核心是

UsbFiles = NewUsbFiles

导致绑定到 "break" - UsbFiles 不再指向绑定到列表框的集合。

解决此问题的另一种可能方法是简单地保留绑定集合并重新填充内容。

var usbFileSelector = new UsbFileSelector()
{
   Owner = this,
   UsbFiles.Clear();
   foreach (var uf in NewUsbFiles) {
       UsbFiles.Add(uf);
   }
};