2

我有一个应用程序,用户可以在其中选择一个 Excel 文件,该 Excel 文件是使用OleDbDataAdapter另一个线程上的一个读取的,一旦读取完成,它会将我的 ViewModel 中的命令的 CanExecute 属性更新为 true,因此启用了“保存”按钮。

我的问题是,即使命令的 PropertyChanged 事件被引发并且 CanExecute 被评估为 true,UI 上的按钮也永远不会启用,直到用户执行某些操作与应用程序交互(单击它,选择一个文本框等) )

这是一些显示问题的示例代码。只需将其连接到绑定到SaveCommand和的两个按钮SelectExcelFileCommand,然后创建一个带有调用列的 excel 文件IDSheet1测试它。

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 条记录,并且使应用程序冻结了一两分钟。

4

3 回答 3

1

不确定,但如果你删除await- 有帮助吗?

编辑:

我不是 C# 5 方面的专家,但我收集到的是await等待启动的任务完成......这是一种同步方式,因此在await访问结果之后无需进一步检查任务是否已经完成...从帖子中我认为这await不是必需的,并且它以某种方式“阻止”了OnPropertyChange来自已启动任务的调用。

编辑 2 - 再试一次:

if (dlg.ShowDialog() == true)
    {
        string FN = dlg.FileName;
        Task.Factory.StartNew(() => ReadExcelFile(FN));
    }

编辑 3 - 解决方案(虽然没有 C# 5):

我创建了一个新的 WPF 应用程序,在设计器中放置了 2 个按钮(button1=> 选择 excel 文件,button2=> 保存)...我删除了所有“ OnPropertyChanged”调用(我使用this.Dispatch.Invoke了)...RelayCommandhttp:// 1:1 msdn.microsoft.com/en-us/magazine/dd419663.aspx ...以下是相关的更改来源:

private  void SelectExcelFile()
{
    var dlg = new Microsoft.Win32.OpenFileDialog();
    dlg.DefaultExt = ".xls|.xlsx";
    dlg.Filter = "Excel documents (*.xls, *.xlsx)|*.xls;*.xlsx";

    if (dlg.ShowDialog() == true)
    {
        Task.Factory.StartNew(() => ReadExcelFile(dlg.FileName));
    }
}

private List<int> _fileContents = new List<int>();

public List<int> FileContents
{
    get { return _fileContents; }
    set 
    {
        if (value != _fileContents)
        {
            _fileContents = value;

            this.Dispatcher.Invoke ( new Action (delegate() 
            {
                button2.IsEnabled = true;
                button2.Command = SaveCommand;
            }),null);
        }
    }
}

private void button1_Click(object sender, RoutedEventArgs e)
{
    button2.IsEnabled = false;
    button2.Command = null;
    SelectExcelFileCommand.Execute(null);
}

private void button2_Click(object sender, RoutedEventArgs e)
{
    SaveCommand.Execute(null);
}

OP 描述的所有问题都消失了:Excel 读取在另一个线程上...... UI 没有冻结......Savecommand如果 Excelreading 成功,则启用......

编辑4:

                this.Dispatcher.Invoke(new Action(delegate()
                {
                    CommandManager.InvalidateRequerySuggested();
                }), null);

您可以使用它而不是IsEnabled... 导致CanExecuteChanged事件在不“重建”的情况下被触发SaveCommand(这导致CanExecuteChanged事件被取消注册然后重新注册)

于 2011-07-22T16:47:18.027 回答
1

据我所知,这可能是您需要的。

public static void ExecuteWait(Action action)
{
   var waitFrame = new DispatcherFrame();

   // Use callback to "pop" dispatcher frame
   action.BeginInvoke(dummy => waitFrame.Continue = false, null);

   // this method will wait here without blocking the UI thread
   Dispatcher.PushFrame(waitFrame);
}

并调用以下

    if (dlg.ShowDialog() == true)         
    {             
        ExecuteWait(()=>ReadExcelFile(dlg.FileName));
        OnPropertyChanged("SaveCommand");
    }     
于 2011-08-08T20:51:33.810 回答
0

我仍然不知道这是什么问题,但我找到了解决方法。我只需设置 mySaveCommand = null并引发一个PropertyChanged事件来重新创建命令(set如果命令为空,则命令上的方法构建 RelayCommand)。

我不知道为什么简单地引发 PropertyChanged 事件不会更新 UI。根据我的调试,即使 UI 没有更新,该get方法也会再次被调用并评估。CanExecute = true

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 [File Number] FROM [Sheet1$]", conn);
            var dt = new DataTable();

            // Line that causes the problem
            da.Fill(dt);

            FileContents = new List<int>() { 1, 2, 3 };

            // Does NOT update the UI even though CanExecute gets evaluated at True after this runs
            // OnPropertyChanged("SaveCommand");

            // Forces the Command to rebuild which correctly updates the UI
            SaveCommand = null;  

        }
   }
    catch (Exception ex)
    {
        MessageBox.Show("Unable to read contents:\n\n" + ex.Message, "Error");
    }
}

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!
        Debug.WriteLine("SaveCommand: CanExecute = " + _saveCommand.CanExecute(null).ToString());
        return _saveCommand;
    }
    set
    {
        if (_saveCommand != value)
        {
            _saveCommand = value;
            OnPropertyChanged("SaveCommand");
        }
    }
}
于 2011-07-22T20:06:26.780 回答