WPF更新UI线程实现进度条功能

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 没有机会重绘进度条。

相关推荐
分布式存储与RustFS10 小时前
告别复杂配置:用Milvus、RustFS和Vibe Coding,60分钟DIY专属Chatbot
wpf·文件系统·milvus·对象存储·minio·rustfs·vibe
未来之窗软件服务14 小时前
UI设计(三)按实际输出内容递增的序号效果——东方仙盟筑基期
ui·thinkphp·仙盟创梦ide·东方仙盟sdk
知识分享小能手15 小时前
微信小程序入门学习教程,从入门到精通,自定义组件与第三方 UI 组件库(以 Vant Weapp 为例) (16)
前端·学习·ui·微信小程序·小程序·vue·编程
攻城狮CSU1 天前
WPF 绑定机制实现原理
wpf
攻城狮CSU1 天前
WPF 之数据绑定一(Data Binding)
wpf
wuty0071 天前
记录一下 WPF进程 SendMessage 发送窗口消息进行进程间通信,存在进程权限无法接受消息的问题
wpf·进程间通信·sendmessage·进程权限
c#上位机2 天前
wpf之ToggleButton控件
c#·wpf
浪扼飞舟2 天前
WPF用户控件和依赖属性
wpf
記億揺晃着的那天2 天前
Vue + Element UI 表格自适应高度如何做?
javascript·vue.js·ui
Larry_Yanan3 天前
QML学习笔记(三十一)QML的Flow定位器
java·前端·javascript·笔记·qt·学习·ui