在后台线程上使用 OleDbDataAdapter 会导致我的 UI 无法正确更新?
我有一个应用程序,用户可以在其中选择一个 Excel 文件,在另一个线程上使用 OleDbDataAdapter
读取该 excel 文件,一旦读取完成,它就会将我的 ViewModel 中命令的 CanExecute 属性更新为true,因此“保存”按钮已启用。
我的问题是,即使引发命令的 PropertyChanged 事件并且 CanExecute 被评估为 true,UI 上的按钮也永远不会启用,直到用户执行与应用程序交互的操作(单击它、选择文本框等) )
这是一些显示问题的示例代码。只需将其连接到绑定到 SaveCommand
和 SelectExcelFileCommand
的两个按钮,并在 Sheet1< 上创建一个包含名为
ID
列的 Excel 文件/code> 来测试它。
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
_saveCommand = new RelayCommand(Save, () => (FileContents != null && FileContents.Count > 0));
// This runs after ReadExcelFile and it evaluates as True in the debug window,
// but the Button never gets enabled until after I interact with the application!
Debug.WriteLine("SaveCommand: CanExecute = " + _saveCommand.CanExecute(null).ToString());
return _saveCommand;
}
}
private void Save() { }
private ICommand _selectExcelFileCommand;
public ICommand SelectExcelFileCommand
{
get
{
if (_selectExcelFileCommand == null)
_selectExcelFileCommand = new RelayCommand(SelectExcelFile);
return _selectExcelFileCommand;
}
}
private async void SelectExcelFile()
{
var dlg = new Microsoft.Win32.OpenFileDialog();
dlg.DefaultExt = ".xls|.xlsx";
dlg.Filter = "Excel documents (*.xls, *.xlsx)|*.xls;*.xlsx";
if (dlg.ShowDialog() == true)
{
await Task.Factory.StartNew(() => ReadExcelFile(dlg.FileName));
}
}
private void ReadExcelFile(string fileName)
{
try
{
using (var conn = new OleDbConnection(string.Format(@"Provider=Microsoft.Ace.OLEDB.12.0;Data Source={0};Extended Properties=Excel 8.0", fileName)))
{
OleDbDataAdapter da = new OleDbDataAdapter("SELECT DISTINCT ID FROM [Sheet1$]", conn);
var dt = new DataTable();
// Commenting out this line makes the UI update correctly,
// so I am assuming it is causing the problem
da.Fill(dt);
FileContents = new List<int>() { 1, 2, 3 };
OnPropertyChanged("SaveCommand");
}
}
catch (Exception ex)
{
MessageBox.Show("Unable to read contents:\n\n" + ex.Message, "Error");
}
}
private List<int> _fileContents = new List<int>();
public List<int> FileContents
{
get { return _fileContents; }
set
{
if (value != _fileContents)
{
_fileContents = value;
OnPropertyChanged("FileContents");
}
}
}
编辑
我尝试使用 Dispatcher 以稍后的优先级发送 PropertyChanged 事件,并将 PropertyChanged 调用移到异步方法之外,但这两种解决方案都无法正确更新 UI。
如果我删除线程,或者启动在调度程序线程上从 Excel 读取的进程,它确实可以工作,但这两种解决方案都会导致应用程序在读取 Excel 文件时冻结。在后台线程上阅读的全部目的是让用户可以在文件加载时填写表单的其余部分。该应用程序使用的最后一个文件有近 40,000 条记录,导致应用程序冻结一两分钟。
I have an app where users can select an Excel file, that excel file is read using an OleDbDataAdapter
on another thread, and once it is finished being read it updates the CanExecute property of a Command in my ViewModel to true so the Save button is enabled.
My problem is, even though the PropertyChanged event of the command gets raised AND the CanExecute is evaluated as true, the button on the UI never gets enabled until the user does something to interact with the application (click on it, select a textbox, etc)
Here is some sample code that shows the problem. Just hook it up to two buttons bound to SaveCommand
and SelectExcelFileCommand
, and create an excel file with a column called ID
on Sheet1
to test it.
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
_saveCommand = new RelayCommand(Save, () => (FileContents != null && FileContents.Count > 0));
// This runs after ReadExcelFile and it evaluates as True in the debug window,
// but the Button never gets enabled until after I interact with the application!
Debug.WriteLine("SaveCommand: CanExecute = " + _saveCommand.CanExecute(null).ToString());
return _saveCommand;
}
}
private void Save() { }
private ICommand _selectExcelFileCommand;
public ICommand SelectExcelFileCommand
{
get
{
if (_selectExcelFileCommand == null)
_selectExcelFileCommand = new RelayCommand(SelectExcelFile);
return _selectExcelFileCommand;
}
}
private async void SelectExcelFile()
{
var dlg = new Microsoft.Win32.OpenFileDialog();
dlg.DefaultExt = ".xls|.xlsx";
dlg.Filter = "Excel documents (*.xls, *.xlsx)|*.xls;*.xlsx";
if (dlg.ShowDialog() == true)
{
await Task.Factory.StartNew(() => ReadExcelFile(dlg.FileName));
}
}
private void ReadExcelFile(string fileName)
{
try
{
using (var conn = new OleDbConnection(string.Format(@"Provider=Microsoft.Ace.OLEDB.12.0;Data Source={0};Extended Properties=Excel 8.0", fileName)))
{
OleDbDataAdapter da = new OleDbDataAdapter("SELECT DISTINCT ID FROM [Sheet1$]", conn);
var dt = new DataTable();
// Commenting out this line makes the UI update correctly,
// so I am assuming it is causing the problem
da.Fill(dt);
FileContents = new List<int>() { 1, 2, 3 };
OnPropertyChanged("SaveCommand");
}
}
catch (Exception ex)
{
MessageBox.Show("Unable to read contents:\n\n" + ex.Message, "Error");
}
}
private List<int> _fileContents = new List<int>();
public List<int> FileContents
{
get { return _fileContents; }
set
{
if (value != _fileContents)
{
_fileContents = value;
OnPropertyChanged("FileContents");
}
}
}
EDIT
I've tried using the Dispatcher to send the PropertyChanged event at a later priority, and moving the PropertyChanged call outside of the async method, but neither solution works to update the UI correctly.
It DOES work if I either remove the threading, or launch the process that reads from Excel on the dispatcher thread, but both of these solutions cause the application to freeze up while the excel file is being read. The whole point of reading on a background thread is so the user can fill out the rest of the form while the file loads. The last file this app got used for had almost 40,000 records, and made the application freeze for a minute or two.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
据我所知,这可能就是您所需要的。
并调用以下内容
From what I can follow this might be what you need.
And calling the following
不确定,但是如果删除
await
- 有帮助吗?编辑:
我不是 C# 5 方面的专家,但据我所知,
await
等待启动的任务完成......这是一种同步方式,因此在await< 之后/code> 无需进一步检查任务是否已完成即可访问结果...
从帖子中我认为不需要
await
并且它以某种方式“阻止”来自启动任务的OnPropertyChange
调用。编辑 2 - 另一次尝试:
编辑 3 - 解决方案(尽管没有 C# 5):
我创建了一个新的 WPF 应用程序,放置 2 个按钮(
button1
=> 选择 excel 文件,button2
=> Save) 在设计器中...我删除了所有“OnPropertyChanged
”调用(我改为使用this.Dispatch.Invoke
) ...RelayCommand
是 1:1,来自 http://msdn。 microsoft.com/en-us/magazine/dd419663.aspx ...以下是相关的更改源:OP描述的所有问题都消失了:Excel读取在另一个线程上...UI没有冻结...的如果 Excelreading 成功,
Savecommand
将启用...编辑 4:
您可以使用它代替
IsEnabled
...导致CanExecuteChanged
事件在不“重建”SaveCommand
的情况下被触发(这会导致CanExecuteChanged
事件被取消注册,然后重新注册)not sure, but if you remove the
await
- does it help ?EDIT:
I am no expert on C# 5 but what I gather that
await
wait for the launched task(s) to finish... it is a way to synchronize so the after theawait
the result be accessed without further checking whether the task(s) already finished...From the post I think that
await
is not needed and that it somehow "blocks" theOnPropertyChange
call from the insise the launched Task.EDIT 2 - another try:
EDIT 3 - solution (without C# 5 though):
I created a fresh WPF app, put 2 buttons (
button1
=> select excel file,button2
=> Save) in the designer... I removed all "OnPropertyChanged
" calls (I usedthis.Dispatch.Invoke
instead)...RelayCommand
is 1:1 from http://msdn.microsoft.com/en-us/magazine/dd419663.aspx ... following is the relevant changed source:all problems described by the OP are gone: the Excel reading is on another thread... the UI does not freeze... the
Savecommand
gets enabled if the Excelreading is successfull...EDIT 4:
you can use this instead of the
IsEnabled
... causes theCanExecuteChanged
event to be fired without "rebuilding" theSaveCommand
(which causes theCanExecuteChanged
event to be unregistered and then reregistered)我仍然不知道问题是什么,但我找到了解决方法。我只需设置
SaveCommand = null
并引发PropertyChanged
事件来重新创建命令(命令上的set
方法会构建 RelayCommand,如果它为空)。我不知道为什么简单地引发 PropertyChanged 事件不会更新 UI。根据我的调试,即使 UI 没有更新,
get
方法也会再次被调用并在CanExecute = true
处进行评估。I still have no idea what it's problem is, but I have found a workaround. I simply set my
SaveCommand = null
and raise aPropertyChanged
event to re-create the Command (theset
method on the command builds the RelayCommand if it is null).I have no idea why simply raising the PropertyChanged event won't update the UI. According to my Debug, the
get
method is getting called again and evaluating atCanExecute = true
even though the UI doesn't update.