簡體   English   中英

如何在WPF中使用progressBar

[英]How to use progressBar in WPF

我試圖在按下按鈕並且按鈕正在運行其過程之后,將進度條合並到我的主窗口中。 我知道我只是缺少一些簡單的東西,但是由於我主要使用Windows窗體,所以我還是WPF的新手。

我的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>

我有如下的填充按鈕單擊方法:

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的困擾。

您不能從未創建控件的線程訪問控件(舊的Win32限制)。 您必須使用UI Sync Context從后台線程訪問UI元素,如下所示

在類中的某處定義字段

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

然后使用它:

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

您還可以使用TaskScheduler使用任務:

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);
}

您應該使用Dispatcher.InvokeDispatcher.BeginInvoke方法,因為progressBar屬於另一個線程。 換句話說,代替

progressBar.Value = n;

采用

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

並且您的代碼應該可以正常工作,除非名稱中有錯字。

請參閱此帖子 ,以在填充ProgressBar時獲得更好的選擇。

此外,網格和Margin不是一個好的選擇。 而是使用DockPanel或將RowDefinitions或ColumnDefinitions添加到網格中。

您的btnPopulate_Click()方法在哪里聲明? 如果在MainWindow類中,則包含元素引用的字段應該存在。 請提供一個良好的最小,完整和可驗證的代碼示例 ,該示例可靠地重現您描述的編譯時錯誤消息。

同時…

請注意,您的代碼在其他方面也是完全錯誤的。 最好使用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;
    }
}

請注意,在第二個示例中,您可以完全跳過視圖模型,因為進度值的分配發生在UI線程中,因此直接在此處直接分配給ProgressBar.Value屬性是安全的。 但是無論如何,您仍然應該使用視圖模型,因為這與標准WPF范例和WPF API的期望保持了一致(即,您可以通過其他方式做到這一點,但您將與之抗衡設計人員的意圖)。 WPF API,這將導致更多的挫敗感和難度)。

simplyfied版本:您啟動另一個Thread不能修改UI線程內容。

該解決方案可以解決該問題,但是您仍然應該了解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();
        }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM