【问题标题】:Indeterminate Progress Bar不确定的进度条
【发布时间】:2014-07-29 17:51:15
【问题描述】:

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

主视图模型:

 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.

【问题讨论】:

  • 线程/异步 ImportReagentLotFilePath。之后在 UI 线程上报告进度条。
  • 我不确定您使用的是哪个框架,但如果您使用的是 4.5+,这将是完美的异步方法。另一种选择是使用 BackgroundWorker,或者只是在您的方法中创建并启动一个线程。
  • 我使用的是 .NET 4.5。为什么使用 Async vs. Background Worker?

标签: c# wpf mvvm progress-bar backgroundworker


【解决方案1】:

您好,我这里有点快,您使用的方法没有任何异步等待重载。所以你可以使用旧的 BackgroundWorker。我在这里为您提供了一个非常简单的示例,快速制作(制作食物)。 (未运行的)示例只会报告进度 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 “statemachine-auto 踏步机”。

干杯,

斯蒂安

【讨论】:

  • 为什么要为驱动器使用 ObservableCollection?
  • @KalaJ 我以为你会在你的 UI 中显示这个。当集合发生变化时,OC 会通知 GUI。
  • 不,我不需要在我的 UI 中显示它。我只需要一个不确定的进度条来显示(没有计数或数字),当操作完成时,带有进度条的窗口会关闭。现在我收到了 CdRoms 集合的空异常。
  • @KalaJ 那里的代码可能会更干净,通常我使用包含调度程序的基类和类似的东西。确保调度程序类不为空,并特别在报告方法和涉及更改绑定到 UI 的属性的任何内容中创建一些断点。我在这里很快,很少使用 bgw 类了......
  • 嗨,Stian,很抱歉,我发现很难遵循您的代码。由于您投入的所有工作,我会赞成您的回答。我仍然很难让它工作,所以也许我错过了一些东西。我敢肯定,肯定有更简单的方法可以做到这一点,所以我将尝试简化代码。
【解决方案2】:

您可以使用以下内容将任何方法包装在异步中...

await Task.Factory.StartNew<T>(()=>{ ... Do something here...});

T 是返回类型的泛型。在您的中继命令示例中:

... = new RelayCommand(async () =>{ await DoSomethingAsync()});

那么……

private async DoSomethingAsync()
{
    await Task.Factory.StartNew(()=> {...});
}

你的......就是你想要的任何东西。

【讨论】:

  • 包装任何方法是什么意思?
  • @KalaJ 参见上面的示例 :)
  • 任何同步的东西都可以像上面一样被包装起来,使其异步。
  • 我从来没有使用过包装器...你能把我链接到包装器教程或为什么要使用包装器/什么是包装器?我大致了解它是什么,但从未使用过。
  • 取上面的sn-p,用一段代码替换do something here。坐下来,运行该应用程序,然后惊叹于您的代码似乎同时在两个地方运行!想要更多,请阅读blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
【解决方案3】:

在 SO WPF 聊天中 Julien 和 NetSCAPE 的帮助下,我发现了一些我做错的事情:

-我的后台线程中有对话消息,这些消息不应该出现在执行后台线程的方法中。因此,这就是我不断收到 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");
        }    
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-05
    • 1970-01-01
    • 2011-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多