最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C# WPF實(shí)現(xiàn)讀取文件夾中的PDF并顯示其頁(yè)數(shù)的操作指南

 更新時(shí)間:2025年09月24日 10:11:29   作者:Humbunklung  
文章介紹了利用WPF和AI工具Gemini開(kāi)發(fā)PDF整理工具的過(guò)程,采用iText處理PDF、NPOI導(dǎo)出Excel,結(jié)合MVVM模式實(shí)現(xiàn)UI與邏輯分離,并解決轉(zhuǎn)換器錯(cuò)誤問(wèn)題,最終完成文件信息展示與批量處理功能,需要的朋友可以參考下

工作中需要整理一些PDF格式文件,程序員的存在就是為了讓大家可以“更高效地工作”,而AI的出現(xiàn)就可以讓程序更“高效地工作”,于是求助于很長(zhǎng)(我指上下文)的Gemini,它幫助了我快速搭建項(xiàng)目,但也給我留下了坑(見(jiàn)本文“后記”部分),于是我把這個(gè)開(kāi)發(fā)過(guò)程記錄了下來(lái)。

技術(shù)選型

  • UI框架: WPF (.NET 6/7/8 或 .NET Framework 4.7.2+) - 用于構(gòu)建現(xiàn)代化的Windows桌面應(yīng)用。
  • PDF處理: iText (替代了舊版的 iTextSharp 及 iText7) - 一個(gè)強(qiáng)大且流行的開(kāi)源PDF處理庫(kù)。
  • Excel導(dǎo)出: NPOI - 一個(gè)開(kāi)源的.NET庫(kù),可以讀寫Office文檔,無(wú)需安裝Microsoft Office。
  • 設(shè)計(jì)模式: MVVM - 使UI和業(yè)務(wù)邏輯分離,提高代碼的可測(cè)試性和復(fù)用性。

第一步:創(chuàng)建項(xiàng)目并安裝依賴庫(kù)

打開(kāi) Visual Studio,創(chuàng)建一個(gè)新的 WPF 應(yīng)用程序 項(xiàng)目(本文為.net 8.0項(xiàng)目)。

通過(guò) NuGet 包管理器安裝以下必要的庫(kù)。在“解決方案資源管理器”中右鍵點(diǎn)擊你的項(xiàng)目,選擇“管理NuGet程序包”,然后搜索并安裝:

  • iText
  • NPOI
  • Microsoft.WindowsAPICodePack-Shell (為了一個(gè)更好看的文件夾選擇對(duì)話框)

第二步:定義數(shù)據(jù)模型 (Model)

這是我們用來(lái)存儲(chǔ)每個(gè)PDF文件信息的類。

PdfFileInfo.cs

namespace PdfFileScanner
{
    public class PdfFileInfo
    {
        public string FileName { get; set; } = string.Empty;
        public int PageCount { get; set; }
        public string FileSize { get; set; } = string.Empty;
    }
}

第三步:創(chuàng)建視圖模型 (ViewModel)

ViewModel 是連接視圖和模型的橋梁,包含了所有的業(yè)務(wù)邏輯和UI狀態(tài),在這里,我按照AI的提示創(chuàng)建了MainViewModel類。

MainViewModel.cs

using iText.Kernel.Pdf;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Microsoft.Win32;
using Microsoft.WindowsAPICodePack.Dialogs; // For modern folder browser

namespace PdfFileScanner
{
    public class MainViewModel : INotifyPropertyChanged
    {
        // INotifyPropertyChanged 實(shí)現(xiàn),用于通知UI屬性已更改
        public event PropertyChangedEventHandler? PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        // 存儲(chǔ)PDF文件信息的集合,ObservableCollection能自動(dòng)通知UI更新
        public ObservableCollection<PdfFileInfo> PdfFiles { get; } = new ObservableCollection<PdfFileInfo>();

        private string _statusText = "請(qǐng)選擇一個(gè)文件夾...";
        public string StatusText
        {
            get => _statusText;
            set { _statusText = value; OnPropertyChanged(nameof(StatusText)); }
        }

        private double _progressValue;
        public double ProgressValue
        {
            get => _progressValue;
            set { _progressValue = value; OnPropertyChanged(nameof(ProgressValue)); }
        }

        private bool _isBusy;
        public bool IsBusy
        {
            get => _isBusy;
            set
            {
                _isBusy = value;
                OnPropertyChanged(nameof(IsBusy));
                // 當(dāng)IsBusy狀態(tài)改變時(shí),通知命令重新評(píng)估其能否執(zhí)行
                ((RelayCommand)SelectFolderCommand).RaiseCanExecuteChanged();
                ((RelayCommand)ExportToExcelCommand).RaiseCanExecuteChanged();
            }
        }
        
        // 命令綁定
        public ICommand SelectFolderCommand { get; }
        public ICommand ExportToExcelCommand { get; }

        public MainViewModel()
        {
            SelectFolderCommand = new RelayCommand(async () => await ProcessFolderAsync(), () => !IsBusy);
            ExportToExcelCommand = new RelayCommand(ExportToExcel, () => PdfFiles.Count > 0 && !IsBusy);
        }

        private async Task ProcessFolderAsync()
        {
            // 使用現(xiàn)代化的文件夾選擇對(duì)話框
            var dialog = new CommonOpenFileDialog
            {
                IsFolderPicker = true,
                Title = "請(qǐng)選擇包含PDF文件的文件夾"
            };

            if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
            {
                string selectedPath = dialog.FileName;
                IsBusy = true;
                StatusText = "正在準(zhǔn)備處理...";
                PdfFiles.Clear();
                ProgressValue = 0;

                await Task.Run(() => // 在后臺(tái)線程執(zhí)行耗時(shí)操作,避免UI卡死
                {
                    var files = Directory.GetFiles(selectedPath, "*.pdf");
                    int processedCount = 0;

                    foreach (var file in files)
                    {
                        processedCount++;
                        var progressPercentage = (double)processedCount / files.Length * 100;
                        
                        // 更新UI元素必須在UI線程上執(zhí)行
                        Application.Current.Dispatcher.Invoke(() =>
                        {
                            StatusText = $"正在處理: {Path.GetFileName(file)} ({processedCount}/{files.Length})";
                            ProgressValue = progressPercentage;
                        });

                        try
                        {
                            // 獲取文件信息
                            var fileInfo = new FileInfo(file);
                            int pageCount = 0;

                            // 使用 iText7 讀取PDF頁(yè)數(shù)
                            using (var pdfReader = new PdfReader(file))
                            {
                                using (var pdfDoc = new PdfDocument(pdfReader))
                                {
                                    pageCount = pdfDoc.GetNumberOfPages();
                                }
                            }
                            
                            // 創(chuàng)建模型對(duì)象并添加到集合中
                            var pdfData = new PdfFileInfo
                            {
                                FileName = fileInfo.Name,
                                PageCount = pageCount,
                                FileSize = $"{fileInfo.Length / 1024.0:F2} KB" // 格式化文件大小
                            };

                            Application.Current.Dispatcher.Invoke(() => PdfFiles.Add(pdfData));
                        }
                        catch (System.Exception ex)
                        {
                            // 如果某個(gè)PDF文件損壞,記錄錯(cuò)誤并繼續(xù)
                            Application.Current.Dispatcher.Invoke(() =>
                            {
                                StatusText = $"處理文件 {Path.GetFileName(file)} 時(shí)出錯(cuò): {ex.Message}";
                            });
                        }
                    }
                });

                StatusText = $"處理完成!共找到 {PdfFiles.Count} 個(gè)PDF文件。";
                IsBusy = false;
            }
        }

        private void ExportToExcel()
        {
            var saveFileDialog = new SaveFileDialog
            {
                Filter = "Excel 工作簿 (*.xlsx)|*.xlsx",
                FileName = $"PDF文件列表_{System.DateTime.Now:yyyyMMddHHmmss}.xlsx"
            };

            if (saveFileDialog.ShowDialog() == true)
            {
                try
                {
                    // 使用 NPOI 創(chuàng)建 Excel
                    IWorkbook workbook = new XSSFWorkbook();
                    ISheet sheet = workbook.CreateSheet("PDF文件信息");

                    // 創(chuàng)建表頭
                    IRow headerRow = sheet.CreateRow(0);
                    headerRow.CreateCell(0).SetCellValue("文件名");
                    headerRow.CreateCell(1).SetCellValue("頁(yè)數(shù)");
                    headerRow.CreateCell(2).SetCellValue("文件大小 (KB)");

                    // 填充數(shù)據(jù)
                    for (int i = 0; i < PdfFiles.Count; i++)
                    {
                        IRow dataRow = sheet.CreateRow(i + 1);
                        dataRow.CreateCell(0).SetCellValue(PdfFiles[i].FileName);
                        dataRow.CreateCell(1).SetCellValue(PdfFiles[i].PageCount);
                        dataRow.CreateCell(2).SetCellValue(PdfFiles[i].FileSize);
                    }
                    
                    // 自動(dòng)調(diào)整列寬
                    sheet.AutoSizeColumn(0);
                    sheet.AutoSizeColumn(1);
                    sheet.AutoSizeColumn(2);

                    // 寫入文件
                    using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write))
                    {
                        workbook.Write(fs);
                    }
                    
                    MessageBox.Show("成功導(dǎo)出到Excel!", "導(dǎo)出成功", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (System.Exception ex)
                {
                    MessageBox.Show($"導(dǎo)出失敗: {ex.Message}", "錯(cuò)誤", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }
    }

    // 一個(gè)簡(jiǎn)單的ICommand實(shí)現(xiàn)
    public class RelayCommand : ICommand
    {
        private readonly System.Action _execute;
        private readonly System.Func<bool>? _canExecute;

        public event System.EventHandler? CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public RelayCommand(System.Action execute, System.Func<bool>? canExecute = null)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object? parameter) => _canExecute == null || _canExecute();
        public void Execute(object? parameter) => _execute();
        public void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested();
    }
}

第四步:設(shè)計(jì)用戶界面 (View)

這是 MainWindow.xaml 文件,定義了程序窗口的布局和控件,并將它們綁定到 ViewModel。

MainWindow.xaml

<Window x:Class="PdfFileScanner.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:PdfFileScanner"
        mc:Ignorable="d"
        Title="PDF文件掃描器" Height="600" Width="800" MinHeight="400" MinWidth="600">
    
    <!-- 設(shè)置窗口的數(shù)據(jù)上下文為ViewModel -->
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>

    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- 頂部操作欄 -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
            <Button Content="選擇文件夾" Command="{Binding SelectFolderCommand}" Padding="15,5" FontSize="14" IsEnabled="{Binding !IsBusy}"/>
            <Button Content="導(dǎo)出到Excel" Command="{Binding ExportToExcelCommand}" Margin="10,0,0,0" Padding="15,5" FontSize="14" IsEnabled="{Binding !IsBusy}"/>
        </StackPanel>

        <!-- 文件列表 -->
        <DataGrid Grid.Row="1" ItemsSource="{Binding PdfFiles}" AutoGenerateColumns="False" 
                  CanUserAddRows="False" IsReadOnly="True" FontSize="14">
            <DataGrid.Columns>
                <DataGridTextColumn Header="文件名" Binding="{Binding FileName}" Width="*"/>
                <DataGridTextColumn Header="頁(yè)數(shù)" Binding="{Binding PageCount}" Width="Auto"/>
                <DataGridTextColumn Header="文件大小" Binding="{Binding FileSize}" Width="Auto"/>
            </DataGrid.Columns>
        </DataGrid>

        <!-- 底部狀態(tài)欄和進(jìn)度條 -->
        <Grid Grid.Row="2" Margin="0,10,0,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="200"/>
            </Grid.ColumnDefinitions>

            <TextBlock Grid.Column="0" Text="{Binding StatusText}" VerticalAlignment="Center" 
                       TextTrimming="CharacterEllipsis"/>

            <ProgressBar Grid.Column="1" Value="{Binding ProgressValue}" Maximum="100" Height="20"
                         Visibility="{Binding IsBusy, Converter={StaticResource BooleanToVisibilityConverter}}"/>
        </Grid>
    </Grid>
</Window>

MainWindow.xaml.cs (代碼隱藏文件)
這里我們只需要確保 DataContext 被正確設(shè)置。上面的XAML已經(jīng)通過(guò) <local:MainViewModel/> 標(biāo)簽完成了這一步,所以代碼隱藏文件非常干凈。

using System.Windows;

namespace PdfFileScanner
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            // DataContext 在 XAML 中設(shè)置,這里無(wú)需代碼
        }
    }
}

總結(jié)與解釋

  1. 文件夾選擇: 點(diǎn)擊“選擇文件夾”按鈕,會(huì)觸發(fā) SelectFolderCommand。我們使用了 Microsoft.WindowsAPICodePack-Shell 庫(kù),它提供了一個(gè)比默認(rèn)的 FolderBrowserDialog 更現(xiàn)代、更友好的對(duì)話框。
  2. 后臺(tái)處理與進(jìn)度更新:
    • 核心的PDF文件處理邏輯被包裹在 Task.Run() 中,這會(huì)將其放到一個(gè)后臺(tái)線程上執(zhí)行,防止UI線程(負(fù)責(zé)渲染窗口和響應(yīng)用戶操作的線程)被阻塞而導(dǎo)致程序“未響應(yīng)”。
    • 在后臺(tái)線程中,我們不能直接修改UI控件(如 ProgressBarTextBlock)或綁定到UI的集合(如 ObservableCollection)。因此,我們使用 Application.Current.Dispatcher.Invoke() 將這些更新操作“派發(fā)”回UI線程執(zhí)行,這是WPF中進(jìn)行跨線程UI更新的標(biāo)準(zhǔn)做法。
    • IsBusy 屬性用來(lái)控制UI狀態(tài)。當(dāng) IsBusytrue 時(shí),按鈕會(huì)被禁用,進(jìn)度條會(huì)顯示。
  3. 信息提取:
    • 文件名和大小: 使用 System.IO.FileInfo 類可以輕松獲取。
    • PDF頁(yè)數(shù): 使用 iText 7 庫(kù)。我們通過(guò) PdfReaderPdfDocument 對(duì)象打開(kāi)PDF文件,然后調(diào)用 GetNumberOfPages() 方法。using 語(yǔ)句確保文件流被正確關(guān)閉和釋放。
  4. 列表展示:
    • WPF的 DataGrid 控件的 ItemsSource 屬性被綁定到 ViewModel 中的 ObservableCollection<PdfFileInfo> 集合。
    • ObservableCollection 的美妙之處在于,每當(dāng)你向其中 AddRemove 一個(gè)項(xiàng)時(shí),它會(huì)自動(dòng)通知綁定的 DataGrid 更新,無(wú)需手動(dòng)刷新。
  5. Excel導(dǎo)出:
    • 點(diǎn)擊“導(dǎo)出到Excel”按鈕會(huì)觸發(fā) ExportToExcelCommand。
    • 該命令首先會(huì)彈出一個(gè)標(biāo)準(zhǔn)的“文件保存”對(duì)話框,讓用戶選擇保存位置和文件名。
    • 然后,它使用 NPOI 庫(kù)在內(nèi)存中創(chuàng)建一個(gè)Excel工作簿 (XSSFWorkbook 對(duì)應(yīng) .xlsx格式),創(chuàng)建工作表、表頭行,然后遍歷 PdfFiles 集合,將每條數(shù)據(jù)寫入新的一行。
    • 最后,將內(nèi)存中的工作簿寫入到用戶選擇的文件流中。

這個(gè)方案完整地實(shí)現(xiàn)了你要求的所有功能,并且采用了現(xiàn)代C#和WPF的最佳實(shí)踐,代碼結(jié)構(gòu)清晰,易于擴(kuò)展和維護(hù)。

后記

關(guān)于轉(zhuǎn)換器的錯(cuò)誤

Visibility="{Binding IsBusy, Converter={StaticResource BooleanToVisibilityConverter}}" 改代碼因沒(méi)有轉(zhuǎn)換器Converter而出錯(cuò),故需自定義一個(gè)轉(zhuǎn)換器:

添加轉(zhuǎn)換器類BooleanToVisibilityConverter

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

public class BooleanToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool booleanValue)
        {
            if (booleanValue)
            {
                return Visibility.Visible;
            }
            else
            {
                // Default to Collapsed, or Hidden based on 'parameter' or another property
                return Visibility.Collapsed;
            }
        }
        return Visibility.Visible; // Default if not a boolean
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException(); // Usually not needed for Visibility conversion
    }
}

然后在 MainWindow.xaml 中注冊(cè)這個(gè)轉(zhuǎn)換器:

    <!-- 在這里添加資源定義 -->
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
    </Window.Resources>

修改后的MainWindow.xaml文件如下:

<Window x:Class="PdfFileScanner.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:PdfFileScanner"
        mc:Ignorable="d"
        Title="PDF文件掃描器" Height="600" Width="800" MinHeight="400" MinWidth="600">

    <!-- 設(shè)置窗口的數(shù)據(jù)上下文為ViewModel -->
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>

    <!-- 在這里添加資源定義 -->
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
    </Window.Resources>
    
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- 頂部操作欄 -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
            <Button Content="選擇文件夾" Command="{Binding SelectFolderCommand}" Padding="15,5" FontSize="14" IsEnabled="{Binding !IsBusy}"/>
            <Button Content="導(dǎo)出到Excel" Command="{Binding ExportToExcelCommand}" Margin="10,0,0,0" Padding="15,5" FontSize="14" IsEnabled="{Binding !IsBusy}"/>
        </StackPanel>

        <!-- 文件列表 -->
        <DataGrid Grid.Row="1" ItemsSource="{Binding PdfFiles}" AutoGenerateColumns="False" 
                  CanUserAddRows="False" IsReadOnly="True" FontSize="14">
            <DataGrid.Columns>
                <DataGridTextColumn Header="文件名" Binding="{Binding FileName}" Width="*"/>
                <DataGridTextColumn Header="頁(yè)數(shù)" Binding="{Binding PageCount}" Width="Auto"/>
                <DataGridTextColumn Header="文件大小" Binding="{Binding FileSize}" Width="Auto"/>
            </DataGrid.Columns>
        </DataGrid>

        <!-- 底部狀態(tài)欄和進(jìn)度條 -->
        <Grid Grid.Row="2" Margin="0,10,0,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="200"/>
            </Grid.ColumnDefinitions>

            <TextBlock Grid.Column="0" Text="{Binding StatusText}" VerticalAlignment="Center" 
                       TextTrimming="CharacterEllipsis"/>

            <ProgressBar Grid.Column="1" Value="{Binding ProgressValue}" Maximum="100" Height="20"
                         Visibility="{Binding IsBusy, Converter={StaticResource BooleanToVisibilityConverter}}"/>
        </Grid>
    </Grid>
</Window>

問(wèn)題解決!

運(yùn)行效果如下:

以上就是C# WPF實(shí)現(xiàn)讀取文件夾中的PDF并顯示其頁(yè)數(shù)的操作指南的詳細(xì)內(nèi)容,更多關(guān)于C# WPF讀取文件夾PDF并顯示其頁(yè)數(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

磐安县| 竹北市| 定西市| 治多县| 南投市| 景泰县| 鄄城县| 齐河县| 濉溪县| 临汾市| 沾益县| 昌吉市| 岑巩县| 奉节县| 云安县| 攀枝花市| 建水县| 新宁县| 温州市| 竹北市| 自治县| 宁波市| 剑川县| 锡林浩特市| 阿克陶县| 西安市| 莒南县| 吉隆县| 平阳县| 道孚县| 宣化县| 界首市| 鹿邑县| 泽库县| 偏关县| 仙居县| 龙门县| 中西区| 高邮市| 济南市| 安仁县|