简体   繁体   English

如何在WPF中制作一个简单的实时更新的进度条?

[英]How to create a simple progress bar that updates in real time in WPF?

I have the following code:我有以下代码:

var progress = new Progress<int>(valor => progressElement.Value = valor);

await Task.Run(() =>
       {
           Application.Current.Dispatcher.BeginInvoke(new Action(() =>
           {
               var children = LogicalTreeHelper.GetChildren(canvas);
               //count the number of children in a separate variable
               var childaux = LogicalTreeHelper.GetChildren(canvas);
               int numChildren = ((List<object>)childaux.OfType<object>().ToList()).Count();

               progressElement.Maximum = numChildren;

               int childcont = 0;

               foreach (var child in children)
               {
                   //long code work
                   childcont++;
                   ((IProgress<int>)progress).Report(childcont);
                   Thread.Sleep(100);
               }
           }));
       });

The result is, the progresss bar updating at the end of the loop, instead of refreshing in real time.结果是,进度条在循环结束时更新,而不是实时刷新。 I cant remove BeginInvoke because then i cant access my canvas element.我无法删除 BeginInvoke,因为这样我就无法访问我的 canvas 元素。 Any help is appreciated.任何帮助表示赞赏。

Assuming you are using a view model, bind the progress-bar value to some value, say MyProgress , then update this value using a Progress<T> :假设您正在使用视图 model,将进度条值绑定到某个值,比如MyProgress ,然后使用Progress<T>更新该值:

int myProgress;
int MyProgress{
  get => myProgress; 
  set{ 
  myProgress; 
  OnPropertyChanged(nameof(MyProgress));
}
public async void StartSlowMethod(){
  var progress = new Progress<int>();
  progress.ProgressChanged += p => MyProgress = p;
  var result = await Task.Run(() => SlowMethod(progress));
  // Handle the result
}
public double SlowMethod(IProgress<int> progress){
   for(int i = 0; i < 1000; i++){
      progress.Report(i);
      ...
   }
}

Note that this is written on free hand and not tested, there might be some errors I missed.请注意,这是徒手编写的,未经测试,可能会遗漏一些错误。 I would recommend checking the official examples.我建议检查官方示例。

You will need to ensure the Max-value of the progress bar has the correct value.您需要确保进度条的最大值具有正确的值。 I usually prefer to report a double from 0-1, and scale this to the progress-range just before displaying.我通常更喜欢报告 0-1 的双精度值,并在显示之前将其缩放到进度范围。 I also prefer to wrap all of this in a helper class to make it easier to start methods on a background thread, and display a progress bar while it is working.我也更喜欢将所有这些包装在一个帮助程序 class 中,以便更容易地在后台线程上启动方法,并在它工作时显示进度条。

What you're doing there is starting up a background thread then that just starts up a process on the ui thread, which seems to be very expensive.你在那里做的是启动一个后台线程,然后它只是在 ui 线程上启动一个进程,这似乎非常昂贵。

Unless this is some sort of much simplified version of your real code, the task is pointless.除非这是您的真实代码的某种大大简化的版本,否则该任务毫无意义。 You might as well just run all that code on the ui thread.您还不如在 ui 线程上运行所有代码。

Your big problem there though is你的大问题是

 Thread.Sleep(100);

You're blocking the UI thread which is the thing would render any changes to UI.您正在阻塞 UI 线程,这会呈现对 UI 的任何更改。

If your method was instead async:如果您的方法是异步的:

       Application.Current.Dispatcher.BeginInvoke(new Action(**async** () 

You could then instead of sleeping the thread do:然后,您可以不让线程休眠,而是执行以下操作:

 await Task.Delay(100);

Which would free up the ui thread for 100ms这将释放 ui 线程 100ms

You should not use UI as a data store though.但是,您不应该将 UI 用作数据存储。 If you instead used mvvm and bound viewmodels which were templated out into UI then you could work with the data in those viewmodels rather than the things in the cannvas.如果您改为使用模板化到 UI 中的 mvvm 和绑定视图模型,那么您可以使用这些视图模型中的数据而不是 cannvas 中的内容。

Then you could likely run your expensive code on a background thread and just dispatch progress changes back to the UI thread.然后,您可能会在后台线程上运行昂贵的代码,并将进度更改分派回 UI 线程。 In fact if you bound the value on a progress bar to a property on a viewmodel implements inotifypropertychanged then you would likely find such a simple binding's change notification was automatically marshalled back to the UI thread and you needed no dispatcher code.事实上,如果您将进度条上的值绑定到 viewmodel 实现 inotifypropertychanged 上的属性,那么您可能会发现这种简单绑定的更改通知会自动编组回 UI 线程,您不需要调度程序代码。

I haven't done this in a while, but I'd recommend using a "background worker" thread, which worked well for me a few times before我已经有一段时间没有这样做了,但我建议使用“后台工作者”线程,它之前对我来说效果很好

I attach a working code that I assume is doing what is needed.我附上了一个工作代码,我认为它正在做需要做的事情。 I added some pieces that I dreamed up.我添加了一些我梦寐以求的作品。 The Handling of the LogicalTreeHelper must run on the UI thread, but is not too heavy. LogicalTreeHelper 的处理必须在 UI 线程上运行,但不会太重。 For each "child" I start a task which is the slow part.对于每个“孩子”,我都会开始一项缓慢的任务。 After the await I update the view model Counter property that is bounded to the ProgressBar.在等待之后,我更新了绑定到 ProgressBar 的视图 model Counter 属性。

Code behind:代码背后:

public partial class MainWindow : Window
    {
        MainViewModel _mainViewModel = new MainViewModel();
        public MainWindow()
        {
            InitializeComponent();
            DataContext = _mainViewModel;
            for(int i =0;i<100;i++)
            {
                Button btn = new Button();
                canvas.Children.Add(btn);
            }
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Start();
        }
        public async void Start()
        {
            var children = LogicalTreeHelper.GetChildren(canvas);
            //count the number of children in a separate variable
            var childaux = LogicalTreeHelper.GetChildren(canvas);
            int numChildren = ((List<object>)childaux.OfType<object>().ToList()).Count();
            _mainViewModel.MaxChildren = numChildren;
            int childcont = 0;
            foreach (var child in children)
            {
                await Task.Run(() =>
                {
                    {
                        //long code work
                        childcont++;
                        Thread.Sleep(100);
                    }
                });
                _mainViewModel.Counter = childcont;
            }  
        }
    }
}

The view model:视图model:

public class MainViewModel : INotifyPropertyChanged
    {
        int _counter;
        public int Counter
        {
            get { return _counter; }
            set { _counter = value; NotifyPropertyChanged(nameof(Counter)); }
        }
        int _maxChildren = 1000;
        public int MaxChildren
        {
            get { return _maxChildren; }
            set { _maxChildren = value; NotifyPropertyChanged(nameof(MaxChildren)); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

The XAML in the main window主 XAML window

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>


        </Grid.RowDefinitions>
        <Button Grid.Column="0" Click="Button_Click">Start!</Button>
        <ProgressBar Grid.Column="1" Value = "{Binding Counter}"  Margin = "10" Maximum = "{Binding MaxChildren}"  
                  Height = "15" IsIndeterminate = "False" />
        <Canvas  x:Name="canvas" Grid.Row="1"/>

    </Grid> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM