2

我使用 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>

模型

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

主窗口.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);

            }
        }
    }
}
4

5 回答 5

5

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

数据层是你的DataContext,当你写一个普通的绑定时,你是绑定到DataContext.

当你写

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

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

<!-- Trying to bind to DirectorySearchModel.Files.Files -->
<ListBox ItemsSource="{Binding Path=Files}"/>

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

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

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

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

您可以将DirectorySearchModel用于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
}

但老实说,这仍然不理想,因为您的视图和数据层仍然紧密耦合在一起,并且这不符合 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>

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

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

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

于 2013-04-16T19:55:06.160 回答
2

你有: this.DataContext = new DirectorySearchModel().Files;

和这个: <ListBox Name="listBox1" ItemsSource="{Binding Path=Files}"/>

这意味着它将尝试绑定到 DirectorySearchModel().Files.Files。您可能想要更改为:this.DataContext = new DirectorySearchModel();

于 2013-04-16T19:19:35.383 回答
2

** 编辑 **

我盯着瞎了:)

你没有重用你的模型:这行得通,我只是测试了它

    private DirectorySearchModel model = new DirectorySearchModel();

    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(MainWindow_Loaded);

    }
    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        this.DataContext = model; //The model is already available as private member
    }

    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 = "*.*";
            Action<string, string> proc = model.Search; // use existing model (the private member).
            cbResult = proc.BeginInvoke(path, pattern, null, null);

        }
    }

在 Xaml 中输入:

<ListBox ItemsSource="{Binding Path=Files}"/>
于 2013-04-16T19:35:12.953 回答
1

在您的 MainWindow.cs 中,更改为:

public partial class MainWindow : Window
{
    IAsyncResult cbResult;
    DirectorySearchModel _model = new DirectorySearchModel();
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = _model;
    }
...

在您的 Xaml 中,将您的绑定更新为:

<ListBox Name="listBox1"  ItemsSource="{Binding Files, UpdateSourceTrigger="PropertyChanged"}"/>
于 2013-04-16T19:23:11.680 回答
1

您必须将窗口的 DataContext 设置为 DirectorySearchModel 的新实例。它没有更新,因为您的 ListBox 继承了设置为 Files 属性的窗口的 DataContext。您的列表框正在寻找不存在的文件集合的文件属性。

您可以使用此代码

public MainWindow()
{
   this.DataContext = new DirectorySearchModel();
}

这样,您的列表框将具有新目录搜索模型的数据上下文,并将查找您在 ItemSource 绑定的Path属性上指定的 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 = "*.*";
            //OLD CODE                
            //new Model.DirectorySearchModel().Search(path, pattern);
            //SUGGESTION
            (this.DataContext AS DirectorySearchModel).Search(path, pattern);
            Action<string, string> proc = (this.DataContext AS DirectorySearchModel).Search;
            cbResult = proc.BeginInvoke(path, pattern, null, null);

        }
    }
于 2013-04-16T19:26:53.163 回答