[英]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.Invoke
或Dispatcher.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.