不确定进度条

时间:2014-07-29 17:51:15

标签: c# wpf mvvm progress-bar backgroundworker

目前,我有一个按钮,当用户点击它时,它会查找已准备好并包含文件的特定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.

3 个答案:

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