在后台线程上使用 OleDbDataAdapter 会导致我的 UI 无法正确更新?

发布于 2024-11-25 15:44:37 字数 2953 浏览 1 评论 0原文

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

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

这是一些显示问题的示例代码。只需将其连接到绑定到 SaveCommandSelectExcelFileCommand 的两个按钮,并在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

樱桃奶球 2024-12-02 15:44:37

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

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

From what I can follow this might be what you need.

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

And calling the following

    if (dlg.ShowDialog() == true)         
    {             
        ExecuteWait(()=>ReadExcelFile(dlg.FileName));
        OnPropertyChanged("SaveCommand");
    }     
情徒 2024-12-02 15:44:37

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

编辑:

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

编辑 2 - 另一次尝试:

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

编辑 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 ...以下是相关的更改源:

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没有冻结...的如果 Excelreading 成功,Savecommand 将启用...

编辑 4:

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

您可以使用它代替 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 the await 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" the OnPropertyChange call from the insise the launched Task.

EDIT 2 - another try:

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

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 used this.Dispatch.Invoke instead)... RelayCommand is 1:1 from http://msdn.microsoft.com/en-us/magazine/dd419663.aspx ... following is the relevant changed source:

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

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:

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

you can use this instead of the IsEnabled... causes the CanExecuteChanged event to be fired without "rebuilding" the SaveCommand (which causes the CanExecuteChanged event to be unregistered and then reregistered)

梅窗月明清似水 2024-12-02 15:44:37

我仍然不知道问题是什么,但我找到了解决方法。我只需设置 SaveCommand = 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");
        }
    }
}

I still have no idea what it's problem is, but I have found a workaround. I simply set my SaveCommand = null and raise a PropertyChanged event to re-create the Command (the set 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 at CanExecute = true even though the UI doesn't update.

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");
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文