我使用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;
}
}
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);
}
}
}
}
答案 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);
}
}