.Netの同期リストビュー
質問
マスターListViewがスクロールされたときに、子ListViewビューが一致するように更新されるように、あるListViewから別のListViewにビューを結び付けるコントロールに取り組んでいます。
これまでのところ、マスタースクロールバーボタンがクリックされたときに、子ListViewにビューを更新させることができました。問題は、ScrollBar自体をクリックしてドラッグすると、子のListViewが更新されないことです。 Spy ++を使用して送信されるメッセージを確認しましたが、正しいメッセージが送信されています。
現在のコードは次のとおりです。
public partial class LinkedListViewControl : ListView
{
[DllImport("User32.dll")]
private static extern bool SendMessage(IntPtr hwnd, UInt32 msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll")]
private static extern bool ShowScrollBar(IntPtr hwnd, int wBar, bool bShow);
[DllImport("user32.dll")]
private static extern int SetScrollPos(IntPtr hWnd, int wBar, int nPos, bool bRedraw);
private const int WM_HSCROLL = 0x114;
private const int SB_HORZ = 0;
private const int SB_VERT = 1;
private const int SB_CTL = 2;
private const int SB_BOTH = 3;
private const int SB_THUMBPOSITION = 4;
private const int SB_THUMBTRACK = 5;
private const int SB_ENDSCROLL = 8;
public LinkedListViewControl()
{
InitializeComponent();
}
private readonly List<ListView> _linkedListViews = new List<ListView>();
public void AddLinkedView(ListView listView)
{
if (!_linkedListViews.Contains(listView))
{
_linkedListViews.Add(listView);
HideScrollBar(listView);
}
}
public bool RemoveLinkedView(ListView listView)
{
return _linkedListViews.Remove(listView);
}
private void HideScrollBar(ListView listView)
{
//Make sure the list view is scrollable
listView.Scrollable = true;
//Then hide the scroll bar
ShowScrollBar(listView.Handle, SB_BOTH, false);
}
protected override void WndProc(ref Message msg)
{
if (_linkedListViews.Count > 0)
{
//Look for WM_HSCROLL messages
if (msg.Msg == WM_HSCROLL)
{
foreach (ListView view in _linkedListViews)
{
SendMessage(view.Handle, WM_HSCROLL, msg.WParam, IntPtr.Zero);
}
}
}
}
}
MS Tech Forumsのこの投稿に基づくSB_THUMBTRACKイベントをキャプチャして処理しようとしました:
protected override void WndProc(ref Message msg)
{
if (_linkedListViews.Count > 0)
{
//Look for WM_HSCROLL messages
if (msg.Msg == WM_HSCROLL)
{
Int16 hi = (Int16)((int)msg.WParam >> 16);
Int16 lo = (Int16)msg.WParam;
foreach (ListView view in _linkedListViews)
{
if (lo == SB_THUMBTRACK)
{
SetScrollPos(view.Handle, SB_HORZ, hi, true);
int wParam = 4 + 0x10000 * hi;
SendMessage(view.Handle, WM_HSCROLL, (IntPtr)(wParam), IntPtr.Zero);
}
else
{
SendMessage(view.Handle, WM_HSCROLL, msg.WParam, IntPtr.Zero);
}
}
}
}
// Pass message to default handler.
base.WndProc(ref msg);
}
これにより、子のListView ScrollBarの場所が更新されますが、子の実際のビューは変更されません。
だから私の質問は:
- マスターListView ScrollBarがドラッグされたときに子ListViewsを更新することは可能ですか?
- もしそうなら、どのように?
解決
同じことをしたかったので、検索してみたところ、ここであなたのコードが見つかりましたが、助けになりましたが、もちろん問題は解決しませんでした。しかし、それをいじってみたところ、解決策が見つかりました。
スクロールボタンが機能するので、それを使用してスライダーを機能させることができることに気付いたときにキーが来ました。つまり、SB_THUMBTRACKイベントが発生すると、子のListViewがマスターの位置に近づくまで、SB_LINELEFTイベントとSB_LINERIGHTイベントを繰り返し発行します。はい、これは完全ではありませんが、十分に機能します。
私の場合、マスターListViewは&quot; reportView&quot;と呼ばれますが、子ListViewは&quot; summaryView&quot;と呼ばれます。適切なコードは次のとおりです。
public class MyListView : ListView
{
public event ScrollEventHandler HScrollEvent;
protected override void WndProc(ref System.Windows.Forms.Message msg)
{
if (msg.Msg==WM_HSCROLL && HScrollEvent != null)
HScrollEvent(this,new ScrollEventArgs(ScrollEventType.ThumbTrack, (int)msg.WParam));
base.WndProc(ref msg);
}
}
そしてイベントハンドラー自体:
reportView.HScrollEvent += new ScrollEventHandler((sender,e) => {
if ((ushort) e.NewValue != SB_THUMBTRACK)
SendMessage(summaryView.Handle, WM_HSCROLL, (IntPtr) e.NewValue, IntPtr.Zero);
else {
int newPos = e.NewValue >> 16;
int oldPos = GetScrollPos(reportView .Handle, SB_HORZ);
int pos = GetScrollPos(summaryView.Handle, SB_HORZ);
int lst;
if (pos != newPos)
if (pos<newPos && oldPos<newPos) do { lst=pos; SendMessage(summaryView.Handle,WM_HSCROLL,(IntPtr)SB_LINERIGHT,IntPtr.Zero); } while ((pos=GetScrollPos(summaryView.Handle,SB_HORZ)) < newPos && pos!=lst);
else if (pos>newPos && oldPos>newPos) do { lst=pos; SendMessage(summaryView.Handle,WM_HSCROLL,(IntPtr)SB_LINELEFT, IntPtr.Zero); } while ((pos=GetScrollPos(summaryView.Handle,SB_HORZ)) > newPos && pos!=lst);
}
});
そこにあるwhileループの奇妙なフォーマットについては申し訳ありませんが、そういうものをコーディングしたいのです。
次の問題は、子ListViewのスクロールバーを取り除くことでした。 HideScrollBarというメソッドがあることに気付きました。これは本当にうまくいきませんでした。私の場合、より良い解決策はスクロールバーをそこに残すことでしたが、「カバー」することでした。代わりにそれを。列ヘッダーでもこれを行います。子コントロールをマスターコントロールの下にスライドさせて、列ヘッダーをカバーします。そして、私はそれを含むパネルから落ちるように子供を伸ばします。そして、包含パネルの端に沿って少し境界線を提供するために、子ListViewの表示されている下端をカバーするコントロールをスローします。かなりきれいに見えます。
次のように、列幅の変更を同期するイベントハンドラも追加しました:
reportView.ColumnWidthChanging += new ColumnWidthChangingEventHandler((sender,e) => {
summaryView.Columns[e.ColumnIndex].Width = e.NewWidth;
});
これらはすべてちょっとしたもののように思えますが、私には役立ちます。
他のヒント
これは、メンタルジュースが流れるようにするための推測です。 マスターリストのスクロールハンドラーで、子リストのスクロールハンドラーを呼び出すことができます(マスターから送信者とイベント引数を渡します)。
これをフォームのロードに追加します:
masterList.Scroll += new ScrollEventHandler(this.masterList_scroll);
これを参照するもの:
private void masterList_scroll(Object sender, System.ScrollEventArgs e)
{
childList_scroll(sender, e);
}
private void childList_scroll(Object sender, System.ScrollEventArgs e)
{
childList.value = e.NewValue
}
ListViewから継承して独自のクラスを作成し、垂直および水平スクロールイベントを公開します。
その後、フォームにスクロールハンドラを作成して、2つのコントロールを同期します
これは、リストビューがスクロールイベントを発行できるようにするサンプルコードです。
public class MyListView : System.Windows.Forms.ListView
{
const int WM_HSCROLL = 0x0114;
const int WM_VSCROLL = 0x0115;
private ScrollEventHandler evtHScroll_m;
private ScrollEventHandler evtVScroll_m;
public event ScrollEventHandler OnHScroll
{
add
{
evtHScroll_m += value;
}
remove
{
evtHScroll_m -= value;
}
}
public event ScrollEventHandler OnHVcroll
{
add
{
evtVScroll_m += value;
}
remove
{
evtVScroll_m -= value;
}
}
protected override void WndProc(ref System.Windows.Forms.Message msg)
{
if (msg.Msg == WM_HSCROLL && evtHScroll_m != null)
{
evtHScroll_m(this,new ScrollEventArgs(ScrollEventType.ThumbTrack, msg.WParam.ToInt32()));
}
if (msg.Msg == WM_VSCROLL && evtVScroll_m != null)
{
evtVScroll_m(this, new ScrollEventArgs(ScrollEventType.ThumbTrack, msg.WParam.ToInt32()));
}
base.WndProc(ref msg);
}
フォームのスクロールイベントを処理します:
コントロールにWindowsメッセージを送信できるようにPInvokeメソッドを設定します。
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] int iMsg, int iWParam, int iLParam);
イベントハンドラーを設定します(lstMasterとlstChildは2つのリストボックスです):
lstMaster.OnVScroll += new ScrollEventHandler(this.lstMaster_OnVScroll);
lstMaster.OnHScroll += new ScrollEventHandler(this.lstMaster_OnHScroll);
const int WM_HSCROLL = 0x0114;
const int WM_VSCROLL = 0x0115;
private void lstMaster_OnVScroll(Object sender, System.ScrollEventArgs e)
{
SendMessage(lstChild.Handle,WM_VSCROLL,(IntPtr)e.NewValue, IntPtr.Zero);
}
private void lstMaster_OnHScroll(Object sender, System.ScrollEventArgs e)
{
SendMessage(lstChild.Handle,WM_HSCROLL,(IntPtr)e.NewValue, IntPtr.Zero);
}
問題の単純な解決策は、親リストビューでペイントメッセージを処理し、リンクされたリストビューに正しいデータが表示されているかどうかを確認することです。そうでない場合は、EnsureVisibleメソッドを呼び出して正しいデータを表示するように更新します。