ListViewはセル表示(表とかグリッドとか言われる物)をする上でいちばん楽な方法であるため、意外と使っているのがあたしの現状ですが?
ListViewに表示される物が増えてくると、気になってくるのが遅延
なので、遅延を隠蔽するためのいくつかの機能があり、これらを利用することになるのですが、それでも限界という物があります。
結局、ListViewにアイテムを大量に追加することその物が遅いと言うことです。
ListViewはItems.Add等を実行すると、その場で追加するため(昔のSendMessage等のように処理が終了するまで戻ってこない)、複数追加するとその度にListViewがリフレッシュ(画面上ちらつく)され、遅い。
なので、いくつかの方法で、コレに対処が出来ます。
その一つが、再描画その物を中止させる。
ListView.BeginUpdate;
ListView.EndUpdate;
です。
コレにより描画その物が停止されるので、画面がプルプルすることもなく、追加が終わってOSに制御が戻された時点で更新されます。
しかし、コレを試してみると、
ListViewItemを配列に組み込む作業その物が結構時間が掛かることが判ります。
それの対応のためか、
ListView.Items.AddRange
という、初めから複数を一気に追加するための機能が追加されました。
しかしこれも数千追加するとなるとそれなりに時間が掛かります。
それ以上に、元データを用意する方も時間が掛かるのです。
特にImageListを作るには、ImageListその物がUIに組み込まれている機能なので、別スレッドから追加することが出来ない。
上記ListView.Items.AddRangeもUIの機能なので同様ですね?
ですから、元データを作るところは別スレッドで行う事が出来ても、UIの追加自体はメインスレッドで行わなければならない。
マルチスレッドごときでは対策できないのです。
(注:Invokeを使えば別スレッドから処理できるとかいう馬鹿がたまに居ますが、あれは一時的にUI側のスレッドを走らせて処理して、戻ってくるのを待って居るのです。だからUI側のスレッドで走っているのです。)
現在、MusicPlayerを作っているのは過去のを読んで貰えれば判るとは思いますが、数万曲のプレイリストを作った場合どうなるのかは判ったものではありません。
OSが64Bit化され、メモリやアイテム数の制限がなくなってきたのかも知れない昨今において、数万程度で時間が掛かって貰っては困るのです。
もちろん、1,2秒が待てないという意味での話ではありません。
あれだけ書いておきながら、”結局遅いのか”と思われたくないのです(笑
でも、これだけは言っておきます。
あたしが作った部分が遅いわけではありません。
ListView.Items.AddRangeを実行してから、戻ってくるまでがとても時間が掛かるのです。
(5000曲追加すると数十秒かかります。)
手の出しようが無いのです。
とか思っていたのですが、別の解決手段が提供されていました。
それが、
VirtualModeってやつです。
これは、つねにリスト全体を保持しておくのではなく、必要になったときに、その部分だけのデータをLVIとして送り込んで、そこの部分だけ表示するというものです。
(注:この関数機能自体は、マルチスレッドではありませんが、マルチスレッドで1件ずつ追加してるようなイメージでOKです。)
コレにより、ListViewItemの問題の対外の問題が解決されるのです。
それが、消費メモリです。
現在テストのために取り込んでいる音楽データは5000曲
この全タイトルをListView.Itemsに追加すると、消費メモリが2G近くまで増大するのです。
(全テキストと全イメージのデータを合わせても20Mくらいなのにね!)
一度なら表示出来るのですが、2度目は、データ差し替えタイミングの問題で、メモリ不足になってしまうのです。
もちろん、更新タイミングをもっと細かく区切って細かくDisposeすれば大丈夫かも知れませんが、現状そうは行っていませんし、余りにクリティカルすぎです。
それと、数万曲は入らない確定です。
VirtualModeの場合は表示時にに必要なデータだけを取得して居るみたいです。
だから、全く何も保持していない感じです。
実際には、
必要になったときにListView.RetrieveVirtualItemが呼ばれますので、即座にLVIを引き渡します。
あと、ListView.CacheVirtualItemsにキャッシュ機能があるので、それを利用することで、十分な早さになります。
CacheVirtualItemsは作らなくても動作しますし、この関数自体にキャッシュ機能が有るわけでは無いので作らなくても全然OKです。
ですがが、RetrieveVirtualItemが必要なアイテムを一つずつ要求するのに対して、CacheVirtualItemsはある程度の量を”まとめて作っておくように”と言う通知なので、コレを作っておくことで次に来るRetrieveVirtualItemの時に作り置きを返答することが出来ます。
また、CacheVirtualItemsを使うかどうかとは関係が無いのですが、RetrieveVirtualItemはものすごく大量に飛んでくるので、ListViewItemの配列を作っておいて、作った物はそこにどんどん溜めておく必要が有ります。
結果的に、CacheVirtualItemsを用意しなくても、キャッシュ機能は作らなければならないので、有った方がスマートです。
ただ、構造的にLVIは全部作る可能性があると言うことが一つの問題です。
まぁ、データを1000件以上作ったら一度リフレッシュするとかの機能が有っても良いですね?
でも、それほど心配は要りませんでした。
5000曲全部読み込んだ段階での消費メモリは180M 約1/10ですね
コレなら、このままでも5万件で1.8Gですから、32ビットアプリでもギリギリメモリは足りそうです。
でも、10万件は無理
数万件程度溜まったらリフレッシュする機能は必要かも知れません。
そこで考えるのが64bit化
64Bitプログラムに変換した所・・・・・大問題多発なのです。
Accessを利用する都合でAccess Database エンジンも64Bitの物が必要。
これは、有るからいれれば良い的な話ですが?
問題はいれられないこと。
32Bitと64Bitを同時にいれられないみたいなのです。
OLEで接続するわけですから、OLEのエンジンが64と32を自動的に仲介してくれるのかと思ったのですが?
どうもOLEは実行プログラムに対してかなり身近にリンクされて実行されているようで、出来なかったのです。
しかもOfficeまるごと64Bitのを入れないとダメです。
ちょろっと読んだだけなので詳細は分からないのですが、DBに埋め込んでおけるいくつかの機能は32BitAccessと64BitAccessで互換がないとかで、データベースファイルその物を作り直さないとダメになるっぽいです。
(注:保存してあるデータだけなら互換はあるみたいです。)
このアプリのためだけにそれほど大変な作業を使ってくれるかも知れない人達に強いるのは無理。
まぁ、そんな感じで、64bit化はやはり諦めるしかないようです。
それはそれとして、以前Officeをインストールしたときは、4時間くらい経って成功して終わったはずなのですが、消して、64ビットに入れ替えて、再度消して32ビットにしようとか色々していたら、インストールもアンインストールも出来なくなって
削除ツールとか、手動削除方法とか、沢山して、それで、やっと32bit版が入りました。
まぁ、やらないことをお勧め、
但し、95%辺りで、正常に出来ませんでした的なエラーが出て、再試行は出来るのですが、何度再試行しても完了しないので、「無視」を選択するとすぐ完了したりして、使いづらいったらありゃしない?
で、そうなるとやはり2G居ないで動作するように作成する必要がありますね!
で、チョイチョイ弄ってあっちが動くようになると、こっちがダメ、あっちがダメというのが出てくる。
今度はImageList
5000曲分のサムネイルを読み込むのは確かにメモリを圧迫するかも知れません。
でも一応32*32にまで圧縮はしているんですけどね?
単純に32*32*4*5000バイト消費すると考えるとデータだけで20M
本来であれば、重いはずも無いデータ量。でもImageはそれぞれクラスですし、Imageクラスには余計な機能がたくさん有りますし・・・で、調べてみるとやはりImageListがかなり重いよう。
該当文章に書かれているPCは今からすると10年以上前のでしょうか?
クロックはおそらく1G前後だと思われる頃の文章ですから、それと比べると全然遅くないですが、
時間の問題ですよね。
ですから、自分でイメージの配列を保持して、OwnerDrawで書いてしまうのが良いらしいですね。
その結果として消費メモリは130Mになりました、イメージデータが増えるに従って速度の低下は見られますが、
Listクラスを使っているのを考えると十分でしょうか?
それはそれとして、OwnerDrawの際、Column 0だけなんか減んな癖が合って、
e.BoundのLeftが常に0なんですよね?
色々と調べてみたんですが?e.Header.DisplayIndexが0以外の場合はListView.Columnsの各DisplayIndexがColumn 0のDisplayIndexよりも小さいものの幅を全部足す以外無いっぽいですね。
まぁ、自分で計算するのは大変ですが、実際にはPCが計算してくれるので、適当にforeachで回してしまえばすぐなので、まぁ問題ないかと?
それはそれとして、あたしとしては、その結果に+1をしています。どうも境界線上に有る気がするんですよ? 気分的な問題ですけどね?
どうも、グリッドはセルの左側に書かれているような気がするんです。
他のの幅を全部足したところから書いてしまうと、グリッドと重なっているような気がする。
そう言ったあれこれを経て、ListViewの問題は大方完了
所で、コレが原因で、ソートが出来なくなりました。
ListViewが全件の情報を保持していないため、ソート機能が使えないのと、グループの設定も出来なくなりました。
現状、グループをOnにすると造り込みの甘さが目立つ結果となっているので、コレを使わない方向でって事なので、まぁどうでも良いんですが、グループをOnにしてもグループ表示してくれないのです。
(まぁ、一応今後のために、グループ情報自体は保持しているんですけどね)
で、そうなると、データのソートは毎回DBですることになります。
まぁ、DataTableでも簡単なソートは出来ますし、何なら、Sortを実装しても良いんですが?
プレイリストなどの分を全件メモリ上に展開しておく必要があります。
まぁ、余り褒められたものではありませんので、DB上で行うことにしました・・・
そしたら、今度は別の問題が発生したのです。
作成中のプレイリストのソートが出来ません。
保存すれば出来ますが?
勝手に保存するわけにも行かない。
でも、メモリ上で行うわけにも行かない。
なので、作成中のプレイリストという専用のテーブルを用意しなければなりません。
何故かというと、DBではファイル管理機能を謳ってはいますが、対象外のフォルダから一時的に追加することもあるでしょう?
って事で、ファイル名だけで保持しているデータもあるんですよ?
それらのTag情報はそもそも、DBに乗っていない。
登録済みのは、複数テーブルにデータが分散していますから、それらと一緒にソートすることは出来ない。
結果、ソート対象情報を全件持った専用のテーブルを用意しなければならなくなったのです。
因みに、保存しておかないとソートも何も出来ないので、この、仮置き場への保存は、随時となります。
画面上では、未保存としておいてって感じですね?
その上で、実際に保存したときに、有るべき保存場所へ有るべき形で保存すると言うことに成ります。
で、コレをするためには、プレイリストのもっと綿密な管理が必要になりました。
旧式のプログラムのように必要な機能を満たすために、それ専用の関数を作っていけば良い問題ではありません。
いわゆるオブジェクト指向と言うものです。
コレまでも、それなりに暮らすわけはして作ってきましたが、
それだけでは、もうどうにもならないほど構造が複雑になってしまったのです。
結局プレイリスト専用のクラスを作る事にしました。
PlayListCollection
複数のプレイリストをまとめて管理するための物です。
PlayList
プレイリストの情報を保持するものです。
PlayListItem
リストのそれぞれの曲を表すデータです。
PlayListCollectionには、現在のプレイリストと、保存済みのプレイリスト全体の情報を保持し、全ての保存/読み出しの機能を含めました。
仮に、プレイリストを間違えて消してしまうようなプログラムを作ったとしても、ここを通さないと消せないので、自動的に問い合わせが出るような感じです。
また、同様に、現在のプレイリストを変更するための機能も一つの関数に集約することで、間違えた操作をできなくしました。(オーバーロードはたくさん有ります。)
とりあえず、特殊な機能を持っているプレイリストは
「現在のプレイリスト」と呼んでいる作成中/更新中の物
「無名のプレイリスト」と呼んでいる自動保存されて好き勝手に追加や削除が出来るもの。
「現在のプレイリスト」自体は、実際にはプレイリストとしては存在していなくて、無名のプレイリストやそれ以外の普通(有名)のプレイリストのコピーとして作成され、リストへの追加や削除をしても、元になっているプレイリストを変更しないで、データの保持及びソートなどが出来る様にしたもの。
「無名のプレイリスト」はちょっとファイルを再生してみたいときとかに、mp3ファイルをダブルクリックしたりすると自動的に選択されて、リストに追加されたりする物です。
「新規」を選ぶと、まっさらになったりします。
今すぐ聴きたい曲があったり、最近買ったばかりで、ヘビロテしたいときとか、様々ですね!
それ以外のプレイリストは、「現在のプレイリスト」にコピーされて再生されるわけですが、ファイルをD&Dしたり、DBに登録されているファイルをダブルクリックしたりすると、プレイリストにどんどん追加されていくので、上書き保存すれば、元のプレイリストが更新される感じです。
編集中の情報は一つしか保持されないので、保存しないで、他のプレイリストを開いたりすると、変更は破棄されて、新しく開いたプレイリストが「現在のプレイリスト」になるかんじですね。
まぁ、使う側は「現在のプレイリスト」を意識する必要は全くありませんけどね、
それにしても、WindowsMediaPlayerはなんであんなに起動が遅いのでしょうか?
アイコンをダブルクリックして起動してから再生までに1秒の遅延もなくプレイヤーは出来たわけですが?
あ〜、未だ、仕様の変更が適用されていない場所が合ったりしているのを、作り込んでいる最中なので、まだ公開は出来ないのですが?
もう、普段あたしが使う分には、使用に多いえられるような状態にはなっています。
|