locked
Silverlightで複数DataGridによるスクロールの同期 RRS feed

  • 質問

  • こんにちは

    Silverlightで、複数DataGridを横に並べて配置した場合のスクロールの同期方法がわからず悩んでいます。
    (FlexGridでは実現可能な方法があるのは確認しましたが、Silverlightでの実現方法が見つかりませんでした)
    http://www.fxug.net/modules/xhnewbb/viewtopic.php?topic_id=3643

    実現方法がわかる方がいらっしゃいましたら、ご教授をお願いいたします。

    ちなみに、環境は、
    VisualStudio2010+Silverlight4です。

    2011年10月26日 9:09

回答

すべての返信

  • 以下が参考になるのではないかと思います。

    Synchronous scrolling DataGrid in Silverlight
    http://reynaerta.wordpress.com/2010/12/29/synchronous-scrolling-datagrid-in-silverlight/

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    • 回答としてマーク sugarwing 2011年11月4日 4:17
    • 回答としてマークされていない sugarwing 2011年11月6日 14:05
    • 回答の候補に設定 山本春海 2011年12月1日 6:18
    • 回答としてマーク 山本春海 2011年12月2日 9:01
    2011年10月27日 5:27
    モデレータ
  • trapemiyaさん

    返信ありがとうございます。

    教えていただいたサイトの内容を確認し、挑戦してみます。

    結果は後程、ご報告いたします。

    2011年11月1日 6:31
  • trapemiyaさん

    ありがとうございます。
    教えていただいたサイトの方法を試したところ、見事に動作しました。

    こちらの環境がVB.NETでの開発だったため、コンバートしてStaticクラスをModuleに置き換えることで機能しました。

    2011年11月4日 4:22
  • 教えていただいたサイトの情報で2つのDataGridを同期することができるようになったのですが、3つ以上のDataGridを同期させるにはどうしたらいいのでしょうか?
    2011年11月6日 14:05
  • 解決済みだと思い、気が付くのが遅くなりました。すみません。

    ソースを見てみました。基本的には自分(SynchronousScrollDataGrid)がスクロールした際に、自分(SynchronousScrollDataGrid)のSynchronousScrollingDataGridプロパティに設定したSynchronousScrollDataGridをスクロールさせることによって同期する動作になっています。
    であれば、SynchronousScrollingDataGridプロパティを同期させたいSynchronousScrollDataGridの数だけ用意し、それらを全て動かすようにすれば実現できるはずです。
    これが汎用的でない場合、同期させたい複数のSynchronousScrollingDataGridをコレクションで渡すことを考えます。XAMLだとちょっと私は思い付かないので、複数のSynchronousScrollingDataGridを含むGridを渡し、コードでその中から複数のSynchronousScrollingDataGridを取り出す方法を思い付きます。

    また、発想を変えて、イベントで複数のSynchronousScrollingDataGridにスクロールするように通知することも考えられます。この場合、メインのSynchronousScrollingDataGridを一つ決めて、そこに例えばScrollPositionChangedのようなイベントを作成し、各SynchronousScrollingDataGridがそれをリッスンするようにすれば良いでしょう。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年11月9日 4:50
    モデレータ
  • trapemiyaさん

    ご回答ありがとうございます。

    コレクションで渡す方法とイベントでの通知取得は、やり方がすぐに思いつかなかったので、同期させるDataGrid数分SyncronousScrollingDataGridプロパティを作成し、スクロール動作時(UpdateScrollPositionメソッド)に同期させる他の2つのDataGridのスクロールをさせることで何とかできました。

    ただ、この方法だと、DataGridをマウスホイールで移動とDataGridのスクロールバーを直接上下に移動する場合には同期されるのですが、DataGrid上にカーソルがあり、上下キーでレコードを移動した場合のScrollBarの変動が同期できませんでした。

    このDataGrid上での上下キーによる移動で発生するScrollbarの変動でも同期をとるにはどうしたらよいのでしょうか?

     

    以下は追加したプロパティと変更したコントロールメソッドおよびXAML変更部分です。

    ■追加プロパティ(SynchronousScrollDataGrid.cs)■
    public SynchronousScrollDataGrid SynchronousScrollingDataGrid2
    {
        get { return (SynchronousScrollDataGrid)GetValue(SynchronousScrollingDataGrid2Property); }
        set
        {
            SetValue(SynchronousScrollingDataGrid2Property, value);
        }
    }
    
    public static readonly DependencyProperty SynchronousScrollingDataGrid2Property =
       DependencyProperty.Register("SynchronousScrollingDataGrid2", typeof(SynchronousScrollDataGrid), typeof(SynchronousScrollDataGrid), null);
    
    

    ■コントロールメソッド(SynchronousScrollDataGrid.cs)■

    private void UpdateScrollPosition(double newValue)
    {
        SynchronousScrollDataGrid otherDg = GetValue(SynchronousScrollingDataGridProperty) as SynchronousScrollDataGrid;
        if (otherDg != null)
            otherDg.Scroll(ScrollMode.Vertical, newValue);
        //追加した部分↓
        SynchronousScrollDataGrid otherDg2 = GetValue(SynchronousScrollingDataGrid2Property) as SynchronousScrollDataGrid;
        if (otherDg2 != null)
            otherDg2.Scroll(ScrollMode.Vertical, newValue);
        //追加した部分↑
    }
    
    


    ■Xaml(MainPage.xaml)■

    <myControls:SynchronousScrollDataGrid 
                          x:Name="firstDataGrid"
                          SynchronousScrollingDataGrid="{Binding ElementName=secondDataGrid}"
                          SynchronousScrollingDataGrid2="{Binding ElementName=thirdDataGrid}"
                          ItemsSource="{Binding ItemsList1}"
                          CanUserSortColumns="False"
                          RowHeight="20"
                          AutoGenerateColumns="False">
                          :
                          :
    </myControls:SynchronousScrollDataGrid>
    <myControls:SynchronousScrollDataGrid 
                          x:Name="secondDataGrid"
                          Grid.Column="1"
                          RowHeight="20"
                          CanUserSortColumns="False"
                          SynchronousScrollingDataGrid="{Binding ElementName=firstDataGrid}"
                          SynchronousScrollingDataGrid2="{Binding ElementName=thirdDataGrid}"
                          ItemsSource="{Binding ItemsList2}"
                          AutoGenerateColumns="False">
                          :
                          :
    </myControls:SynchronousScrollDataGrid>
    <myControls:SynchronousScrollDataGrid 
                          x:Name="thirdDataGrid"
                          Grid.Column="1"
                          RowHeight="20"
                          CanUserSortColumns="False"
                          SynchronousScrollingDataGrid="{Binding ElementName=firstDataGrid}"
                          SynchronousScrollingDataGrid2="{Binding ElementName=secondDataGrid}"
                          ItemsSource="{Binding ItemsList3}"
                          AutoGenerateColumns="False">
                          :
                          :
    </myControls:SynchronousScrollDataGrid>
    


    • 編集済み sugarwing 2011年11月14日 1:23
    2011年11月14日 1:20
  • VerticalScrollBarのValueChangedイベントで同期させるぐらいでしょうか。上記で紹介したページのオリジナルのソースを以下のように修正します。いずれもSL_Synchronous_DataGrid_Sampleクラスの変更になります。

    //コンストラクタの追加
    public SynchronousScrollDataGrid()
    {
        forceScroll = false;
    }
    
    
    /// <summary>
    /// Get both scrollbars.
    /// </summary>
     private void LoadScrollBars()
    {
        verticalScrollBar = this.GetTemplateChild("VerticalScrollbar") as ScrollBar;
        if (verticalScrollBar != null)
            verticalScrollBar.Scroll += new ScrollEventHandler(OnVerticalScroll);
    
            verticalScrollBar = this.GetScrollbar(ScrollMode.Vertical);
            verticalScrollBar.ValueChanged += new RoutedPropertyChangedEventHandler<double>(verticalScrollBar_ValueChanged);   // <---追加
    }
    
    
    //verticalScrollBar_ValueChangedを追加
    void verticalScrollBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        if (forceScroll)
        {
            forceScroll = false;
            return;
        }
        
        //UpdateScrollPosition(e.NewValue);
        if (e.NewValue > e.OldValue)
            OnVerticalScroll(this, new ScrollEventArgs(ScrollEventType.SmallIncrement, this.GetScrollPosition(ScrollMode.Vertical) + this.RowHeight));
        else if (e.NewValue < e.OldValue)
            OnVerticalScroll(this, new ScrollEventArgs(ScrollEventType.SmallDecrement, this.GetScrollPosition(ScrollMode.Vertical) - this.RowHeight));
    
    }
    
    
    public bool forceScroll { get; set; }   // <---追加。UpdateScrollPosition実行後にもValueChangedイベントが発生するのでそれを無視するための制御
    
    /// <summary>
    /// Updates the scoll position on the other datagrid.
    /// </summary>
    /// <param name="newValue"></param>
    private void UpdateScrollPosition(double newValue)
    {
        SynchronousScrollDataGrid otherDg = GetValue(SynchronousScrollingDataGridProperty) as SynchronousScrollDataGrid;
    
        if (otherDg != null)
        {
            otherDg.forceScroll = true;   // <---追加
            otherDg.Scroll(ScrollMode.Vertical, newValue);
        }
    }
    

    でも、微妙にずれますね。OnMouseWheelのoverrideを真似して書いたんですが、元々マウスホイールだとずれるんですね・・・
    もうちょっと精査する必要があると思いますが、とりあえず方針としてはこんな感じかと。

     


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/
    2011年11月15日 1:55
    モデレータ
  • trapemiyaさん

    ご返信および詳細なソースサンプルまで提示していただきありがとうございます。

    確かに2行分ずれて連動してしまいますね。
    解決方法としてOnSelectedChangedイベントで強制的にforceScrollをFalseにすることでずれることなく連動できました。

    ただ、下キーを押しっぱなしの状態でレコードを移動すると連動先のDataGridのスクロールが2倍位のスクロール量で動き、その際に連動元のスクロールバーの最大値が多くなる方向に変化してしまって、連動元と連動先のスクロールバーの最大値が違くなるという現象が発生しました。(スクロールバーの長さが違ってしまう)
    (※1レコードずつ下キーを押下していく分には問題ありません)
    今回の加工内容としては、verticalScrollBar_ValueChangedを変更して、OnVerticalScroll2というメソッドを用意しました。

    また、OnSelectedChangedイベントをOverrideして上下キーで動作させたときにforceScrollを強制的にFalseにしています。

     

    //OnSelectedChangedイベントでフラグ強制解除
    protected override void OnSelectionChanged(System.Windows.Controls.SelectionChangedEventArgs e)
    {
        forceScroll = false;
        base.OnSelectionChanged(e);
    }
    
    //verticalScrollBar_ValueChangedを追加
    void verticalScrollBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        if (forceScroll)
        {
            forceScroll = false;
            return;
        }
        OnVerticalScroll2(this, new ScrollEventArgs(ScrollEventType.SmallIncrement, this.GetScrollPosition(ScrollMode.Vertical)));
    }
    
    //擬似イベントなのでハンドルなし
    private void OnVerticalScroll2(object sender, ScrollEventArgs e)
    {
        if (e.ScrollEventType != ScrollEventType.EndScroll)
        {
            UpdateScrollPosition(e.NewValue);
        }
    }
    
    


    2011年11月15日 6:48