不确定进度条
目前,我有一个按钮,当用户点击它时,它会查找已准备好并包含文件的特定CD-ROM驱动器。 有时,当用户单击按钮时,单击按钮会导致鼠标按下,程序会挂起一段不确定的时间,直到计算机读取CD-ROM驱动器为止。
我做了进度条,但我注意到了一些事情:
1)程序在检查CD驱动器的方法被调用之前挂起/冻结。 因此,我无法设置在调用方法时显示的进度条。 似乎程序在单击按钮时以及用户同时放入CD时挂起。 单击按钮并且鼠标仍然按下/直到系统检测到CD驱动器后,如何显示进度条?
2)我对如何实现Background Worker感到困惑。 我看起来很喜欢的例子,但没有一个匹配MVM(没有代码隐藏)方法的不确定进度条。
3)操作完成后如何使窗口消失? 目前,我有一个取消按钮(绝对没用)。
这是我到目前为止所建立的。 不知道如何继续:
进度条:
我有一个ProgressBarViewModel,其中包含允许用户取消进度窗口的命令。另外,我还有另一个ViewModel,我需要在里面调用progressBar对话框,但我不知道在哪里调用它,因为如果我在里面调用它方法,按钮仍然挂起而不显示进度条。
我注意到如果我在代码隐藏中使用Button_PreviewMouseDown方法,但是,当鼠标关闭并且系统显示进度条但我不想使用代码隐藏时,进度条会正确显示,因为我在另一个视图中有进度条。
目前,对于我的导入按钮,所有附加的命令都是一个调用驱动器搜索CD-ROM驱动器的方法的命令。
MainViewModel:
public ICommand ImportCDFilePathCommand { get { return new RelayCommand(ImportCDFilePath, null); } } private void ImportCDFilePath() { // dialogService.ShowDialog("Progress", progressBarWindow); x.DriveType == DriveType.CDRom); // Get all the cd roms var cdRoms = allDrives.Where(x=>x.DriveType==DriveType.CDRom && allDrives.Any(y=>y.IsReady)); //.... There is other code that is commented out too long and not necessary }
编辑:
使用BackgroundWorker的一些尝试:
static BackgroundWorker _bw = new BackgroundWorker(); //constructor MainViewModel() { _bw.DoWork += bw_DoWork; _bw.RunWorkerAsync("Message to worker"); } void bw_DoWork(object sender, DoWorkEventArgs e) { // This is called on the worker thread Console.WriteLine(e.Argument); // writes "Message to worker" // Perform time-consuming task... ImportCDFilePath(); }
我得到的错误:
The calling thread must be STA, because many UI components require this.
嗨,我在这里有点快,你使用的方法没有任何async-await重载。 所以你可以使用旧的BackgroundWorker。 我在这里为你提供了一个非常简单的例子,快速制作(制作食物)。 (unrun)示例仅报告进度0或100,但它不会至少冻结您的UI。 报告进度时,您发送一个int(进度)和一个用户对象,可能是您要发送的任何对象。 只是施展它,做你想要的:)
public class TestViewModel : INotifyPropertyChanged { private int progress; private BackgroundWorker bgWorker; private bool isBusy; private readonly Dispatcher dispatcher; private ObservableCollection cdRoms; public Int32 Progress { get { return progress; } set { if (value == progress) return; progress = value; OnPropertyChanged(); } } public bool IsBusy { get { return isBusy; } set { if (value.Equals(isBusy)) return; isBusy = value; OnPropertyChanged(); } } public ICommand ImportCDFilePathCommand { get { return new RelayCommand(ImportReagentLotFilePath); } } public ObservableCollection CdRoms { get { return cdRoms; } set { if (Equals(value, cdRoms)) return; cdRoms = value; OnPropertyChanged(); } } // This one made your app crash if you defined it directly in the xaml as datacontext and not were using a viewmodellocator public TestViewModel(Dispatcher dispatcher) // ugh I'm sure there is an interface for this, feed your UI dispatcher here { this.dispatcher = dispatcher; } // Add this one! public TestViewModel() { this.dispatcher = App.Current.Dispatcher; // Bad pie } private void ImportReagentLotFilePath() { IsBusy = true; Progress = 0; bgWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true }; bgWorker.DoWork += bgWorker_DoWork; bgWorker.ProgressChanged += bgWorker_ProgressChanged; bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted; bgWorker.RunWorkerAsync(/*whatever parameter you want goes here*/); } void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // you are done! Progress = 100; CdRoms = new ObservableCollection (e.UserState as IEnumerable ); } void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { // Notifty your gui changes here forinstance, this method will be called on the gui thread. Just cast/parse what you feed Progress = e.ProgressPercentage; if (Progress == 100) IsBusy = false; } void bgWorker_DoWork(object sender, DoWorkEventArgs e) { try { DriveInfo[] allDrives = DriveInfo.GetDrives(); bool cdRomExists = allDrives.Any(x => x.DriveType == DriveType.CDRom); IEnumerable cdroms = allDrives.Where(x => x.DriveType == DriveType.CDRom && allDrives.Any(y => y.IsReady)); // reports the progress on the ui thread.... bgWorker.ReportProgress(Progress,cdroms); } catch (Exception ex) { // errror handling + cancel run dispatcher.BeginInvoke((Action) (() => { IsBusy = false; Progress = 0; })); } } public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] // remove if you are not using R# protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } }
使用任务:
// Alternatively use a task..... public ICommand TaskTestCommand { get { return new RelayCommand(DoStuffAsync); } } public Task DoStuffAsync() { Task tcs = Task.Factory.StartNew(() => { try { // No awaits... please note that anything bound in the gui must be changed on the dispatcher DriveInfo[] allDrives = DriveInfo.GetDrives(); bool cdRomExists = allDrives.Any(x => x.DriveType == DriveType.CDRom); IEnumerable cdroms = allDrives.Where(x => x.DriveType == DriveType.CDRom && allDrives.Any(y => y.IsReady)); } catch (Exception ex) { // handle your errors here. Note that you must check the innerexception for the real fault System.Diagnostics.Trace.WriteLine(ex.ToString()); } }).ContinueWith((e) => { // this code is run when the task is completed... if(e.Exception!=null) { // hande error.. / } else { // complete.. do whatever here } }); return tcs; }
希望它能帮助您朝着正确的方向前进! 我实际上有点惊讶,你所使用的方法没有async-await重载,因为它允许你使用漂亮的async-await“statemachine-auto treader”。
干杯,
了Stian
您可以使用以下方法将任何方法包装在异步中…
await Task.Factory.StartNew(()=>{ ... Do something here...});
T是返回类型的通用。 在你的relay命令示例中:
... = new RelayCommand(async () =>{ await DoSomethingAsync()});
然后…
private async DoSomethingAsync() { await Task.Factory.StartNew(()=> {...}); }
你的……是你想要的。
在Julien和NetSCAPE的帮助下,在SO WPF聊天中,我发现了一些我做错的事情:
– 我的后台线程中有对话框消息,不应该在执行后台线程的方法中。 因此,这就是我不断收到STA错误的原因。
– 我需要做的是使用Task.Run或Task.Factory.StartNew而不是与await / async配对,因为它在运行之前等待执行任务。
以下是删除对话框消息后我的回答:
private void DoSomethingAsync() { ProgressBarVisibility = Visibility.Visible; Task.Factory.StartNew(() => { PerformCDDetection(); }).ContinueWith(t => { ProgressBarVisibility = Visibility.Collapsed; }); } public ICommand ImportFilePathCommand { get { return new RelayCommand(() => { DoSomethingAsync(); }); } } private void PerformCDDetection() { //Gets all the drives DriveInfo[] allDrives = DriveInfo.GetDrives(); //checks if any CD-Rom exists in the drives var cdRomExists = allDrives.Any(x => x.DriveType == DriveType.CDRom); // Get all the cd roms var cdRoms = allDrives.Where(x => x.DriveType == DriveType.CDRom && allDrives.Any(y => y.IsReady)); if (cdRomExists.Equals(true)) { // Loop through the cd roms collection foreach(var cdRom in cdRoms) { Console.WriteLine("Drive {0}", cdRom.Name); Console.WriteLine(" File type: {0}", cdRom.DriveType); if (cdRom.IsReady == true) { if (cdRom.DriveType == DriveType.CDRom) { DirectoryInfo di = new DirectoryInfo(cdRom.RootDirectory.Name); var file = di.GetFiles("*.xml", SearchOption.AllDirectories).FirstOrDefault(); if (file == null) { Console.WriteLine("failed to find file"); } else { foreach (FileInfo info in di.GetFiles("*.xml", SearchOption.AllDirectories)) { Debug.Print(info.FullName); break; // only looking for the first one } break; } } } else if (cdRom.IsReady == false) { Console.WriteLine("Cd-ROM is not ready"); break; } } } else { Console.WriteLine("CD ROM is not detected"); } }