WPF UI 未更新

san*_*ngh 2 c# wpf mvvm

我已经使用 MVVM 模型和代码开发了一个简单的 wpf 应用程序,如下所示。我调试代码并发现集合正在更新但 UI 没有更新,即使我在列表框中没有看到任何记录。

编辑

在此处输入图片说明

<Window x:Class="Model.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WPF Dispatcher Demo" Height="350" Width="525">


    <DockPanel>
        <Grid DockPanel.Dock="Bottom">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="2*" Name="col0" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBox Name="textBlock1" Text="{Binding ShowPath}" 
                 VerticalAlignment="Center" HorizontalAlignment="Left" 
                  Margin="30" Grid.Column="0" />
            <Button Name="FindButton" Content="Select Path" 
              Width="100" Margin="20" Click="FindButton_Click" Grid.Column="1" />
        </Grid>
        <ListBox Name="listBox1"  ItemsSource="{Binding Path=Files}"/>


    </DockPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

模型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Threading;
using System.ComponentModel;
using System.Collections.ObjectModel;


namespace Model
{
    public class DirectorySearchModel : INotifyPropertyChanged
    {
        private ObservableCollection<string> _files = new ObservableCollection<string>();
        private string _showPath;
        public DirectorySearchModel() { }
        void ShowCurrentPath(string path)
        {
            ShowPath = path;
        }


        void AddFileToCollection(string file)
        {
            _files.Add(file);
        }
        public void Search(string path, string pattern)
        {

            if (System.Windows.Application.Current.Dispatcher.CheckAccess())
                ShowPath = path;
            else
                System.Windows.Application.Current.Dispatcher.Invoke(
                  new Action<string>(ShowCurrentPath), DispatcherPriority.Background, new string[] { path }
                );
            string[] files = Directory.GetFiles(path, pattern);
            foreach (string file in files)
            {
                if (System.Windows.Application.Current.Dispatcher.CheckAccess())
                    Files.Add(file);
                else
                    System.Windows.Application.Current.Dispatcher.Invoke(new Action<string>(AddFileToCollection), DispatcherPriority.Background,
                      new string[] { file }
                    );
            }
            string[] dirs = System.IO.Directory.GetDirectories(path);
            foreach (string dir in dirs)
                Search(dir, pattern);
        }

        public string ShowPath
        {
            get
            {
                return _showPath;
            }
            set
            {
                _showPath = value;
                OnPropertyChanged("ShowPath");
            }
        }
        public ObservableCollection<string> Files
        {
            get
            {
                return _files;
            }
            set
            {
                _files = value;
                OnPropertyChanged("Files");
            }
        }
        public void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));

            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}
Run Code Online (Sandbox Code Playgroud)

主窗口.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Forms;
using System.IO;

namespace Model
{
    public partial class MainWindow : Window
    {
        IAsyncResult cbResult;
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);

        }
        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = new DirectorySearchModel().Files;
        }

        private void FindButton_Click(object sender, RoutedEventArgs e)
        {

            FolderBrowserDialog dlg = new FolderBrowserDialog();
            string path = AppDomain.CurrentDomain.BaseDirectory;
            dlg.SelectedPath = path;
            DialogResult result = dlg.ShowDialog();
            if (result == System.Windows.Forms.DialogResult.OK)
            {
                path = dlg.SelectedPath;
                string pattern = "*.*";
                new Model.DirectorySearchModel().Search(path, pattern);
                Action<string, string> proc = new Model.DirectorySearchModel().Search;
                cbResult = proc.BeginInvoke(path, pattern, null, null);

            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Rac*_*hel 5

在WPF中,有两层:UI层和数据层

数据层是您的DataContext,当您编写普通绑定时,您将绑定到DataContext.

当你写

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = new DirectorySearchModel().Files;
}
Run Code Online (Sandbox Code Playgroud)

您告诉 WPF 表单的数据层将是字符串的 ObservableCollection,但是ObservableCollection<string>没有名为 的属性Files,因此您的 ListBox 绑定失败。

<!-- Trying to bind to DirectorySearchModel.Files.Files -->
<ListBox ItemsSource="{Binding Path=Files}"/>
Run Code Online (Sandbox Code Playgroud)

您需要将数据层更改为您的DirectorySearchModel,因此绑定正确评估为DirectorySearchModel.Files

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = new DirectorySearchModel();
}
Run Code Online (Sandbox Code Playgroud)

但这不是你唯一的问题。您的 Button 的 Click 事件在一个实例上运行,DirectorySearchModel而不是在现有实例上运行。

您可以简单地使用(DirectorySearchModel)MainWindow.DataContext,但这并不理想,因为它将您的 UI 和您的数据层紧密地结合在一起,并且假设DataContext将始终是 type DirectorySearchModel

您可以将DirectorySearchModelused存储在moncadad 建议DataContext某个地方,以便您可以从代码中的其他地方访问它:

DirectorySearchModel _model = new DirectorySearchModel();
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    _model = new DirectorySearchModel();
    this.DataContext = _model ;
}

private void FindButton_Click(object sender, RoutedEventArgs e)
{
    // use _model instead of "new DirectorySearchModel()" here
}
Run Code Online (Sandbox Code Playgroud)

但老实说这仍然不理想,因为您的视图和数据层仍然紧密耦合在一起,并且这不符合 MVVM 设计模式(您已经用它标记了您的问题,所以我假设您是使用)。

最好的解决方案是实际用ICommandon替换按钮的 Click 事件,DirectorySearchModel这样您就不必担心从 UI 层存储和访问数据层的副本。这还有一个额外的好处是将应用程序逻辑保留在应用程序层内,而不是将其与 UI 层混合:

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    this.DataContext = new DirectorySearchModel();
}

<DockPanel>
    <Grid DockPanel.Dock="Bottom">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*" Name="col0" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <TextBox Text="{Binding ShowPath}" ... />

        <Button Content="Select Path" Command="FindButtonCommand" ... />
    </Grid>
    <ListBox ItemsSource="{Binding Path=Files}"/>
</DockPanel>
Run Code Online (Sandbox Code Playgroud)

这正确地将您的 UI 与您的数据层分开,这是 MVVM 设计模式的主要目标。

这样,您的应用程序逻辑都保留在您的应用程序对象中,而您的 UI 只是一个非常用户友好的界面,用于与您的应用程序对象进行交互。

我喜欢写初学者的 WPF 文章,我建议阅读我的文章你说的这个“DataContext”是什么?了解DataContext它是什么以及它如何更好地工作:)