コンテンツにスキップ

選択行のプレビュー

扱うこと: 結果の表で選択した行の前後をシートのような表で見せる選択行のプレビュー、結果の表を元のファイルごとの見出しでまとめる表示。扱わないこと: 検索そのもの(検索タブ)、元のファイルを開く(元のファイルを開く)。先に読むページ: 検索タブ。

選択行のプレビュー

sequenceDiagram
    actor U as 利用者
    participant G as 画面のスレッド
    participant B as 画面の裏の仕事(startJob)

    U->>G: 行を選ぶ
    G->>B: readContentIndexLines を頼む(最初は前後 50 行ずつ)
    B-->>G: 前後の行(キャッシュがあれば使い回す)
    G->>U: シートのような表で表示
    U->>G: 縦にスクロールして端に近づく
    G->>B: 上または下の続きを読み足す(readContentIndexLines)

結果の表で行を選ぶと、表の下に選択行とその前後の行を表示する。元のファイルを開かなくても、一致した場所の周り(見出しの行・隣のセルなど)が分かる。

項目 仕様
表示・非表示 常に表示する(行を選んでいなくても枠を出したままにする)。行を選ぶたびに表の高さが変わらず、次に選んだ行も同じ場所に出る。行を選んでいないときは、枠の中に 行を選ぶと、その前後を表示します。 と出し、[開く][フォルダを開く]は押せない
高さの変更 結果の表とプレビューの間の線を上下にドラッグして高さを変えられる(GridSplitter。見た目は検索とインデックス管理で共通の Style Splitter.Rows)。結果の表は 60px、プレビューは 100px(列見出し+1 行が見える高さ)を下限とする。初めの高さは 200px。高さは画面を開いている間だけ保つ(設定には保存しない)。高さを変えても、読み直さない(表示する行数は高さで決めない。下の「表示する行」)
見出し <相対フォルダ\ファイル名> ・ <場所(行ごとの表記)> ・ <種別>(例: 営業部\見積.xlsx ・ [シート]4月!B3 ・ セル。Excel はセル番地が場所に含まれる)。右に[開く](右の[▾]のメニューで開き方を選ぶ。元のファイルを開く)と[フォルダを開く]を置く。動作は 元のファイルを開く の[元のファイルを開く]・[フォルダを開く]と同じ
表示する行 最初は選択行の前後 50 行ずつ(最大 101 行。previewFirstLines)を読んで出す。高さに入る行数より多く読み、縦にスクロールして見られる。高さを変えても読み直さない。選択行は、プレビューの上から 3 行目に来るよう縦にスクロールする(位置は判断層の getPreviewTopRow。選択行の前が 2 行に満たなければ先頭から)
行は仮想化して、見えている行(と前後の余白)だけ画面の要素を作る(PreviewRows のテンプレートの中のスクロール PreviewScroll に VirtualizingStackPanel。Recycling・縦のスクロールは行の単位)。行の数が増えても要素の数は増えない
本文インデックスのファイルから、ヒットの元のファイル・場所(Book・Location)の中の該当の行だけを取り出す(readContentIndexLines、実装構成)。Excel は場所の N 行目がシートの N 行目なので、元のシートの前後の行になる。場所の先頭・末尾では、ある行だけを表示する
読み込みの速さ 大きな本文インデックスのファイルでも画面が固まらないよう、次の 3 つを行う。
・読み込み(readContentIndexLines)は画面のスレッドでは行わず、画面の裏の仕事のスレッド(startJob。BackgroundQueue)で行う。読み終わったら表を作る。読んでいる間に別の行を選んだら、古い行の結果(頼んだ番号が古いもの・今選んでいない行のもの)は捨てる
・検索で読んだ本文インデックスのファイルの内容と場所の一覧(newTsvTextCache。検索を速くする仕組み)があれば、ファイルを読み直さずに使う。本文インデックスのファイルが書き換わったら(大きさ・更新日時が変わったら)読み直す。キャッシュに無い(上限を超えた等)ときは、画面が最後に読んだ 1 つのファイルの控え(previewMarks。場所の一覧と、場所ごとの 200 行ごとの位置)を渡し、同じファイルの行を続けて選ぶ間は、目当ての行の手前の位置から必要な行までだけをファイルから読む(readContentIndexRange)。控えは 1 つのファイルの分だけで、別のファイルに移ったとき(更新日時・大きさが変わったときも)は使わず、そのファイルの全体を読む。つまり、初めて開いたファイルの最初の読みは、ファイルの全体を読む
・↑↓で続けて選択が変わったときは、最後の選択の 120 ms 後に 1 回だけ読む
読み足し 縦にスクロールして、見えている行が読んである範囲の端から 20 行以内(previewReadAheadLines)に近づいたら、その向きに 200 行(previewChunkLines)を読み足す(上は 1 行目まで)。読んでいる間は、その端に 読み込み中… の行を出す。読めなかったときは 読めませんでした と出し、その向きはもう頼まない。ファイルの端まで読んだ向きも頼まない
・同じ向きの読み足しは 1 つずつ。別の行を選んだら、古い結果は捨てる
・上に足したときは、見ていた行が同じ所に見えたままになるよう縦の位置を直す。選んだセルの範囲は行の番号で持つので、足してもずれない
・持つ行が上限(3,000 行。previewMaxRows)を超えたら、見ている所から遠い側を 200 行ずつ捨てる。捨てた側は、また近づいたら読み直す
・読み足しの判断は判断層の getPreviewReadMore・getPreviewTrim。続きの読み込みは、前に読んだ行の位置の控え(場所ごとの 200 行ごと)から読み始めるので、ファイルの先頭から読み直さない(readContentIndexLines)
表示する列 全部の列を表に持ち、横にスクロールして見る(列の数で切らない。以前の「一度に 200 列まで」は無くした)。ただし、画面の要素は見えている列(と左右に 3 列ずつ。previewColumnMargin)の分だけ作る(列の仮想化)。列見出しも、見えている列の分だけを作る。見えていない列の幅は、見出しの帯と各行の左右の空き(PreviewWindow の LeftSpace・RightSpace)で埋め、横スクロールの幅は全部の列を並べたときと同じにする
・見える列の範囲は判断層の getPreviewColumnWindow(列の幅の累積・横の位置・見える幅から、先頭と末尾の列と左右の空きを返す。累積は PreviewTable の Prefix が、列が増える・幅が変わるまで使い回し、境目は二分探索で探すので、列が非常に多くても横にスクロールするたびに全部の列を数え直さない)。範囲が変わったときだけ、見えている行(と前後 10 行。previewCellRowMargin)のセルを作り直す(PreviewTable の SetWindow・EnsureCells)。範囲の外に出たセルは捨てる
・選んだセルの範囲は行番号と列の番号で持つので、見えていない列を含む範囲でもコピーできる(見えていない列のセルの値も、表が持つ元の値から取る)
・列見出しの幅を変えたときは、その列から右の左右の空きを計算し直し、範囲を決め直す。ただし見出しをドラッグしている間は範囲を固定し(見出しを入れ替えると、ドラッグしている部品が作り直されて途切れるため。縦にスクロールして見えた行のセルだけ作る)、ドラッグが終わったあとに決め直す。ドラッグの途中で別の行を選んで表が替わったときは、固定を解いて新しい表の範囲を決める
・列の幅は、先頭の行(最大 10 行。WidthSampleRows)のセルから決める。一致したセルを含む行は、必ずその中に入れる
表の形 左端に行番号、上に列見出し(Excel は A, B, C…、Word・PowerPoint はタブで分けた順の 1, 2, 3…)。セルの分け方は splitTsvCells と同じ(" で囲まれたセルは 1 セル)
強調 選択行は行全体を薄い青にし、行番号を太字にする。一致したセルは黄色の背景・太字(前後の行で一致したセルも同じ)
列の幅 初めの幅は、見出しと各行のセルの文字数(セル内改行のあるセルは最も長い行)から決める(Excel は最大 260px、段落が 1 列になる Word・PowerPoint は最大 640px。ただし Word・PowerPoint・テキストのファイルは、1 列の幅を行番号を除いた枠の見える幅いっぱいに広げる(getPreviewFillWidth。初めの幅より狭くはならない。行を入れて縦のバーが出るかどうかが決まってから合わせ、見える幅が変わるたび(縦のバーの出入り・窓や左の欄の幅の変更)に合わせ直す。狭まれば初めの幅まで戻る。列見出しのドラッグで変えた幅は合わせ直さない))。列見出しの右端をドラッグすると幅を変えられる(下限 24px)。変えた幅はその表示の間だけ有効で、ほかの行を選ぶと初めの幅に戻る
セルの表示 セル内改行のあるセルは改行のまま折り返して表示する(3 行程度まで。入りきらない分は隠れる)。1 行のセルも幅に入りきらなければ折り返す
長いセルは先頭 300 文字+… で表示し(MaxCellChars)、ツールチップは先頭 1,000 文字まで(MaxToolTipChars)。コピー(下の「値のコピー」)は切り詰めずに元の値をそのまま使う。列の幅を決めるときも各行の先頭 100 文字までしか見ない(MaxWidthChars。幅には上限があるため結果は変わらない)
実測(32,767 文字のセル): 切り詰めないと約 0.9 秒、切り詰めて約 0.4 秒
値のコピー セルをクリックするとそのセルを選び(青い枠)、Ctrl+C でその値をコピーする。Shift+クリック、またはドラッグで四角い範囲を選べる。右クリックメニューに、先頭に[元のファイルのこの場所を開く](太字。ダブルクリックと同じ処理)、区切りのあとに[選んだセルをコピー][この行をコピー]を置く(並び・文言は判断層の getPreviewMenuItems。プレビューが無いときは出さない)
・1 セル:値をそのまま(引用符・エスケープなし)入れるので、ほかのアプリの入力欄にそのまま貼れる
・複数セル:タブ区切り・行は改行で入れる。改行・タブ・" を含むセルは " で囲む(Excel に貼り付けると元の位置に並ぶ)
・ステータスに セルの値をコピーしました:<値> / N 個のセルをコピーしました(Excel に貼り付けると、元の位置に並びます) と表示する
縦スクロール 読んだ行が高さに入りきらないときは縦にスクロールする(行の単位。セル内改行で高さが違う行があっても、行の単位なので飛ばない)。列見出しは行とは別のスクロールに置いて固定し、縦スクロールで隠れないようにする(横位置は行に合わせる)
横スクロール 選択行で最初に一致したセルが見えないときは、そのセルが見える位置まで横にスクロールする(そのセルのある列を含む範囲のセルを作ってから、位置を合わせる)。横にスクロールしたまま縦にスクロール・読み足しをしても、横の位置は変わらない。右端までスクロールすると、最後の列が見える
本文インデックスのファイルが読めない インデックスが作り直された・削除されたなどで読めない・その場所が見つからないときは、選択行だけを表示する(読み込みのスレッドで失敗したときも同じ)

ファイルごとにまとめた表示

flowchart TD
    H["FileGroup(元のファイル 1 つの見出し)"] --> C{"開いている?"}
    C -- いいえ --> Z["見出しだけ表示(▸)"]
    C -- はい --> R["ensureRows で HitRow を作る(▾)"]
    R --> P["prepareHitRow で場所の表記・強調を作る"]

結果の表(No.7)は、元のファイルごとの見出しの下に、そのファイルでヒットした行を並べる。検索した直後はすべて閉じた状態(見出しだけ)にし、どのファイルに何件あるかを先に見せる。1 つのファイルに多くヒットしても一覧が長くならず、件数が多くても表に入れる項目がファイルの数だけで済むため速い。

項目 仕様
見出し 元のファイル 1 つに 1 行。左から、開閉の印(閉じている ▸ / 開いている ▾)、アプリのアイコン(Excel は緑の X、Word は青の W、PowerPoint は赤の P)、ファイル名(太字)、フォルダ(インデックスフォルダからの相対パス。閉じていても開いていても出す。ルート直下のファイルは出さない)。右端に、ヒットした場所が 2 か所以上のときは場所と件数([シート]4月 ほか 2 か所 ・ 3 件)、1 か所だけのときは件数だけ(3 件。場所は行の「場所」列に出るため重ねない)。ツールチップは元のファイルのフルパス
場所の表記 場所ごとの表記(describePlace。[シート]4月・3 ページ(目安)・スライド 7 など。結果の表の「場所」列はこれにセル番地を足したもの)。2 か所以上なら、見つかった順の先頭と残りの数を [シート]4月 ほか 2 か所 のように出す(describeFileLocations。見出しの右端に付ける場所は getFileHeaderLocationPrefix が決め、1 か所だけなら付けない)。絞り込み中もすべてのヒットから作る
件数 見出しの件数は、そのファイルで絞り込みに合う行の数。No.5 の件数は 一致 N 件(M ファイル)(行の数と、ファイルの数)
開く・閉じる 見出しをクリック(または選んで Enter)すると、そのファイルの行を開く・閉じる。Shift・Ctrl を押しながらのクリックは複数選択なので切り替えない。No.14 の[すべて開く][すべて折りたたむ]で、すべてのファイルを開く・閉じる。検索中に[すべて開く]を押すと、そのあと見つかったファイルも開いた状態で足す。新しく検索すると、すべて閉じた状態に戻る
見出しを選んだとき そのファイルの先頭の行(絞り込みに合うもの)を選んだものとして扱う。選択行のプレビュー(選択行のプレビュー)はその行を出し、[開く][フォルダを開く]、右クリックメニューの[元のファイルを開く]などはその行の場所を開く。ダブルクリックでは開かない(クリックで開閉するため)。Ctrl+C([選択行をコピー])は、そのファイルの行(絞り込みに合うもの)をすべてコピーする
並び順 ファイルは既定でパス順(見つかった順)。列見出し(場所・種別・該当行)をクリックすると、ファイルの中の行をその列で並べ替え、ファイルの順は並べ替えた後の先頭の行の順にする(もう一度で逆順。sortFileGroups)。「場所」は TSV の場所の名前 → 行番号 → 見つかった順の順にする(セル番地の文字の順では A10 が A9 の前に来るため、番地ではなく行番号で並べる)。同じシートでも 売上(セル)・売上[コメント]・売上[図形] は別の場所の名前のため、「場所」で並べ替えるとセル → コメント → 図形の塊になる。ファイル名・フォルダでの並べ替えは無く、ファイルの順は見つかった順か、これらの列で並べ替えたときの先頭の行の順になる。検索中に並べ替えたときは、検索の終わりに、あとから来た行も含めて並べ直す
絞り込み 絞り込み(絞り込み)はファイル名に当て、合うファイルは行をすべて残し、合わないファイルは見出しも出さない(selectShownRows・getResultItems)
結果をファイルに出力 閉じているファイルの行も含め、絞り込みに合うすべての行を表の順に出力する(getShownHitRows。形式は 結果をファイルに出力 のまま)
  • 表(ResultGrid)には、見出し(FileGroup)と、開いているファイルの行(HitRow)だけを入れる(result_list.ps1)。見出しの行は、行の形(DataGridRow の Template)を tab_search.xaml の FileHeaderRow に替えて、列をまたいだ 1 行にする。
  • 行は必要になるまで作らない(遅延表示)。 検索中は、ヒットを見出しの Hits に生のまま足すだけにし、1 回の取り込み(pumpSearch)の最後に、新しいファイルの見出しをまとめて表に入れる(flushResults)。表の行(HitRow。場所の表示・強調の元になる)は、ファイルを開いたとき・見出しを選んだとき・絞り込み・並べ替え・出力・コピーのときに、必要なファイルの分だけ作る(ensureRows)。見出しの件数は、絞り込んでいなければヒットの数(行を作らずに分かる)、絞り込み中は合う行の数にする。一致箇所の強調などは、画面に出た行だけで作る(HitRow.Prepare)。
  • 開閉・絞り込み・並べ替えで多くの行を出し入れするときは、1 件ずつではなく表の中身をまとめて入れ替える(rebuildResults)。
  • WPF の DataGrid のグループ化(GroupStyle)は使わない。グループ化は 1 件ごとに振り分けるため遅く(実測: 10,000 件・504 ファイルで、行ごとの一覧 約 3 秒 → グループ化 約 8〜9 秒。最初に閉じておいても速くならない)、列の幅が決まらない不具合もあったため。
  • 実測(10,000 件・504 ファイル。検索スレッドが結果を出し終えたあと、画面が取り込み終えるまでの待ちが検索時間の大半を占める): 検索中に行を作ると 約 4.0 秒(うち待ち 約 2.4 秒)、行を後で作ると 約 2.6〜2.9 秒(うち待ち 約 1.1〜1.3 秒)。代わりに[すべて開く]は、そのとき 10,000 行を作るため 約 1.1〜1.5 秒かかる(1 ファイルを開くときは、そのファイルの分だけ)。
  • 判断(場所の要約 describeFileLocations・表に並べる項目 getResultItems・絞り込み selectShownRows・並べ替え sortFileGroups・行を画面に出す準備 prepareHitRow)は判断層の search\result_list_view.ps1 に置き、テストする。
  • 見出しのアイコン(xaml/search/result_list.xaml の Badge)は AppKind(getContentIndexFileKind)で色と頭文字を変える。Excel(緑・X)・Word(青・W)・PowerPoint(オレンジ・P)に加え、テキストファイルは灰色の地に T。PDF は getContentIndexFileKind が PDF を返す。