[小ネタ]ListViewで項目の頭出しをする

#はじめに
ListView の並びは変えずにアイテムを一番上に表示したいな～　ということでやってみました。

##やり方
<Code>ListView</Code>の<Code>ScrollViewPos</Code>プロパティと<Code>GetItemRect</Code>メソッドを使います。

```pascal

var
  itemRect:TRectF;
begin
  itemRect :=  ListView1.GetItemRect(対象アイテムのインデックス);
  ListView1.ScrollViewPos := ListView1.ScrollViewPos + itemRect.Top;
end;
```

<Code>GetItemRect</Code>メソッドは、ListViewの現在のスクロール位置からの相対位置を返すので、対象アイテムがリストの上側に隠れている場合は負の値になります。
その為、現在のスクロール位置にアイテム位置を足し算してあげればOKです。
更に、<Code>FloatAnimation</Code>を組み合わせれば、スクロールする様子をユーザーに見せることもできます！

なお、<Code>ScrollViewPos</Code>の最大値を超えてはスクロールしない為、最後の方のアイテムはリストの先頭には表示できません。


その他、<Code>ScrollTo</Code>メソッドもあります。こちらは対象のアイテムが画面内に無い場合に、そのアイテムが表示されるまでスクロールしてくれます。（ここでは詳しく扱いません）


##サンプル
例として、リストアイテムのヘッダーを頭出しするプログラムを作ってみました。
![ListViewSeek.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/1bacaeef-46d8-c684-79c2-1ec777dc29ea.gif)

###手順１　コンポーネント配置

* フォームに<Code>TComboBox</Code>、<Code>TListView</Code>、<Code>TProtoTypeBideSource</Code>を配置します。
* <Code>ComboBox1</Code>は<Code>Align:Top</Code>、<Code>ListView1</Code>は<Code>Align:Client</Code>に設定します。
* <Code>ProtoTypeBindSource1</Code>は<Code>ColorsNames</Code>フィールドを追加します。その際、値のシャッフルと繰り返しはチェックを外します。
* <Code>TFloatAnimation</Code>を<Code>ListView1</Code>の子になるよう配置します。<Code>PropertyName</Code>フィールドに、<Code>ScrollViewPos</Code>を設定します。

![l1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/8966651a-c863-5d49-3e77-71d141e2c969.png)


###手順２　バインディング
* デザイナ上で<Code>ListView1</Code>を右クリック→ビジュアルにバインドを選択します（LiveBindingデザイナが表示されます）。
* LiveBindingデザイナ上で、<Code>ProtoTypeBindSource1</Code>の<Code>ColorsName1</Code>から<Code>ListView1</Code>の<Code>Item.Text</Code>へドラッグします（矢印が引かれます）。同様に、<Code>ColorsName1</Code>から<Code>ItemHeader.Text</Code>に矢印を引きます。
* オブジェクトインスペクタにて、<Code>LinkFillControlToField1</Code>の<Code>FillHeaderCustomFormat</Code>に<code>SubString(%s, 0, 1)</code>を設定します（選択肢に出てきます）。
また、<Code>UseEvalShortCut</Code>を<Code>False</Code>にします。

![l2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/efcc8957-1131-7075-73e1-8fbc6b8431e6.png)


###手順３　イベント処理記述

オブジェクトインスペクタにて、<Code>LinkFillControlToField1</Code> のイベント<Code>OnFilledList</Code>をダブルクリックし、以下のようにコードを記述します。
内容は、<Code>ComboBox1</Code>に<Code>ListView1</Code>内のヘッダーアイテムを登録しています。

```pascal

procedure TForm1.LinkFillControlToField1FilledList(Sender: TObject);
var
  i:integer;
  item:TListViewItem;
begin
  ComboBox1.Clear;

  for I := 0 to ListView1.ItemCount -1 do
  begin
    item := ListView1.Items[i];
    if item.Purpose = TListItemPurpose.Header then
      ComboBox1.Items.AddObject(item.Text, item);
  end;
end;

```

次に、<Code>ComboBox1</Code>の<Code>OnChange</Code>イベントに以下を記述します。
リストのスクロール位置の取得とアニメーションの設定・開始を行っています。

```pascal

procedure TForm1.ComboBox1Change(Sender: TObject);
var
  item:TListViewItem;
  itemRect:TRectF;
begin
  if ComboBox1.ItemIndex > -1 then
  begin
    item := ComboBox1.Items.Objects[ComboBox1.ItemIndex] as TListViewItem;
    itemRect := ListView1.GetItemRect(item.Index);

    FloatAnimation1.StartValue := ListView1.ScrollViewPos;
    FloatAnimation1.StopValue := ListView1.ScrollViewPos+itemRect.Top;
    FloatAnimation1.Start;
  end;
end;
```

##完成
コンパイルして、動作確認してみましょう！

##応用
こんな感じのインターフェースも簡単に作れます！（操作パネルによってアイテムが隠れないようにスクロールする処理）
![ListViewInterface.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/ed56e413-c1c5-bc07-31b1-23649fe116ce.gif)

###やり方

####フォームデザイン

![l3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33684/6759cb0b-1790-c885-cf45-db62b80e64e3.png)

各コントロールを以下のように設定します（デフォルト値から変えた部分のみ）。

```
ListView1
　Align:Client
```

```
FloatAnimation_ListViewScroll
　PropertyName:ScrollViewPos
　StartFromCurrent:True
```

```
Panel1
　Align:Bottom
　Height:好みの大きさ
```

```
FloatAnimation_PanelSlideIn
　PropertyName:Height
　StartFromCurrent:True
```

```
FloatAnimation_PanelSlideOut
　Delay:3.0
　PropertyName:Height
　StartFromCurrent:True
```

（Button1、Button2は飾りなので適当に）

####ソースコード

Form1 の<code>OnCreate</code>イベントと、ListView1 の <code>OnItemClick</code>イベントを作成します。

```pascal

procedure TForm1.FormCreate(Sender: TObject);
begin
  OriginPanelHeight := Panel1.Height;
  Panel1.Height := 0;
end;

procedure TForm1.ListView1ItemClick(const Sender: TObject;
  const AItem: TListViewItem);
var
  itemRect:TRectF;
  overlapped:Single;
begin

  itemRect := ListView1.GetItemRect(AItem.Index);
  overlapped := itemRect.Bottom - (ListView1.Height - OriginPanelHeight);

  if overlapped > 0 then
  begin
    FloatAnimation_ListViewScroll.StopValue := ListView1.ScrollViewPos + overlapped;
    FloatAnimation_ListViewScroll.Start;
  end;

  FloatAnimation_PanelSlideIn.StopValue := OriginPanelHeight;

  FloatAnimation_PanelSlideIn.Start;
  FloatAnimation_PanelSlideOut.Start;
end;
```

以上です。

#おわりに
Delphi (\*´ω`*) ﾊﾔﾙ!