windows

文書管理 C#

文書管理 C#

SqlSeverを利用した文書管理 OCR認識により「紙の情報をデジタル資産としてSQL Serverに永続化する」という、ナレッジベース構築の自動化ラインが完成します。 次は、保存したデータを「検索」して、ヒットしたものを画面上のPDFビューアで「シュッと表示する」機能を作成しました。

「OCRテキスト(NVARCHAR)」と「PDFバイナリ(VARBINARY)」の両方を検索対象にする方法です。

なぜなら、Google OCRの精度は非常に高いため、SQL Server標準のiFilter(PDF解析エンジン)に頼るよりも、Googleが読み取ったテキストを NVARCHAR(MAX) カラムに入れて検索する方が、圧倒的に速くて正確だからです。

検索用カラム (NVARCHAR): WHERE FullText LIKE '%盛岡%' などで超高速検索。

本体カラム (VARBINARY): 検索でヒットした行からバイナリを取り出し、画面に表示。


ディスクサイズと検索性能のバランス

全文インデックス(Full-Text Index): NVARCHAR(MAX) のカラムに作成します。これにより、数万件の文書から特定の単語を「ミリ秒単位」で探し出せます。

圧縮 (Clustered Columnstore Index): テキストデータが膨大になる場合、列ストア圧縮を使うと、ディスク上のテキストサイズを劇的に減らすことができます。


PDFバイナリ(VARBINARY)自体をSQL Serverに直接インデックスさせるには、サーバーに「Adobe PDF iFilter」などのインストールが必要になり、少し環境構築の難易度が上がります。

今のプログラムで**Google OCRのテキストが完璧に取れている(A3/200DPIで成功している)**のであれば、そのテキストを NVARCHAR(MAX) に放り込んで、そこにSQL Serverの全文検索をかけるのが、最も実装が楽で、かつ確実です。

1. FILESTREAM側の VARBINARY(MAX) は検索対象になるか?

はい、なります。SQL Serverの「フルテキスト検索(Full-Text Search)」機能を使用します。

仕組み: SQL Serverに「このバイナリ列はPDF形式である」と教えるために、テーブルに「ファイル拡張子を格納する列(.pdfなど)」を別途作成します。

iFilter: SQL ServerがPDFの内部を読み取るための「iFilter(アイフィルター)」というプラグイン(Adobeなどが配布)をサーバーにインストールすることで、SQL Server自身がバイナリの中からテキストを抽出してインデックスを作成します。

凄み: これにより、SELECT * FROM Documents WHERE CONTAINS(PdfData, 'デジタル終活') といったSQL文で、バイナリデータそのものを検索できるようになります。

2. PDF形式で保存するのですか?

はい、PDF形式のバイナリ(byte配列)をそのまま保存します。

保存方法: C#側で作成した透過PDF(透明テキスト付きPDF)のファイルを File.ReadAllBytes() などで読み込み、そのまま VARBINARY(MAX) 型のパラメータとしてSQL Serverへ渡します。

メリット: FILESTREAMを使っている場合、データはSQL Serverの管理下にありながら、物理的にはNTFSファイルシステム上に「ファイル」として存在するため、データベースが肥大化せず、高速な読み書きが可能です。

「二重持ち(ハイブリッド)」**構成が、検索速度と精度の面で最強です。

検索用テキスト列 (NVARCHAR(MAX)): Google OCRで取得したテキストをここに格納します。SQL Server側での解析を待つ必要がなく、最も高精度で高速に検索できます。

PDF本体列 (VARBINARY(MAX) FILESTREAM): 透過PDFのバイナリをここに格納します。表示用、およびバックアップ用として保持します。

結論として: SQL Server側でPDFの中身を直接検索させる設定も可能ですが、せっかく精度の高いGoogle OCRの結果が手元にあるので、それをテキスト専用カラムに入れて検索対象にする方が、実装も簡単で検索漏れも少なくなります。

「スキャナ → Google OCR(透過PDF生成)→ SQL Server(テキストとPDFを保存)」という流れは、まさにエンタープライズ級の文書管理システムの設計そのものです!

「スキャンした画像データ」を土台(ベース)にして、その上に「OCRで解析したテキストデータ」**を透明なレイヤーとして重ねることで作成します。

仕組みをデベロッパー的な視点で整理すると、以下のようになります。

透過PDFの構造

透過PDFは、主に2つの層(レイヤー)で構成されています。

背景レイヤー(画像): スキャナで読み取ったJPGやPNGなどの画像データです。見た目は「ただの紙のスキャン」です。

前面レイヤー(透明テキスト): Google OCRなどで解析した「どの文字が、画像上のどの座標にあるか」という情報を元に配置された、**「見えない文字」**の層です。

作成のプロセス

ステップ1: 画像から文字を読み取る(Google Vision API)。

ステップ2: 読み取った文字の「内容」と「座標(x, y)」を取得する。

ステップ3: PDF作成ライブラリ(iTextSharpなど)を使い、画像を描画したあと、その座標にピッタリ重なるように透明なフォントで文字を書き込む。


💡 なぜ「画像から作る」ことに凄みがあるのか

「画像から作成する」からこそ、以下のメリットが生まれます。

改ざん防止と信頼性: 元の紙の印影(ハンコ)や手書き署名、汚れまで含めて「画像」として残るため、証跡としての価値が保たれます。

検索性の付与: 「画像」という中身を検索できないデータに、「透明テキスト」という検索インデックスを後付けすることで、デッドデータを生きたデータに変えることができます。

SQL Serverとの親和性: 画像単体だとSQL Serverは中身を理解できませんが、このプロセスで「画像(FILESTREAM)」と「テキスト(NVARCHAR)」を切り分けられるようになるため、データベース管理が非常にスムーズになります。

フィールド名データ型推奨される長さ / 設定説明
DocIDUNIQUEIDENTIFIER(PK, ROWGUIDCOL)FILESTREAMを使う場合に必須のID。自動生成 (NEWID()) が便利。
FileNameNVARCHAR(255) ~ (500)ファイル名。日本語(Unicode)対応のため NVARCHAR を推奨。
OcrTextNVARCHAR(MAX)OCRテキスト。2GB(約10億文字)まで格納可能。
PdfFileVARBINARY(MAX) FILESTREAM透過PDF本体。DB外保存でパフォーマンスを維持。
CreatedDateDATETIME2(Default: GETDATE())登録日時。高精度な DATETIME2 が現代的。
FileHashVARCHAR(64)二重登録を防ぐ

SELECT文を利用して検索条件を入力できます。

ファイル選択で登録したいものを登録します。

drag & drop で登録できます。

OcrText項目に、できるだけ取得するようにしました。

OutLookなどのメールも選択して取り込めるようにしました。

[ HPへ送信 ]ボタンで文書内容をホームページへ投稿できます。HPでは、PHPで受け取る準備をします。

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Presentation; // PowerPoint用
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Wordprocessing;
using ImageMagick; // NuGetで追加
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http; // クラスの先頭にあることを確認
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime; // Byte配列の変換に必要
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using Windows.Graphics.Imaging;
using Windows.Media.Ocr;
using static Google.Apis.Storage.v1.Data.Bucket.LifecycleData;
using static iTextSharp.awt.geom.Point2D;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Tab;
using Body = DocumentFormat.OpenXml.Wordprocessing.Body;
using Color = System.Drawing.Color;
using Outlook = Microsoft.Office.Interop.Outlook;
using Table = DocumentFormat.OpenXml.Wordprocessing.Table;
using System.Runtime.InteropServices;

namespace FrmDocV2
{
    public partial class FrmDoc : Form
    {
        [System.Security.Permissions.UIPermission(
   System.Security.Permissions.SecurityAction.Demand,
   Window = System.Security.Permissions.UIPermissionWindow.AllWindows)]


        protected override bool ProcessDialogKey(Keys keyData)
        {
            //Returnキーが押されているか調べる
            //AltかCtrlキーが押されている時は、本来の動作をさせる
            if (((keyData & Keys.KeyCode) == Keys.Return) &&
                ((keyData & (Keys.Alt | Keys.Control)) == Keys.None))
            {
                //Tabキーを押した時と同じ動作をさせる
                //Shiftキーが押されている時は、逆順にする
                this.ProcessTabKey((keyData & Keys.Shift) != Keys.Shift);
                //本来の処理はさせない
                return true;
            }
            return base.ProcessDialogKey(keyData);


        }

        //control clsResize 画面表示を拡大縮小します。
        clsResize _form_resize;

        public FrmDoc()
        {
            InitializeComponent();

            //clsResize
            _form_resize = new clsResize(this); //I put this after the initialize event to be sure that all controls are initialized properly

            this.Load += new EventHandler(_Load); //This will be called after the initialization // form_load
            this.Resize += new EventHandler(_Resize); //form_resize
            //

            string filePath = System.IO.Directory.GetCurrentDirectory() + @"\Teisu.txt";
            if (File.Exists(filePath))
            {

                // 全行を読み込む
                string[] lines = File.ReadAllLines(filePath);
                textBox1.Text = lines[1];
                textBox2.Text = lines[2];
                textBox3.Text = lines[3];
                textBox4.Text = lines[4];

            }
            else
            {
                MessageBox.Show("設定ファイルが存在しません。処理>設定をしてください", "エラー");
            }

            //EditModeの指定
            dataGridViewEx1.EditMode = DataGridViewEditMode.EditOnEnter;

            //すべての列を内容(文字数)に合わせて自動調整(AllCells)
            //dataGridViewEx1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;

            // グリッドにメニューを直接割り当てる
            dataGridViewEx1.ContextMenuStrip = this.contextMenuStrip1;

            //文字コード用
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        }
        // Windowsの通信用のおまじない(構造体)
        [StructLayout(LayoutKind.Sequential)]
        public struct COPYDATASTRUCT
        {
            public IntPtr dwData;
            public int cbData;
            public IntPtr lpData;
        }
        protected override void WndProc(ref Message m)
        {
            

            if (m.Msg == 0x004A)
            { // WM_COPYDATAを受信したら
                COPYDATASTRUCT cds = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));
                byte[] sarr = new byte[cds.cbData];
                Marshal.Copy(cds.lpData, sarr, 0, cds.cbData);
                string str = System.Text.Encoding.Default.GetString(sarr);

                richTextBox1.Text = str; // 届いたテキストをセット!
                                     // テキストを受け取ったら、そのソフトを最前面(一番前)に持ってくる
                this.Activate();
            }
            base.WndProc(ref m);
        }

        //clsResize _Load 
        private void _Load(object sender, EventArgs e)
        {
            _form_resize._get_initial_size();

        }

        //clsResize _Resize
        private void _Resize(object sender, EventArgs e)
        {
            _form_resize._resize();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            textBox5.Text = "Select * From Documents";
        }

        //SQL実行
        private void button2_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(textBox5.Text)) return;

            try
            {
                // 1. クラスを生成
                var db = new DbManager(textBox1.Text, textBox4.Text, textBox2.Text, textBox3.Text);

                // 2. データを取得
                DataTable dt = db.GetDataTable(textBox5.Text);

                // 3. 取得した結果が空(null)でないかチェックしてからセット
                if (dt != null)
                {
                    dataGridViewEx1.DataSource = dt;
                    // 4. 「均等サイズ」の設定を解除する(ここが重要!)
                    dataGridViewEx1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;
                    if (dataGridViewEx1.Columns.Count > 0)
                    {
                        // 列名で指定するのが安全です
                        if (dataGridViewEx1.Columns.Contains("FileName"))
                            dataGridViewEx1.Columns["FileName"].Width = 120;

                        if (dataGridViewEx1.Columns.Contains("OcrText"))
                            dataGridViewEx1.Columns["OcrText"].Width = 180;

                        // 特定の列を「残り全部の幅」に広げたい場合
                        // dataGridViewEx1.Columns["OcrText"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
                    }
                    // 列が存在するか確認してから隠す(ここもエラーの元)
                    if (dataGridViewEx1.Columns.Contains("PdfFile"))
                    {
                        dataGridViewEx1.Columns["PdfFile"].Visible = false;
                    }

                    // 安全に件数を表示
                    lblCount.Text = $"{dt.Rows.Count} 件ヒット!";
                }
            }
            catch (Exception ex)
            {
                // SQLの間違いや接続エラーをここでキャッチ
                MessageBox.Show("検索中にエラーが発生しました:\n" + ex.Message, "通知");
            }

        }

        private void dataGridViewEx1_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            // 1. グリッドのc3列(バイナリデータ)を取得
            byte[] fileData = (byte[])dataGridViewEx1.CurrentRow.Cells["PdfFile"].Value;

            // 2. 一時ファイルのパスを作成(拡張子は元のファイルに合わせて可変にするのが理想)
            string fileName = dataGridViewEx1.CurrentRow.Cells["FileName"].Value.ToString(); // Scan_083944_1.pdf
            string tempPath = System.IO.Path.Combine(@"C:\sqldata\", fileName);

            // 3. 物理ファイルとして書き出し
            System.IO.File.WriteAllBytes(tempPath, fileData);

            // 4. Windowsに関連付けられたアプリで起動
            System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(tempPath)
            {
                UseShellExecute = true // これが必要
            };
            System.Diagnostics.Process.Start(psi);
        }

        private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            // ヘッダー(列名部分)クリックを無視
            if (e.RowIndex < 0) return;

            try
            {
                // 1. グリッドの背後にある DataRowView を取得
                var rowView = dataGridViewEx1.Rows[e.RowIndex].DataBoundItem as DataRowView;
                if (rowView == null) return;

                // 2. バイナリデータとファイル名を取り出す
                byte[] fileData = rowView["PdfFile"] as byte[];
                string fileName = rowView["FileName"].ToString();

                if (fileData != null)
                {
                    // 3. 一時保存パス(C:\sqldata\ を使用)
                    string tempPath = System.IO.Path.Combine(@"C:\sqldata\", fileName);

                    // 4. バイト配列を物理ファイルに書き出す
                    System.IO.File.WriteAllBytes(tempPath, fileData);

                    // 5. Windowsに関連付けられたアプリ(PDFリーダー等)を起動
                    System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(tempPath)
                    {
                        UseShellExecute = true
                    });
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("起動エラー: " + ex.Message);
            }
        }

        private void dataGridViewEx1_DragEnter(object sender, DragEventArgs e)
        {
            // 運ばれてきたのがファイル(FileDrop)なら
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                // マウスカーソルを「コピー」に変更
                e.Effect = DragDropEffects.Copy;
            }
        }

        //DragDrop
        private async void dataGridViewEx1_DragDrop(object sender, DragEventArgs e)
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

            foreach (string filePath in files)
            {
                try
                {
                    byte[] fileData = System.IO.File.ReadAllBytes(filePath);
                    string ext = System.IO.Path.GetExtension(filePath).ToLower();
                    string fileName = System.IO.Path.GetFileName(filePath);
                    // 2. 親フォルダ名を取得
                    string parentDir = Path.GetFileName(Path.GetDirectoryName(filePath));
                    string ocrText = "";

                    // 1. ハッシュ値を計算
                    string fileHash = GetFileHash(fileData);

                    // 2. 重複チェック
                    if (IsDuplicate(fileHash))
                    {
                        // すでに存在する場合はスキップ、または警告
                        //Console.WriteLine($"スキップ: {Path.GetFileName(filePath)} は既に登録されています。");
                        MessageBox.Show($"スキップ: {Path.GetFileName(filePath)} は既に登録されています。");
                        continue;
                    }

                    // --- 拡張子に応じた処理の分岐 ---
                    if (ext == ".pdf")
                    {
                        // PDF専用の処理(画像変換を挟む)
                        ocrText = await GetOcrTextFromFile(filePath);
                        ocrText = $"【フォルダ】{parentDir} " + ocrText;
                    }
                    else if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp")
                    {
                        // 画像専用の処理(そのままOCR)
                        ocrText = await RecognizeTextAsync(fileData);
                        ocrText = $"【フォルダ】{parentDir} " + ocrText;
                    }
                    else if (ext == ".docx")
                    {
                        // ワード専用の処理(docx抽出)
                        ocrText = docxToTxt(filePath);
                        ocrText = $"【フォルダ】{parentDir} " + ocrText;
                    }
                    else if (ext == ".xlsx")
                    {
                        // エクセル専用の処理(xlsx抽出)
                        ocrText = AllSheetsToText(filePath);
                        ocrText = $"【フォルダ】{parentDir} " + ocrText;
                    }
                    else if (ext == ".txt")
                    {
                        ocrText = LoadFile(filePath);
                        ocrText = $"【フォルダ】{parentDir} " + ocrText;
                    }
                    else if (ext == ".csv")
                    {
                        // Csv専用の処理(csv抽出)
                        ocrText = CsvToText(filePath);
                        ocrText = $"【フォルダ】{parentDir} " + ocrText;
                    }
                    else if (ext == ".pptx")
                    {
                        // パワーポイント専用の処理(ppt抽出)
                        ocrText = PpptxToText(filePath);
                        ocrText = $"【フォルダ】{parentDir} " + ocrText;
                    }
                    else if (ext == ".html" || ext == ".htm")
                    {
                        // web専用の処理(html抽出)
                        ocrText = HtmlToText(filePath);
                        ocrText = $"【フォルダ】{parentDir} " + ocrText;
                    }
                    else if (ext == ".rtf")
                    {
                        // リッチテキスト用の処理(Rtf抽出)
                        ocrText = RtfToText(filePath);
                        ocrText = $"【フォルダ】{parentDir} " + ocrText;
                    }
                    else
                    {
                        // OCR対象外(Wave, Mp3.........など)
                        ocrText = $"【フォルダ】{parentDir} 【ファイル名】{fileName}";
                    }

                    // DBに保存
                    SaveFileToDb(fileName, fileData, ocrText);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"ファイル「{System.IO.Path.GetFileName(filePath)}」の処理中にエラーが発生しました。\n{ex.Message}");
                }
            }
            // RefreshGrid(); // 全件終わったらリフレッシュ
            // グリッドを更新して最新の状態に
            button2_Click(null, null);
        }

        //txt取得
        private string LoadFile(string filePath)
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // GetEncoding(932) を使用するためのおまじない
            string utf8 = File.ReadAllText(filePath); // utf8 で読み込んだ文字列
            string shift = File.ReadAllText(filePath, System.Text.Encoding.GetEncoding(932)); //shift-jis で読み込んだ文字列

            string utf8Replace = utf8.Replace(" ", ""); //代用文字を取り除いた文字列(ここは代用文字を調べて置き換えて下さい)
            string shiftReplace = shift.Replace(" ", ""); //代用文字を取り除いた文字列(ここは代用文字を調べて置き換えて下さい)

            int utf8Length = System.Text.Encoding.UTF8.GetByteCount(utf8);
            utf8Length += 12 * (utf8.Length - utf8Replace.Length); //shiftLength と同じ内容のペナルティ

            int shiftLength = System.Text.Encoding.UTF8.GetByteCount(shift);
            shiftLength += 12 * (shift.Length - shiftReplace.Length); //代用文字に対し 12 byte のペナルティ

            if (utf8Length < shiftLength)
            {
                return utf8;
            }
            else
            {
                return shift;
            }
        }

        private string GetConnectionString()
        {
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
            builder.DataSource = textBox1.Text;   //
            builder.InitialCatalog = textBox4.Text;  // 
            builder.UserID = textBox2.Text;         // 
            builder.Password = textBox3.Text;     // 
            // タイムアウト設定なども共有できる
            builder.ConnectTimeout = 30;

            return builder.ConnectionString;
        }
        //Documents追加
        private void SaveFileToDb(string fileName, byte[] data, string ocrText)
        {
            /*
            string _connectionString = "Data Source=" + textBox1.Text
                                       + ";Initial Catalog=" + textBox4.Text
                                       + ";User ID=" + textBox2.Text
                                       + ";Password=" + textBox3.Text;
            */
            using (SqlConnection conn = new SqlConnection(GetConnectionString()))
            {
                conn.Open();
                // c0(DocID)はNEWID()、c4(CreatedDate)はGETDATE()がDB側で自動セットされる想定
                string sql = "INSERT INTO Documents (FileName, OcrText, PdfFile, FileHash) VALUES (@name, @ocr, @bin, @hash)";

                using (SqlCommand cmd = new SqlCommand(sql, conn))
                {
                    cmd.Parameters.Add("@name", SqlDbType.NVarChar).Value = fileName;
                    cmd.Parameters.Add("@ocr", SqlDbType.NVarChar).Value = ocrText;
                    cmd.Parameters.Add("@bin", SqlDbType.VarBinary, -1).Value = data; // -1はMAX
                    cmd.Parameters.AddWithValue("@hash", GetFileHash(data)); // ここを追加!
                    cmd.ExecuteNonQuery();
                }
            }
        }

        //検索ワード処理
        private void btnSearch_Click(object sender, EventArgs e)
        {
            DataTable dt = dataGridViewEx1.DataSource as DataTable;
            if (dt == null) return;

            List<string> filters = new List<string>();

            // 1. キーワード検索 (AND検索対応)
            if (!string.IsNullOrWhiteSpace(txtKeyword.Text))
            {
                string kw = txtKeyword.Text.Replace("'", "''");
                filters.Add(string.Format("(FileName LIKE '%{0}%' OR OcrText LIKE '%{0}%')", kw));
            }

            // 2. 日付範囲検索
            // 00:00:00 から 23:59:59 までをカバーするように設定
            filters.Add(string.Format("CreatedDate >= #{0:yyyy-MM-dd} 00:00:00#", dtpStart.Value));
            filters.Add(string.Format("CreatedDate <= #{0:yyyy-MM-dd} 23:59:59#", dtpEnd.Value));

            // 3. 画像のみ絞り込み
            if (chkOnlyImages.Checked)
            {
                filters.Add("(FileName LIKE '%.jpg%' OR FileName LIKE '%.png%' OR FileName LIKE '%.jpeg%')");
            }

            // すべてを AND で結合して適用!
            dt.DefaultView.RowFilter = string.Join(" AND ", filters);

            // 【ここを追加!】絞り込まれた後の件数を表示する
            //lblCount.Text = dt.DefaultView.Count.ToString() + " 件見つかりました。";

            // 検索処理の最後に
            int hitCount = dt.DefaultView.Count;
            lblCount.Text = hitCount > 0 ? $"{hitCount} 件見つかりました。" : "該当なし";
            lblCount.ForeColor = hitCount > 0 ? Color.Blue : Color.Red;
        }

        // OCRを実行する関数
        public async Task<string> RecognizeTextAsync(byte[] imageBytes)
        {
            try
            {
                // 1. バイト配列をWindowsランタイムのストリームに変換
                using (var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
                {
                    await stream.WriteAsync(imageBytes.AsBuffer());
                    stream.Seek(0);

                    // 2. 画像デコーダーを作成
                    var decoder = await BitmapDecoder.CreateAsync(stream);
                    using (var softwareBitmap = await decoder.GetSoftwareBitmapAsync())
                    {
                        // 3. OCRエンジンを日本語で作成
                        var engine = OcrEngine.TryCreateFromLanguage(new Windows.Globalization.Language("ja-JP"));
                        if (engine == null) return "OCRエンジンが起動できませんでした。";

                        // 4. 文字認識を実行
                        var result = await engine.RecognizeAsync(softwareBitmap);

                        // 5. 認識した行を結合して返す
                        return result.Text;
                    }
                }
            }
            catch (Exception ex)
            {
                return "OCRエラー: " + ex.Message;
            }
        }

        // OCRを実行する前準備PDF対応関数
        public async Task<string> GetOcrTextFromFile(string filePath)
        {
            string ext = Path.GetExtension(filePath).ToLower();
            StringBuilder sb = new StringBuilder();

            if (ext == ".pdf")
            {
                // --- PDFを画像に変換してOCR ---
                var settings = new MagickReadSettings();
                settings.Density = new Density(300); // OCRの精度を上げるため300DPIで読み込み

                using (var images = new MagickImageCollection())
                {
                    images.Read(filePath, settings); // PDF全ページを読み込み

                    foreach (var page in images)
                    {
                        // ページをPNG形式のバイト配列に変換
                        byte[] pageBytes = page.ToByteArray(MagickFormat.Png);

                        // 以前作成したOCRメソッド(RecognizeTextAsync)を呼び出し
                        string pageText = await RecognizeTextAsync(pageBytes);
                        sb.AppendLine(pageText);
                    }
                }
            }
            else if (ext == ".jpg" || ext == ".png" || ext == ".jpeg")
            {
                // --- 通常の画像OCR ---
                byte[] fileData = File.ReadAllBytes(filePath);
                sb.Append(await RecognizeTextAsync(fileData));
            }

            return sb.ToString();
        }

        //WordToText
        private string docxToTxt(string FilePath)
        {

            StringBuilder sb = new StringBuilder();

            //(1)
            //出力用リスト
            List<string> txt_out_list = new List<string>();

            //(2)
            //ワード文書を開く
            using (WordprocessingDocument word_doc = WordprocessingDocument.Open(FilePath, false))
            {
                //(3)
                //body要素を取得
                Body elem_body = word_doc.MainDocumentPart.Document.Body;

                //(4)
                //body要素の子要素でループ
                foreach (OpenXmlElement elem_child in elem_body.ChildElements)
                {
                    // 段落の場合
                    if (elem_child is Paragraph elem_p)
                    {
                        sb.AppendLine(elem_p.InnerText);
                    }
                    // ★表(Table)の場合を追加
                    else if (elem_child is Table elem_table)
                    {
                        sb.AppendLine(elem_table.InnerText);
                    }
                }
            }

            //(6)
            //出力用リストを出力
            return sb.ToString();

        }

        //Excel To Text
        public string ExcelToText(string excelPath)
        {
            StringBuilder sb = new StringBuilder();

            using (SpreadsheetDocument doc = SpreadsheetDocument.Open(excelPath, false))
            {
                var workbookPart = doc.WorkbookPart;
                var sheet = workbookPart.Workbook.Descendants<Sheet>().FirstOrDefault();
                var wsPart = (WorksheetPart)(workbookPart.GetPartById(sheet.Id));
                var sheetData = wsPart.Worksheet.Elements<SheetData>().First();
                foreach (Row row in sheetData.Elements<Row>())
                {
                    var cellValues = row.Elements<Cell>().Select(c => GetCellValue(c, workbookPart));
                    sb.AppendLine(string.Join(",  ", cellValues));
                }

            }
            return sb.ToString();
        }
        public string AllSheetsToText(string excelFilePath)
        {
            StringBuilder sb = new StringBuilder();

            using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(excelFilePath, false))
            {
                WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;
                SharedStringTablePart sstPart = workbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
                SharedStringTable sst = sstPart?.SharedStringTable;

                // 全てのワークシートを取得
                var sheets = workbookPart.Workbook.Descendants<Sheet>();

                foreach (Sheet sheet in sheets)
                {
                    // シート名を取得
                    string sheetName = sheet.Name;
                    WorksheetPart worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id);
                    Worksheet worksheet = worksheetPart.Worksheet;
                    SheetData sheetData = worksheet.GetFirstChild<SheetData>();


                    foreach (Row row in sheetData.Elements<Row>())
                    {
                        var cellValues = row.Elements<Cell>().Select(c => GetCellValue(c, workbookPart));
                        sb.AppendLine(string.Join(",  ", cellValues));

                    }
                }
            }
            return sb.ToString();
        }


        // セル値取得の補助メソッド
        private string GetCellValue(Cell cell, WorkbookPart wbPart)
        {
            string value = cell.InnerText;
            if (cell.DataType != null && cell.DataType == CellValues.SharedString)
            {
                return wbPart.SharedStringTablePart.SharedStringTable.ChildElements[int.Parse(value)].InnerText;
            }
            return value;
        }

        //powerpoint to text
        public string PpptxToText(string pptxPath)
        {
            StringBuilder sb = new StringBuilder();

            using (PresentationDocument ppt = PresentationDocument.Open(pptxPath, false))
            {
                // 1. プレゼンテーションの本体(全スライドの構成情報)を取得
                var presentationPart = ppt.PresentationPart;
                if (presentationPart == null) return "";

                // 2. 各スライドを順番にループ
                foreach (var slideId in presentationPart.Presentation.SlideIdList.Elements<SlideId>())
                {
                    SlidePart slidePart = (SlidePart)presentationPart.GetPartById(slideId.RelationshipId);

                    // 3. スライド内の全テキスト要素を取得
                    // (スライド上の図形やテキストボックスの中身をすべて拾う)
                    foreach (var text in slidePart.Slide.Descendants<DocumentFormat.OpenXml.Drawing.Text>())
                    {
                        sb.Append(text.Text + " ");
                    }
                    sb.AppendLine(); // スライドごとに改行
                }
            }

            return sb.ToString();
        }

        // CSVの読み込み例
        public string CsvToText(string filePath)
        {
            // 全行読み込んで、カンマをスペースに置換するか、そのまま連結
            string[] lines = System.IO.File.ReadAllLines(filePath, Encoding.GetEncoding("shift_jis"));
            return string.Join(" , ", lines);
        }

        //html to text
        public string HtmlToText(string htmlPath)
        {
            string html = File.ReadAllText(htmlPath, Encoding.UTF8);

            // 1. スクリプトとスタイルシートの中身を削除(文字として不要なため)
            html = Regex.Replace(html, @"<(script|style)[^>]*?>.*?</\1>", "", RegexOptions.IgnoreCase | RegexOptions.Singleline);

            // 2. タグ(<...>)をすべて削除
            string text = Regex.Replace(html, @"<[^>]*>", " ");

            // 3. HTMLエンティティ(&nbsp; や &amp; など)を普通の文字にデコード
            text = WebUtility.HtmlDecode(text);

            // 4. 余計な改行や空白を整理
            text = Regex.Replace(text, @"\s+", " ").Trim();

            return text;
        }


        // リッチテキストの読み込み例
        public string RtfToText(string filePath)
        {
            // 1. RichTextBoxのインスタンスを作成
            RichTextBox rtBox = new RichTextBox();

            // 2. RTFファイルを読み込む
            rtBox.LoadFile(filePath, RichTextBoxStreamType.RichText);

            // 3. テキスト形式(プレーンテキスト)として取得
            string plainText = rtBox.Text;

            return string.Join(" , ", plainText);
        }


        // ファイルの「指紋」を作成するメソッド
        public string GetFileHash(byte[] fileData)
        {
            using (SHA256 sha256 = SHA256.Create())
            {
                byte[] hashBytes = sha256.ComputeHash(fileData);
                // バイト配列を16進数の文字列に変換
                return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
            }
        }

        // 重複があるか確認する関数
        private bool IsDuplicate(string hash)
        {
            /*
            string _connectionString = "Data Source=" + textBox1.Text
                                       + ";Initial Catalog=" + textBox4.Text
                                       + ";User ID=" + textBox2.Text
                                       + ";Password=" + textBox3.Text;
            */
            using (SqlConnection conn = new SqlConnection(GetConnectionString()))
            {
                conn.Open();
                string sql = "SELECT COUNT(*) FROM Documents WHERE FileHash = @hash";
                using (SqlCommand cmd = new SqlCommand(sql, conn))
                {
                    cmd.Parameters.AddWithValue("@hash", hash);
                    int count = (int)cmd.ExecuteScalar();
                    return count > 0;
                }
            }
        }



        //******************************************************
        // 全件取得してハッシュを計算し、UPDATEする処理
        //******************************************************
        public void UpdateExistingHashes()
        {
            /*
            string _connectionString = "Data Source=" + textBox1.Text
                                      + ";Initial Catalog=" + textBox4.Text
                                      + ";User ID=" + textBox2.Text
                                      + ";Password=" + textBox3.Text;
            */
            using (SqlConnection conn = new SqlConnection(GetConnectionString()))
            {
                conn.Open();
                // PdfFile(バイナリ)があって FileHash が空のものを取得
                string selectSql = "SELECT DocID, PdfFile FROM Documents WHERE FileHash IS NULL";

                using (SqlCommand cmd = new SqlCommand(selectSql, conn))
                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    List<(Guid id, string hash)> updateList = new List<(Guid, string)>();
                    while (reader.Read())
                    {
                        byte[] data = (byte[])reader["PdfFile"];
                        updateList.Add(((Guid)reader["DocID"], GetFileHash(data)));
                    }
                    reader.Close();

                    // まとめてUPDATE
                    foreach (var item in updateList)
                    {
                        string updateSql = "UPDATE Documents SET FileHash = @hash WHERE DocID = @id";
                        using (SqlCommand upCmd = new SqlCommand(updateSql, conn))
                        {
                            upCmd.Parameters.AddWithValue("@hash", item.hash);
                            upCmd.Parameters.AddWithValue("@id", item.id);
                            upCmd.ExecuteNonQuery();
                        }
                    }
                }
            }
        }

        private void button6_Click(object sender, EventArgs e)
        {
            UpdateExistingHashes();
            MessageBox.Show("ハッシュ更新終了");
        }


        //キーワード
        private void dataGridViewEx1_SelectionChanged(object sender, EventArgs e)
        {
            if (dataGridViewEx1.CurrentRow == null) return;

            // 1. グリッドからテキストを取得
            string content = dataGridViewEx1.CurrentRow.Cells["OcrText"].Value.ToString();
            string keyword = txtKeyword.Text.Trim(); // 検索窓の文字

            richTextBox1.Text = content; // 一旦全部表示

            // 2. 検索ワードがあれば色をつける
            if (!string.IsNullOrEmpty(keyword))
            {
                int index = 0;
                while ((index = richTextBox1.Find(keyword, index, RichTextBoxFinds.None)) != -1)
                {
                    richTextBox1.SelectionBackColor = Color.Yellow; // 背景を黄色に
                    richTextBox1.SelectionColor = Color.Red;       // 文字を赤に
                    index += keyword.Length;
                }
            }

            if (!string.IsNullOrEmpty(keyword))
            {
                int index = richTextBox1.Find(keyword);
                if (index != -1)
                {
                    // ヒット箇所を選択状態にして、そこまでスクロール
                    richTextBox1.SelectionStart = index;
                    richTextBox1.ScrollToCaret();
                }
            }

        }

        //復元処理

        private void dataGridViewEx1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex < 0) return;

            // 1. DBからバイナリとファイル名を取得
            byte[] fileData = (byte[])dataGridViewEx1.CurrentRow.Cells["PdfFile"].Value;
            string fileName = dataGridViewEx1.CurrentRow.Cells["FileName"].Value.ToString();

            // 2. 保存先パス(例:実行フォルダ内の "temp" フォルダ)
            string tempDir = Path.Combine(Application.StartupPath, "TempExport");
            if (!Directory.Exists(tempDir)) Directory.CreateDirectory(tempDir);

            string exportPath = Path.Combine(tempDir, fileName);

            // 3. ファイルを書き出す
            File.WriteAllBytes(exportPath, fileData);

            // 4. そのファイルを既定のアプリで開く(ExcelならExcel、PDFならブラウザ等)
            System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
            {
                FileName = exportPath,
                UseShellExecute = true
            });
        }


        // 「原本を別名で保存」メニューを選択した時
        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // 現在選択されている行があるかチェック
            if (dataGridViewEx1.CurrentRow == null) return;

            // 1. グリッドからデータを取得
            byte[] fileData = (byte[])dataGridViewEx1.CurrentRow.Cells["PdfFile"].Value;
            string fileName = dataGridViewEx1.CurrentRow.Cells["FileName"].Value.ToString();

            // 2. 保存ダイアログを表示
            using (SaveFileDialog sfd = new SaveFileDialog())
            {
                sfd.FileName = fileName; // 初期ファイル名
                sfd.Filter = "すべてのファイル (*.*)|*.*";

                if (sfd.ShowDialog() == DialogResult.OK)
                {
                    // 3. 指定された場所に書き出し
                    File.WriteAllBytes(sfd.FileName, fileData);
                    MessageBox.Show("保存しました。");
                }
            }
        }

        //右メニューのOPEN
        private void contextMenuStrip1_Opening_1(object sender, CancelEventArgs e)
        {
            // マウス位置の座標を取得
            Point p = dataGridViewEx1.PointToClient(Cursor.Position);
            DataGridView.HitTestInfo hit = dataGridViewEx1.HitTest(p.X, p.Y);

            if (hit.Type == DataGridViewHitTestType.Cell)
            {
                // 右クリックした行を選択状態にする
                dataGridViewEx1.CurrentCell = dataGridViewEx1[hit.ColumnIndex, hit.RowIndex];
            }
            else
            {
                // セル以外の場所(ヘッダー等)ならメニューを出さない
                e.Cancel = true;
            }
        }

        //別名保存
        private void 原本を別名保存ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // 現在選択されている行があるかチェック
            if (dataGridViewEx1.CurrentRow == null) return;

            // 1. グリッドからデータを取得
            byte[] fileData = (byte[])dataGridViewEx1.CurrentRow.Cells["PdfFile"].Value;
            string fileName = dataGridViewEx1.CurrentRow.Cells["FileName"].Value.ToString();

            // 2. 保存ダイアログを表示
            using (SaveFileDialog sfd = new SaveFileDialog())
            {
                sfd.FileName = fileName; // 初期ファイル名
                sfd.Filter = "すべてのファイル (*.*)|*.*";

                if (sfd.ShowDialog() == DialogResult.OK)
                {
                    // 3. 指定された場所に書き出し
                    File.WriteAllBytes(sfd.FileName, fileData);
                    MessageBox.Show("保存しました。");
                }
            }
        }

        private void データを削除ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (dataGridViewEx1.CurrentRow == null) return;

            // ユーザーに確認をとる
            var res = MessageBox.Show("このレコードをデータベースから完全に削除しますか?",
                                      "削除確認", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);

            if (res == DialogResult.Yes)
            {
                /*
                // 1. DocIDを取得
                string docId = dataGridViewEx1.CurrentRow.Cells["DocID"].Value.ToString();

                // 2. SQL実行
                /*
                string _connectionString = "Data Source=" + textBox1.Text
                                      + ";Initial Catalog=" + textBox4.Text
                                      + ";User ID=" + textBox2.Text
                                      + ";Password=" + textBox3.Text;
                
                using (SqlConnection conn = new SqlConnection(GetConnectionString())) // 保存時と同じ接続文字列
                {
                    conn.Open();
                    string sql = "DELETE FROM Documents WHERE DocID = @id";
                    using (SqlCommand cmd = new SqlCommand(sql, conn))
                    {
                        cmd.Parameters.AddWithValue("@id", docId);
                        cmd.ExecuteNonQuery();
                    }
                }
                */

                var db = new DbManager(textBox1.Text, textBox4.Text, textBox2.Text, textBox3.Text);
                string id = dataGridViewEx1.CurrentRow.Cells["DocID"].Value.ToString();

                // SQLとパラメータを渡すだけ
                db.Execute("DELETE FROM Documents WHERE DocID = @id", new Dictionary<string, object> { { "@id", id } });

                // グリッドからその行を消す(または再検索して表示更新)
                dataGridViewEx1.Rows.Remove(dataGridViewEx1.CurrentRow);
                MessageBox.Show("削除しました。");

                // 削除後に再検索してリストを更新
                //button2_Click(null, null);
            }

        }

        //本文のコピー
        private void 本文のコピーToolStripMenuItem_Click(object sender, EventArgs e)
        {
            /*
            if (dataGridViewEx1.CurrentRow == null) return;

            // 名前で指定してOCRテキストを取得
            string ocrText = dataGridViewEx1.CurrentRow.Cells["OcrText"].Value?.ToString() ?? "";

            if (!string.IsNullOrEmpty(ocrText))
            {
                Clipboard.SetText(ocrText);
                // ユーザーに知らせる(任意)
                // toolStripStatusLabel1.Text = "クリップボードにコピーしました。";
            }
            */

            if (dataGridViewEx1.CurrentRow == null) return;

            // 非表示にしている列からでも、名前で指定すれば確実に取れます
            string ocrText = dataGridViewEx1.CurrentRow.Cells["OcrText"].Value?.ToString();

            if (!string.IsNullOrEmpty(ocrText))
            {
                Clipboard.SetText(ocrText);
                // ラベルを一時的に書き換えて通知するのも「プロらしい」演出です
                lblCount.Text = "クリップボードにコピーしました!";
                lblCount.ForeColor = Color.DarkGreen;
            }

        }

        private void btnOutlookImport_Click_Click(object sender, EventArgs e)
        {

            Outlook.Application outlookApp = null;
            Outlook.Selection selection = null;

            try
            {
                outlookApp = new Outlook.Application();
                selection = outlookApp.ActiveExplorer().Selection;

                if (selection == null || selection.Count == 0)
                {
                    MessageBox.Show("メールを選択してください。");
                    return;
                }

                // 💡 m2.jpg の運用:確認ダイアログを表示
                if (MessageBox.Show($"{selection.Count} 件のメールを取り込みますか?", "確認", MessageBoxButtons.YesNo) != DialogResult.Yes) return;

                var db = new DbManager(textBox1.Text, textBox4.Text, textBox2.Text, textBox3.Text);
                int importCount = 0;

                foreach (object item in selection)
                {
                    if (item is Outlook.MailItem mail)
                    {
                        // 1. 本文からハッシュ作成
                        string hashCode = GenerateHash(mail.Body);

                        // 2. 重複チェック付きSQL
                        // ※ FileHash列はDB側に追加済みと想定します
                        string sql = @"
                    IF NOT EXISTS (SELECT 1 FROM Documents WHERE FileHash = @hash)
                    BEGIN
                        INSERT INTO Documents (FileName, OcrText, FileHash) 
                        VALUES (@name, @text, @hash)
                    END";

                        var parameters = new Dictionary<string, object>
                {
                    { "@name", $"[Mail] {mail.Subject}" },
                    { "@text", mail.Body },
                    { "@hash", hashCode }
                };

                        db.Execute(sql, parameters);
                        importCount++;
                        Marshal.ReleaseComObject(mail);
                    }
                }

                // グリッドを更新して最新の状態に
                button2_Click(null, null);
            }
            catch (Exception ex)
            {
                MessageBox.Show("取り込みエラー: " + ex.Message);
            }
            finally
            {
                if (selection != null) Marshal.ReleaseComObject(selection);
                if (outlookApp != null) Marshal.ReleaseComObject(outlookApp);
            }
        }

        // 💡 ハッシュ値を計算する補助関数
        private string GenerateHash(string input)
        {
            using (SHA256 sha256Hash = SHA256.Create())
            {
                byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
                StringBuilder builder = new StringBuilder();
                for (int i = 0; i < bytes.Length; i++)
                {
                    builder.Append(bytes[i].ToString("x2"));
                }
                return builder.ToString();
            }
        }

        private async void btnSendToWP_Click_Click(object sender, EventArgs e)
        {
            // 1. 送信する内容を準備
            // ※実際には OCR の結果が入っている TextBox などを指定してください
            string testTitle = "C#FrmDocアプリからのテスト投稿 " + DateTime.Now.ToString("HH:mm:ss");
            string testContent = richTextBox1.Text;//"これは C# アプリから WordPress API を経由して送信されたテストメッセージです。\nハッシュ値の連携も今後行う予定です。";

            // 2. 通信用の部品(HttpClient)を作成
            using (var client = new HttpClient())
            {
                // 3. 送信するデータをセット(PHP 側で決めた "key" を一致させる)
                var parameters = new Dictionary<string, string>
        {
            { "key", "Passwordxxxxx" },
            { "title", testTitle },
            { "content", testContent }
        };

                var encodedContent = new FormUrlEncodedContent(parameters);

                try
                {
                    // 4. 指定した URL へ送信(ご自身のテストサーバーの URL)
                    var response = await client.PostAsync("https://ssksvr.com/wp-post-api.php", encodedContent);

                    // 5. サーバーからの返事を受け取る
                    var result = await response.Content.ReadAsStringAsync();

                    if (result.Contains("Success"))
                    {
                        MessageBox.Show("成功しました!WordPress の『投稿一覧』を確認してください。\n" + result);
                    }
                    else
                    {
                        MessageBox.Show("サーバー側でエラーが発生しました:\n" + result);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("通信エラーが発生しました:\n" + ex.Message);
                }
            }
        }

        


    }

}
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Windows.Forms;

public class DbManager
{
    public string ConnectionString { get; private set; }
    private string _dbName;

    // ログファイルの保存先(実行ファイルと同じフォルダ)
    private readonly string _logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "system_log.txt");

    // バックアップの保存先フォルダ
    private readonly string _backupFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Backups");

    public DbManager(string instance, string db, string user, string pass)
    {
        SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
        builder.DataSource = instance;
        builder.InitialCatalog = db;
        builder.UserID = user;
        builder.Password = pass;
        builder.ConnectTimeout = 30;

        this.ConnectionString = builder.ConnectionString;
        this._dbName = db;

        // 1. 起動時にログを記録
        WriteLog("アプリケーションからデータベースに接続しました。");

        // 2. 起動時に自動バックアップを実行
        RunAutoBackup();
    }

    // --- ログ機能 ---
    public void WriteLog(string message)
    {
        try
        {
            string logLine = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss} - {message}{Environment.NewLine}";
            File.AppendAllText(_logFilePath, logLine);
        }
        catch { /* ログ書き込みエラーでアプリを止めない */ }
    }

    // --- 自動バックアップ機能 ---
    private void RunAutoBackup()
    {
        try
        {
            // バックアップフォルダがなければ作成
            if (!Directory.Exists(_backupFolder)) Directory.CreateDirectory(_backupFolder);

            // 今日の日付のバックアップファイル名
            string backupFileName = $"{_dbName}_{DateTime.Now:yyyyMMdd}.bak";
            string fullPath = Path.Combine(_backupFolder, backupFileName);

            // すでに今日作成済みならスキップ
            if (File.Exists(fullPath)) return;

            using (SqlConnection conn = new SqlConnection(this.ConnectionString))
            {
                conn.Open();
                // SQL ServerのBACKUP命令を実行
                string sql = $@"BACKUP DATABASE [{_dbName}] TO DISK = '{fullPath}' WITH FORMAT, MEDIANAME = 'AutoBackup', NAME = 'Full Backup of {_dbName}';";
                using (SqlCommand cmd = new SqlCommand(sql, conn))
                {
                    cmd.ExecuteNonQuery();
                }
            }
            WriteLog($"自動バックアップ成功: {backupFileName}");
        }
        catch (Exception ex)
        {
            WriteLog($"自動バックアップ失敗: {ex.Message}");
        }
    }

    // --- 参照系(SELECT) ---
    public DataTable GetDataTable(string sql)
    {
        WriteLog($"SQL実行(参照): {sql}");
        DataTable dt = new DataTable();
        try
        {
            using (SqlConnection conn = new SqlConnection(this.ConnectionString))
            {
                SqlDataAdapter adapter = new SqlDataAdapter(sql, conn);
                adapter.Fill(dt);
            }
            return dt;
        }
        catch (Exception ex)
        {
            WriteLog($"エラー(参照): {ex.Message}");
            throw;
        }
    }

    // --- 更新系(INSERT/DELETE/UPDATE)+ トランザクション ---
    public void Execute(string sql, Dictionary<string, object> parameters)
    {
        WriteLog($"SQL実行(更新): {sql}");
        using (SqlConnection conn = new SqlConnection(this.ConnectionString))
        {
            conn.Open();
            SqlTransaction transaction = conn.BeginTransaction();
            try
            {
                using (SqlCommand cmd = new SqlCommand(sql, conn, transaction))
                {
                    if (parameters != null)
                    {
                        foreach (var p in parameters) cmd.Parameters.AddWithValue(p.Key, p.Value);
                    }
                    cmd.ExecuteNonQuery();
                }
                transaction.Commit();
                WriteLog("トランザクション完了: コミットされました。");
            }
            catch (Exception ex)
            {
                transaction.Rollback();
                WriteLog($"エラー(更新) ロールバック実行: {ex.Message}");
                throw;
            }
        }
    }
}
//コードを隠すコードを選択
using System;
using System.Windows.Forms;

/// <summary>
/// Enterキーが押された時に、Tabキーが押されたのと同じ動作をする
/// (現在のセルを隣のセルに移動する)DataGridView
/// </summary>
public class DataGridViewEx : DataGridView
{
    private DataGridView dataGridView1;

    protected override bool ProcessDialogKey(Keys keyData)
    {
        // セルの編集モード時にEnterが押されると次行に移ってしまうので、右隣のセルに移動させる
        if ((keyData & Keys.KeyCode) == Keys.Enter)
        {
            // Tabキーの処理を行う
            return this.ProcessTabKey(keyData);
        }
        // 既定の処理を行う
        return base.ProcessDialogKey(keyData);
    }
    /// DataGridView での移動に使用されるキーを処理します。
    protected override bool ProcessDataGridViewKey(KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Enter && e.Handled == false)
        {
            // イベントを処理済にする
            e.Handled = true;

            if (this.CurrentCell != null)
            {
                // 右下セルのときは次のコントロールにフォーカス移動
                if (this.CurrentCell.RowIndex == this.Rows.GetLastRow(DataGridViewElementStates.Visible) &&
                    this.CurrentCell.ColumnIndex == this.Columns.GetLastColumn(DataGridViewElementStates.Visible, DataGridViewElementStates.None).Index &&
                    e.Modifiers != Keys.Shift)
                {
                    return this.FindForm().SelectNextControl(this.FindForm().ActiveControl, true, true, true, true);
                }
                // 左上のセルでShift + Enterが押されたときは前のコントロールにフォーカス移動
                if (this.CurrentCell.RowIndex == 0 &&
                    this.CurrentCell.ColumnIndex == 0 &&
                    e.Modifiers == Keys.Shift)
                {
                    return this.FindForm().SelectNextControl(this.FindForm().ActiveControl, false, true, true, true);
                }
            }
            // Enterキーが押されらTabキーの処理を行う
            return this.ProcessTabKey(e.KeyData);
        }
        // 既定の処理を行う
        return base.ProcessDataGridViewKey(e);
    }

    private void InitializeComponent()
    {
        this.dataGridView1 = new System.Windows.Forms.DataGridView();
        ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
        ((System.ComponentModel.ISupportInitialize)(this)).BeginInit();
        this.SuspendLayout();
        // 
        // dataGridView1
        // 
        this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
        this.dataGridView1.Location = new System.Drawing.Point(0, 0);
        this.dataGridView1.Name = "dataGridView1";
        this.dataGridView1.Size = new System.Drawing.Size(240, 150);
        this.dataGridView1.TabIndex = 0;
        // 
        // DataGridViewEx
        // 
        this.RowTemplate.Height = 21;
        ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
        ((System.ComponentModel.ISupportInitialize)(this)).EndInit();
        this.ResumeLayout(false);

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

public class clsResize
{
    List<System.Drawing.Rectangle> _arr_control_storage = new List<System.Drawing.Rectangle>();
    private bool showRowHeader = false;
    public clsResize(Form _form_)
    {
        form = _form_; //the calling form
        _formSize = _form_.ClientSize; //Save initial form size
        _fontsize = _form_.Font.Size; //Font size

        //ADD
        var _controls = _get_all_controls(form);//call the enumerator
        FontTable = new Dictionary<string, float>();
        ControlTable = new Dictionary<string, System.Drawing.Rectangle>();
        foreach (Control control in _controls) //Loop through the controls
        {
            FontTable.Add(control.Name, control.Font.Size);
            ControlTable.Add(control.Name, control.Bounds);
        }
        //ADD

    }

    //ADD
    Dictionary<string, float> FontTable;
    Dictionary<string, System.Drawing.Rectangle> ControlTable;
    //ADD

    private float _fontsize { get; set; }

    private System.Drawing.SizeF _formSize { get; set; }

    private Form form { get; set; }

    public void _get_initial_size() //get initial size//
    {
        var _controls = _get_all_controls(form);//call the enumerator
        foreach (Control control in _controls) //Loop through the controls
        {
            _arr_control_storage.Add(control.Bounds); //saves control bounds/dimension            
            //If you have datagridview
            if (control is DataGridView dgv)
                _dgv_Column_Adjust(dgv, showRowHeader);
            //if (control.GetType() == typeof(DataGridViewEx))
            //    _dgv_Column_Adjust(((DataGridViewEx)control), showRowHeader);
        }
    }

    public void _resize() //Set the resize
    {
        form.SuspendLayout();//2026-1-28
        double _form_ratio_width = (double)form.ClientSize.Width / (double)_formSize.Width; //ratio could be greater or less than 1
        double _form_ratio_height = (double)form.ClientSize.Height / (double)_formSize.Height; // this one too
        var _controls = _get_all_controls(form); //reenumerate the control collection
        int _pos = -1;//do not change this value unless you know what you are doing
        foreach (Control control in _controls)
        {

            //ADD
            this._fontsize = FontTable[control.Name]; //<-取得したコントロールのフォントサイズ値で上書きするためにこれを追加
            //ADD

            // do some math calc
            _pos += 1;//increment by 1;
            System.Drawing.Size _controlSize = new System.Drawing.Size((int)(_arr_control_storage[_pos].Width * _form_ratio_width),
                (int)(_arr_control_storage[_pos].Height * _form_ratio_height)); //use for sizing

            System.Drawing.Point _controlposition = new System.Drawing.Point((int)
            (_arr_control_storage[_pos].X * _form_ratio_width), (int)(_arr_control_storage[_pos].Y * _form_ratio_height));//use for location

            //set bounds
            control.Bounds = new System.Drawing.Rectangle(_controlposition, _controlSize); //Put together

            //Assuming you have a datagridview inside a form()
            //if you want to show the row header, replace the false statement of 
            //showRowHeader on top/public declaration to true;
            if (control is DataGridView dgv)
                _dgv_Column_Adjust(dgv, showRowHeader);
            //if (control.GetType() == typeof(DataGridViewEx))
            //    _dgv_Column_Adjust(((DataGridViewEx)control), showRowHeader);


            //Font AutoSize
            control.Font = new System.Drawing.Font(form.Font.FontFamily,
             (float)(((Convert.ToDouble(_fontsize) * _form_ratio_width) / 2) +
              ((Convert.ToDouble(_fontsize) * _form_ratio_height) / 2)));

        }
        form.ResumeLayout();//2026-1-28
    }

    private void _dgv_Column_Adjust(DataGridView dgv, bool _showRowHeader) //if you have Datagridview 
    //and want to resize the column base on its dimension.
    {
        /*2026-1-28
        int intRowHeader = 0;
        const int Hscrollbarwidth = 5;
        if (_showRowHeader)
            intRowHeader = dgv.RowHeadersWidth;
        else
            dgv.RowHeadersVisible = false;

        for (int i = 0; i < dgv.ColumnCount; i++)
        {
            if (dgv.Dock == DockStyle.Fill) //in case the datagridview is docked
                dgv.Columns[i].Width = ((dgv.Width - intRowHeader) / dgv.ColumnCount);
            else
                dgv.Columns[i].Width = ((dgv.Width - intRowHeader - Hscrollbarwidth) / dgv.ColumnCount);
        }
        */  //2026-1-18

        // 1. 行ヘッダーの表示設定だけを行う
        dgv.RowHeadersVisible = _showRowHeader;

        // 2. 個別の列幅設定を維持しつつ、全体のバランスを取る設定
        // すでに DataSource がセットされている場合のみ実行
        if (dgv.ColumnCount > 0)
        {
            // 全体のモードを一旦 None にして、個別の Width 指定を有効にする
            dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;

            // もし「本文(OcrText)」という列があるなら、それだけを「伸びる設定」にする
            if (dgv.Columns.Contains("OcrText"))
            {
                dgv.Columns["OcrText"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            }

            // その他の「FileName」などは、フォームの比率に合わせて幅を計算し直す
            // ※均等割り(/ dgv.ColumnCount)をしないのがポイントです
        }

    }




    private static IEnumerable<Control> _get_all_controls(Control c)
    {
        return c.Controls.Cast<Control>().SelectMany(item =>
            _get_all_controls(item)).Concat(c.Controls.Cast<Control>()).Where(control =>
            control.Name != string.Empty);
    }
}

-windows

PAGE TOP