2

ユーザーが Excel ファイルを選択できるアプリがあり、その Excel ファイルはOleDbDataAdapter別のスレッドを使用して読み取られ、読み取りが完了すると、ViewModel の Command の CanExecute プロパティが true に更新され、[保存] ボタンが有効になります。

私の問題は、コマンドの PropertyChanged イベントが発生し、CanExecute が true として評価されたとしても、ユーザーがアプリケーションと対話するために何かを行う (クリックする、テキストボックスを選択するなど) まで、UI のボタンが有効にならないことです。 )

問題を示すサンプル コードを次に示します。SaveCommandとにバインドされた 2 つのボタンに接続し、テストするために呼び出され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 レコードがあり、アプリケーションが 1、2 分間フリーズしました。

4

3 に答える 3

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 に答える
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ファイルを選択、 =>保存)を配置しました...すべての " "呼び出しを削除しました(代わりに使用しました)... http://から1:1ですmsdn.microsoft.com/en-us/magazine/dd419663.aspx ...以下は、関連する変更されたソースです。button2OnPropertyChangedthis.Dispatch.InvokeRelayCommand

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がフリーズしません...ExcelSavecommandの読み取りが成功すると有効になります...

編集4:

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

IsEnabled...の代わりにこれを使用すると、CanExecuteChanged「再構築」せずにイベントが発生しますSaveCommand(これにより、CanExecuteChangedイベントが登録解除されてから再登録されます)

于 2011-07-22T16:47:18.027 に答える
0

何が問題なのかはまだわかりませんが、回避策を見つけました。コマンドを再作成するために、単に mySaveCommand = nullを設定してイベントを発生させます (コマンドのメソッドが null の場合、RelayCommand を構築します)。PropertyChangedset

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 に答える