WPF UI未更新

时间:2013-04-16 19:15:44

标签: c# wpf mvvm

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

修改

enter image description here

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

MainWindow.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);

            }
        }
    }
}

5 个答案:

答案 0 :(得分:4)

在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,但这并不理想,因为它将您的用户界面和数据层紧密地结合在一起,并假设DataContext始终为DirectorySearchModel类型{1}}。

您可以将DirectorySearchModel用于DataContext用于moncadad suggested之类的地方,以便您可以从代码中的其他位置访问它:

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
}

但说实话,这仍然不太理想,因为你的View和你的数据层仍然紧密地耦合在一起,这不符合MVVM设计模式(你已经标记了你的问题,所以我假设你正在使用)。

最佳解决方案是使用ICommand上的DirectorySearchModel替换按钮的Click事件,这样您就不必担心从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文章,我建议阅读我的帖子What is this "DataContext" you speak of?以了解DataContext是什么以及它如何更好地运作:)

答案 1 :(得分:2)

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

并且:     <ListBox Name="listBox1" ItemsSource="{Binding Path=Files}"/>

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

答案 2 :(得分: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}"/>

答案 3 :(得分: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"}"/>

答案 4 :(得分:1)

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

您可以使用此代码

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

这样,您的列表框将包含新目录搜索模型的datacontext,并将查找您在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);

        }
    }