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

相关推荐
九鼎科技-Leo6 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
芋芋qwq7 小时前
Unity UI射线检测 道具拖拽
ui·unity·游戏引擎
鸿蒙自习室8 小时前
鸿蒙多线程开发——线程间数据通信对象02
ui·harmonyos·鸿蒙
大霞上仙11 小时前
element ui table 每行不同状态
vue.js·ui·elementui
麻花201316 小时前
C#之WPF的C1FlexGrid空间的行加载事件和列事件变更处理动态加载的枚举值
开发语言·c#·wpf
lcintj16 小时前
【WPF】Prism学习(九)
学习·wpf·prism
界面开发小八哥16 小时前
界面控件DevExpress WPF中文教程:网格视图数据布局的列和卡片字段
wpf·界面控件·devexpress·ui开发·用户界面
△曉風殘月〆16 小时前
如何在WPF中嵌入其它程序
wpf
Crazy Struggle16 小时前
功能齐全的 WPF 自定义控件资源库(收藏版)
.net·wpf·ui控件库
栈老师不回家19 小时前
Element UI 组件库详解【Vue】
前端·vue.js·ui