Update WPF ProgressBar that is bound to a dependency object running a long process
I want to bind the value property of a WPF ProgressBar to a dependency property that is updated during a long running process. If the long running process is called from the main thread then this blocks the UI (and hence the ProgressBar) from updating until the process completes - preventing the desired progress through the process being shown. The long running process also cannot be run by spinning a seperate thread because it is not possible to update a dependency property from a different thread to its owner (i.e. the thread on which it was created).
In the code below, when the button is clicked, the long running process runs and the progress bar jumps from 0% to 100% when it completes. Instead, I want to be able to click the button and have the progress bar show the progress through the long running progress (i.e. not just updating from 0% to 100% when the process finishes but showing a smooth progression).
MainWindow.xaml
<Window x:Class="ProgressBarTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<Button Width="200" Height="50" x:Name="btnRun" Click="btnRun_Click">Run Process</Button>
<ProgressBar Width="200" Height="20" x:Name="pbProgress" Minimum="0" Maximum="100" Value="{Binding Path=MyFoo.ProgressValue}"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Threading;
namespace ProgressBarTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public Foo MyFoo { get; set; }
public MainWindow()
{
MyFoo = new Foo();
InitializeComponent();
}
private void btnRun_Click(object sender, RoutedEventArgs e)
{
btnRun.IsEnabled = false;
开发者_运维百科 MyFoo.LongRunningProcess(); // Since this runs on same thread as UI, progress bar does not update until the long running process completes.
btnRun.IsEnabled = true;
}
}
public class Foo : DependencyObject
{
public static readonly DependencyProperty ProgressValueProperty = DependencyProperty.Register("ProgressValue", typeof(double), typeof(Foo));
public double ProgressValue
{
get { return (double)GetValue(ProgressValueProperty); }
set
{
SetValue(ProgressValueProperty, value);
}
}
public Foo()
{
ProgressValue = 0;
}
public void LongRunningProcess()
{
do
{
ProgressValue += 1;
Thread.Sleep(30);
}
while (ProgressValue < 100);
}
}
}
P.S. I know there is a way I can do this by passing the ProgressBar instance as an argument to the long running process so that it can update it directly through Dispatcher.Invoke, but that is not what I want. I want to have the progress bar update through binding to a dependency property.
Thanks
Sean
I'll try to be nicer than Euphoric.
You need to run your LongRunningProcess
using a BackgroundWorker that runs the process on a different thread and then update the property using the ProgressChanged event
// This event handler updates the progress bar.
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
MyFoo.ProgressValue = e.ProgressPercentage;
}
The UI needs to be updated from the main thread. Hence you need to use the Dispather to update the property.
Use this from within the LongRunningProcess() to put the call on the Dispatcher queue:
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action<double>(UpdateData), value);
and the implement the updating method like so:
private void UpdateData(double value){
MyFoo.ProgressValue = value;
}
Hope this helps.
I Dunno if you found a solution to your issue, but this is how i have solved a simular problem in the past. This enables you to bind to your DP and just update the normal property
public static readonly DependencyProperty progressProperty = DependencyProperty.Register("progress", typeof(int), typeof(this));
public int progress
{
get
{
return (int)this.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Background,
(DispatcherOperationCallback)delegate { return GetValue(progressProperty ); },
progressProperty );
}
protected set
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { SetValue(progressProperty , value); }, value);
}
}
精彩评论