WPF更新UI线程实现进度条功能
我的写法
xml
<Page x:Class="CableInspectionScreen.ConfigPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CableInspectionScreen.Utils"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignHeight="1080" d:DesignWidth="1920"
Title="ConfigPage">
<Page.Resources>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibility"/>
</Page.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--存图设置-->
<Border BorderThickness="2" BorderBrush="Black" Margin="10" Height="Auto">
<StackPanel Orientation="Vertical" Margin="10">
<StackPanel Orientation="Horizontal" Margin="30,30,0,20">
<TextBlock Text="存图设置" FontSize="40" FontWeight="Bold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding ShowSaveImageErrorInfo}" VerticalAlignment="Center" Foreground="Red" FontSize="18"
Margin="20,0,0,0" Visibility="{Binding IsVisible,Converter={StaticResource BooleanToVisibility}}"/>
</StackPanel>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50"
hc:InfoElement.Placeholder="第一座桥" Text="{Binding BridgeName}"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="桥梁名称"
hc:InfoElement.Necessary="True" Margin="30,30,30,20"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50"
Text="{Binding DownBridgeName}" IsReadOnly="True"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="下位机桥梁名称"
Margin="30,30,30,20" Opacity="0.6"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50"
hc:InfoElement.Placeholder="NS01" Text="{Binding CableName}"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="缆索编号"
hc:InfoElement.Necessary="True" Margin="30,30,30,20"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50"
Text="{Binding DownCableName}" IsReadOnly="True"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="下位机缆索编号"
Margin="30,30,30,20" Opacity="0.6"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="30,60,30,20">
<Button Content="保存" Command="{Binding BridgePartSaveCommand}" FontSize="18" MinWidth="100" MinHeight="50" Style="{StaticResource ButtonPrimary}" IsEnabled="{Binding IsEnabled}" Margin="0,0,30,0"/>
<Button Content="清空" Command="{Binding BridgePartCancleCommand}" FontSize="18" MinWidth="100" MinHeight="50" Style="{StaticResource ButtonDefault}" IsEnabled="{Binding IsEnabled}"/>
</StackPanel>
</StackPanel>
</Border>
<!--相机设置-->
<Border BorderThickness="2" BorderBrush="Black" Margin="10" Height="Auto" Grid.Column="1">
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock Text="图片下载" FontSize="40" FontWeight="Bold" VerticalAlignment="Center" Margin="30,30,0,20"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50" Text="{Binding RemoteFilePath}"
hc:InfoElement.Placeholder="/home/nvidia/Projects/work/Camera/Configs"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="远程文件路径"
Margin="30,30,30,20"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50" Text="{Binding HostFilePath}"
hc:InfoElement.Placeholder="D:\Program Files (x86)\HuaTengVision\Camera\Configs"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="本地文件路径"
Margin="30,30,30,20"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50" Text="{Binding UserName}"
hc:InfoElement.Placeholder="nvidia"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="用户名"
Margin="30,30,30,20"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50" Text="{Binding PassWord}"
hc:InfoElement.Placeholder="nvidia"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="密码"
Margin="30,30,30,20"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="30,60,30,20">
<Button Content="下载" Command="{Binding CameraPartSaveCommand}" FontSize="18" MinWidth="100" MinHeight="50" Style="{StaticResource ButtonPrimary}" IsEnabled="{Binding IsEnabled}" Margin="0,0,30,0"/>
<!--<Button Content="释放" Command="{Binding CameraPartCancleCommand}" FontSize="18" MinWidth="100" MinHeight="50" Style="{StaticResource ButtonDefault}" IsEnabled="{Binding IsEnabled}"/>-->
<StackPanel>
<!--<Button Content="下载耗时" Margin="20" Click="Button_Click"></Button>-->
<ProgressBar Margin="50" x:Name="progressBar1" Width="300" Height="20" Minimum="0" Maximum="100" Visibility="{Binding PrograssVisibility,Converter={StaticResource BooleanToVisibility}}" Value="{Binding ProgressValue, Mode=OneWay}" />
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!--其它设置-->
<Border BorderThickness="2" BorderBrush="Black" Margin="10" Height="Auto" Grid.Column="2">
<StackPanel Orientation="Vertical" Margin="10">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="参数设置" FontSize="40" FontWeight="Bold" VerticalAlignment="Center" Margin="30,30,0,20"/>
<TextBlock Text="{Binding ShowSaveParamErrorInfo}" VerticalAlignment="Center" Foreground="Red" FontSize="18"
Margin="20,0,0,0" Visibility="{Binding ParamErrorInfoVisible,Converter={StaticResource BooleanToVisibility}}"/>
</StackPanel>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50" Text="{Binding FrontTOF}"
hc:InfoElement.Placeholder="150"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="前TOF(mm)"
Margin="30,30,30,20"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50" Text="{Binding DownFrontTOF}"
IsReadOnly="True" Opacity="0.6"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="下位机前TOF"
Margin="30,30,30,20"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50" Text="{Binding BackTOF}"
hc:InfoElement.Placeholder="150"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="后TOF(mm)"
Margin="30,30,30,20"/>
<hc:TextBox hc:InfoElement.TitleWidth="150" FontSize="20" Height="50" Text="{Binding DownBackTOF}"
IsReadOnly="True" Opacity="0.6"
hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="下位机后TOF"
Margin="30,30,30,20"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="30,60,30,20">
<Button Content="保存" Command="{Binding ParamPartSaveCommand}" FontSize="18" MinWidth="100" MinHeight="50" Style="{StaticResource ButtonPrimary}" IsEnabled="{Binding IsEnabled}" Margin="0,0,30,0"/>
<Button Content="清空" Command="{Binding ParamPartCancleCommand}" FontSize="18" MinWidth="100" MinHeight="50" Style="{StaticResource ButtonDefault}" IsEnabled="{Binding IsEnabled}"/>
</StackPanel>
</StackPanel>
</Border>
<!--<Button Content="加载相机" Name="reload" Click="CameraLoad_Click" HorizontalAlignment="Left" Margin="895,739,0,0" VerticalAlignment="Top" Width="92" Height="44"/>
<Button Content="释放相机" Name="release" Click="CameraRelease_Click" HorizontalAlignment="Left" Margin="1036,739,0,0" VerticalAlignment="Top" Width="92" Height="44"/>-->
</Grid>
</Page>
cpp
private void SaveCameraConfig(object obj)
{
// ... (略)
using (var client = new SftpClient(selectRobotIp, userName, passWord))
{
try
{
client.BufferSize = 1024;
client.Connect();
}
catch (Exception ex)
{
Console.WriteLine("连接失败: " + ex.Message);
}
try
{
var fileSize = client.GetAttributes(remoteFilePath).Size;
using (var fileStream = File.OpenWrite(localFilePath))
{
client.DownloadFile(remoteFilePath, fileStream, downloaded =>
{
// 计算并显示下载进度
double progress = (double)downloaded / fileSize * 100;
ProgressValue = (int)progress; // 这里更新了进度值
});
}
client.Disconnect();
}
catch (Exception ex)
{
// ... (略)
}
}
}
gpt建议的写法
cpp
// 重复下载会将上次覆盖
private async void SaveCameraConfig(object obj)
{
var messageResult = MessageBox.Show("请确保图片已经采集完成并压缩结束, 是否继续?",
"图片下载", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (messageResult != MessageBoxResult.Yes)
{
return;
}
SystemModel device = SystemModel.GetGlobalInstance();
int robotindex = device.SelectedRobotIndex;
string selectRobotIp = "10.60.2.202";
string temp;
if (!Directory.Exists(hostFolderPath))
{
temp = "本地文件夹不存在,请检查!";
Growl.Warning(temp, _token);
return;
}
// 显示进度条
PrograssVisibility = true;
ProgressValue = 1;
// 本地文件完整路径,包括文件名
string localFilePath = Path.Combine(hostFolderPath, Path.GetFileName(remoteFilePath));
try
{
await Task.Run(() =>
{
using (var client = new SftpClient(selectRobotIp, userName, passWord))
{
try
{
client.BufferSize = 1024;
client.Connect();
}
catch (Exception ex)
{
Console.WriteLine("连接失败: " + ex.Message);
}
try
{
var fileSize = client.GetAttributes(remoteFilePath).Size;
// 捕获开始时间
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
using (var fileStream = File.OpenWrite(localFilePath))
{
client.DownloadFile(remoteFilePath, fileStream, downloaded =>
{
// 计算并显示下载进度
double progress = (double)downloaded / fileSize * 100;
// 在UI线程上更新进度条的值,但是会导致下载卡死
Application.Current.Dispatcher.Invoke(() =>
{
ProgressValue = (int)progress;
});
Console.WriteLine($"Downloaded {downloaded} of {fileSize} bytes ({progress:F2}%)");
});
}
// 捕获结束时间
stopwatch.Stop();
TimeSpan timeTaken = stopwatch.Elapsed;
client.Disconnect();
Console.WriteLine("文件已成功下载到本地。");
Console.WriteLine($"下载时间: {timeTaken.TotalSeconds} 秒");
Application.Current.Dispatcher.Invoke(() =>
{
Growl.Success("文件下载完成", _token);
PrograssVisibility = false;
});
}
catch (Exception ex)
{
temp = "文件下载失败: " + ex.Message;
Console.WriteLine(temp);
Application.Current.Dispatcher.Invoke(() =>
{
Growl.Error(temp, _token);
PrograssVisibility = false;
});
}
}
});
} catch (Exception ex)
{
temp = "文件下载失败: " + ex.Message;
Console.WriteLine(temp);
Growl.Error(temp, _token);
}
}
原因 :
之前的写法中,虽然进度条的值在文件下载过程中被更新,但是因为文件下载操作和 UI 更新都在同一个线程中进行(即主线程),导致 UI 线程被下载操作阻塞(由于UI线程必须在主线程中运行),UI 无法及时刷新,进度条无法显示实时的更新。
具体来说,当文件下载操作进行时,它会占用主线程,这样主线程就没有时间处理其他任务,包括更新 UI。因此,虽然 ProgressValue 被更新了,但是 UI 没有机会重绘进度条。