У меня есть приложение, в котором пользователи могут выбрать файл Excel, этот файл Excel читается с использованием OleDbDataAdapter
в другом потоке, и после завершения чтения он обновляет свойство CanExecute команды в моей ViewModel до true, так чтокнопка Сохранить включена.
Моя проблема в том, что, хотя событие PropertyChanged команды вызывается и CanExecute оценивается как истина, кнопка в пользовательском интерфейсе никогда не включается, пока пользователь не сделает что-то для взаимодействия с приложением (щелкните по нему)., выберите текстовое поле и т. д.)
Вот пример кода, который показывает проблему.Просто подключите его к двум кнопкам, привязанным к SaveCommand
и SelectExcelFileCommand
, и создайте файл Excel со столбцом с именем ID
на Sheet1
, чтобы проверить его.
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 за пределы асинхронного метода, но ни одно из решений не работает для корректного обновления пользовательского интерфейса.
Это работаетесли я удаляю поток или запускаю процесс, который читает из Excel в потоке диспетчера, но оба эти решения приводят к зависанию приложения во время чтения файла Excel.Весь смысл чтения в фоновом потоке заключается в том, чтобы пользователь мог заполнить оставшуюся часть формы во время загрузки файла.В последнем файле, для которого использовалось это приложение, было почти 40 000 записей, и приложение зависало на одну или две минуты.