目前,我有一个按钮,当用户点击它时,它会查找已准备好并包含文件的特定CD-ROM驱动器。有时,当用户单击按钮时,按下鼠标按钮并且程序会挂起一段不确定的时间,直到计算机读取CD-ROM驱动器。
我制作了进度条,但我注意到了一些事情:
1)程序在检查CD驱动器的方法被调用之前挂起/冻结。因此,我无法设置进度条以在调用方法时显示。似乎程序在单击按钮时以及用户同时放入CD时挂起。单击按钮并且鼠标仍然按下/直到系统检测到CD驱动器后,如何显示进度条?
2)我对如何实现后台工作者感到困惑。我看起来很喜欢的例子,但没有一个匹配MVVM(没有代码隐藏)方法的不确定进度条。
3)操作完成后如何让窗口消失?目前,我有一个取消按钮(绝对没用)。
这是我到目前为止所建立的。不知道如何继续:
进度条:
<Grid>
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="4" Background="#EEEEEE" HorizontalAlignment="Left" Height="110" VerticalAlignment="Top" Width="295" />
<StackPanel>
<Label x:Name="lblProgress"/>
<ProgressBar x:Name="progress" Height="25" Width="270" IsIndeterminate="True" Foreground="Green"></ProgressBar>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="225,10,0,0" RenderTransformOrigin="0.083,0.526">
<Button x:Name="btnCancel" Width="60" Content="Cancel" Command="{Binding CloseCommand}"/>
</StackPanel>
</StackPanel>
</Grid>
我有一个ProgressBarViewModel,其中包含允许用户取消进度窗口的命令。另外,我还有另一个ViewModel需要在里面调用progressBar对话框,但是我不确定在哪里调用它,因为如果我在我的方法中调用它,按钮仍然挂起而不显示进度条。
我注意到我在代码隐藏中使用了Button_PreviewMouseDown方法,但是,当鼠标关闭且系统显示进度条但是我不想使用代码隐藏时,进度条会正确显示,因为我有进度条在另一种观点。
目前,对于我的导入按钮,所有附加的命令都是一个调用驱动器搜索CD-ROM驱动器的方法的命令。
MainViewModel:
public ICommand ImportCDFilePathCommand
{
get
{
return new RelayCommand(ImportCDFilePath, null);
}
}
private void ImportCDFilePath()
{
// dialogService.ShowDialog("Progress", progressBarWindow); <---Does not get called until the operation is done
//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));
//.... 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.
答案 0 :(得分:1)
嗨我在这里有点快,你使用的方法没有任何async-await重载。所以你可以使用旧的BackgroundWorker。我在这里为你提供了一个非常简单的例子,快速制作(制作食物)。 (unrun)示例仅报告进度0或100,但它不会至少冻结您的UI。报告进度时,您发送一个int(进度)和一个userstate对象,可能是您要发送的任何内容。只需投下它并做你想做的事情:)
public class TestViewModel : INotifyPropertyChanged
{
private int progress;
private BackgroundWorker bgWorker;
private bool isBusy;
private readonly Dispatcher dispatcher;
private ObservableCollection<DriveInfo> 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<DriveInfo> 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<DriveInfo>(e.UserState as IEnumerable<DriveInfo>);
}
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<DriveInfo> 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<DriveInfo> 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&#34; statemachine-auto treader&#34;。
干杯,
了Stian
答案 1 :(得分:0)
您可以使用以下方法将任何方法包装在异步中......
await Task.Factory.StartNew<T>(()=>{ ... Do something here...});
T是返回类型的泛型。在你的继电器命令示例中:
... = new RelayCommand(async () =>{ await DoSomethingAsync()});
则...
private async DoSomethingAsync()
{
await Task.Factory.StartNew(()=> {...});
}
你的......是你想要的。
答案 2 :(得分:0)
在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");
}
}
}