如何在WPF中使用ProgressBar

qvtsj1bj  于 2023-11-21  发布在  其他
关注(0)|答案(4)|浏览(116)

我正试图在我的主窗口中加入一个进度条,当一个按钮被按下时,按钮正在运行它的进程。我知道我只是缺少一些简单的东西,但我仍然是WPF的新手,因为我主要使用Windows窗体。
我的XML结构如下:

<Window x:Class="Program1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Program1"
        mc:Ignorable="d"
        Title="Program1" Height="1029" Width="1300" SnapsToDevicePixels="True" BorderThickness="0" Margin="0" ResizeMode="NoResize" Closing="Window_Closing"
        x:Name="FirstWindow">
    <Grid x:Name="Grid1">
        <Button x:Name="btnPopulate" Content="Populate" HorizontalAlignment="Left" Margin="243,66,0,0" VerticalAlignment="Top" Width="118" Height="29" Click="btnPopulate_Click"/>
        <Button x:Name="btnClear" Content="Clear" HorizontalAlignment="Left" Margin="366,66,0,0" VerticalAlignment="Top" Width="118" Height="29" Click="btnClear_Click"/>
        <ProgressBar x:Name="progressBar" HorizontalAlignment="Left" Height="30" Margin="10,943,0,0" VerticalAlignment="Top" Width="351"/>
    </Grid>
</Window>

字符串
我的populate button click方法如下:

private void btnPopulate_Click(object sender, RoutedEventArgs e)
{
    Thread backgroundThread = new Thread(
            new ThreadStart(() =>
            {
                for (int n = 0; n < 100; n++)
                {
                    Thread.Sleep(50);
                    progressBar.Value = n;
                };
            }
        ));
    backgroundThread.Start();
}


我遇到的问题是,我得到了这个错误:
当前上下文中不存在名称“progressBar”
我不确定如何从按钮单击方法访问progressBar控件。
我知道我可能错过了一些简单的东西,但我仍然试图得到WPF的窍门。

a0x5cqrl

a0x5cqrl1#

你不能从一个没有创建控件的线程访问控件(旧的Win32限制)。你必须使用UI Sync Context从后台线程访问UI元素,类似这样
在类定义字段的某处

SynchronizationContext ctx = SynchronizationContext.Current ?? new SynchronizationContext();

字符串
然后使用它:

void RunOnGuiThread(Action action)
{
    this.ctx.Post(o => action(), null);
}


您还可以使用TaskTable来使用任务:

private readonly TaskScheduler uiSyncContext;


然后定义它

this.uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext();


和使用

var task = Task.Factory.StartNew(delegate
{
   /// do something
});

this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate
{
   /// do something that use UI controls
});

public void CompleteTask(Task task, TaskContinuationOptions options, Action<Task> action)
{
   task.ContinueWith(delegate
   {
      action(task);
      task.Dispose();
   }, CancellationToken.None, options, this.uiSyncContext);
}

k5ifujac

k5ifujac2#

简化版本:启动另一个不能修改UI-Thread-Content的Thread
这个解决方案解决了这个问题,但您仍然需要了解MVVM

private void btnPopulate_Click(object sender, RoutedEventArgs e)
        {
            SynchronizationContext context = SynchronizationContext.Current;

            Thread backgroundThread = new Thread(
                    new ThreadStart(() =>
                    {
                        for (int n = 0; n < 100; n++)
                        {
                            Thread.Sleep(50);
                            context?.Post(new SendOrPostCallback((o) =>
                            {

                                progressBar.Value = n;
                            }), null);
                        };

                    }
                ));
            backgroundThread.Start();
        }

字符串

mwyxok5s

mwyxok5s3#

应该使用Dispatcher.InvokeDispatcher.BeginInvoke方法,因为progressBar属于另一个线程。

progressBar.Value = n;

字符串
使用

Dispatcher.Invoke(new Action(()=> { progressBar.Value = n; }));


你的代码应该可以正常工作,除非名字中有一些错别字。
请参阅this post以获得填充ProgressBar的更好选择。
此外,Grid和Margin并不是一个好的选择。相反,使用DockPanel或在Grid中添加RowDefinitions或ColumnDefinitions。

t1rydlwq

t1rydlwq4#

你的btnPopulate_Click()方法声明在哪里?如果在MainWindow类中,那么包含元素引用的字段应该存在。请提供一个好的Minimal, Complete, and Verifiable code example,它可以可靠地重现你描述的编译时错误消息。
同时...
请注意,您的代码在其他方面也是完全错误的。最好使用MVVM,并简单地在视图模型属性上设置进度条状态值,将该属性绑定到进度条。您还应该使用其他机制,而不是启动专用线程来处理后台操作。我理解您发布的代码只是为了练习,但养成正确做事的习惯是件好事
这里有一些选择,会比你现在有更好的,也会比其他两个答案张贴到目前为止。
如果处理一个长时间运行的操作,并且该操作具有良好的间歇性检查点,您可以在其中报告进度:
首先,定义视图模型:

class ViewModel : INotifyPropertyChanged
{
    private double _progressValue;

    public double ProgressValue
    {
        get { return _progressValue; }
        set { _UpdatePropertyField(ref _progressValue, value); }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void _UpdatePropertyField<T>(
        ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (!EqualityComparer.Default.Equals(field, value))
        {
            field = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

字符串
然后在窗口的C#代码中:

class MainWindow : Window
{
    private readonly ViewModel _viewModel = new ViewModel();

    public MainWindow()
    {
        DataContext = _viewModel;
        InitializeComponent();
    }

    private void btnPopulate_Click(object sender, RoutedEventArgs e)
    {
        Task.Run(() =>
        {
            for (int n = 0; n < 100; n++)
            {
                // simulates some costly computation
                Thread.Sleep(50);

                // periodically, update the progress
                _viewModel.ProgressValue = n;
            }
        });
    }
}


然后在XAML中,将视图模型的ProgressValue属性绑定到ProgressBar.Value属性:

<Window x:Class="Program1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Program1"
        mc:Ignorable="d"
        Title="Program1" Height="1029" Width="1300" SnapsToDevicePixels="True"
        BorderThickness="0" Margin="0" ResizeMode="NoResize" Closing="Window_Closing"
        x:Name="FirstWindow">
  <Grid x:Name="Grid1">
    <Button x:Name="btnPopulate" Content="Populate" HorizontalAlignment="Left"
            Margin="243,66,0,0" VerticalAlignment="Top" Width="118" Height="29"
            Click="btnPopulate_Click"/>
    <Button x:Name="btnClear" Content="Clear" HorizontalAlignment="Left"
            Margin="366,66,0,0" VerticalAlignment="Top" Width="118" Height="29"
            Click="btnClear_Click"/>
    <ProgressBar HorizontalAlignment="Left" Height="30" Margin="10,943,0,0"
                 VerticalAlignment="Top" Width="351" Value="{Binding ProgressValue}"/>
  </Grid>
</Window>


如果你的长时间运行的操作实际上是由较小的异步操作组成的,那么你可以这样做:

private async void btnPopulate_Click(object sender, RoutedEventArgs e)
{
    for (int n = 0; n < 100; n++)
    {
        // simulates one of several (e.g. 100) asynchronous operations
        await Task.Delay(50);

        // periodically, update the progress
        _viewModel.ProgressValue = n;
    }
}


请注意,在第二个示例中,您可以完全跳过视图模型,因为progress值的赋值发生在UI线程中,因此直接赋值给ProgressBar.Value属性是安全的。但无论如何,您仍然应该使用视图模型,因为这更符合标准WPF范例和WPF API的期望(也就是说,你可以用另一种方式来做,但你会与WPF API的设计者的意图作斗争,这将导致更多的挫折和困难)。

相关问题